动态加载现有组件 Angular 2 Final Release

Posted

技术标签:

【中文标题】动态加载现有组件 Angular 2 Final Release【英文标题】:Load existing components dynamically Angular 2 Final Release 【发布时间】:2017-02-02 09:18:39 【问题描述】:

我正在尝试在最终版本 2.0.0 中动态加载组件。

使用 RC5 我正在使用以下代码加载:

创建一个指令来加载控件:

import 
  CheckboxComponent, CheckboxListComponent,DatePickerComponent
 from '../components/';

@Directive(
      selector: '[ctrl-factory]'
    )
    export class ControlFactoryDirective implements OnChanges 
      @Input() model: any;

      constructor(private vcRef: ViewContainerRef, private resolver: ComponentResolver) 
      

      create(cp) 
        this.resolver.resolveComponent(cp)
          .then(factory => 
            const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
            this.vcRef.createComponent(factory, 0, injector, []);
            let ch = this.vcRef.createComponent(factory, 0, injector, []).instance;
            ch.model = this.model;
        );
      

      ngOnChanges() 
        if (!this.model) return;

        switch (this.model.type) 
          case 'checkbox':
            this.create(CheckboxComponent);
            break;
          case 'checkboxlist':
            this.create(CheckboxListComponent);
            break;
          case 'datepicker':
            this.create(DatePickerComponent);
            break;
          default:
            break;
        
      
    

然后像这样在我的页面中加载该指令:

<div ctrl-factory *ngFor="let child of page.childrens" [model]="child"></div>

但是从 rc5 更新到 2.0.0 最终版本后,解析器不再存在,被编译器取代。

我发现很多地方展示了如何使用不同的代码加载它,但所有这些都太复杂了,我无法让它工作。

以此为例:How can I use/create dynamic template to compile dynamic Component with Angular 2.0?

它看起来更具体到那个场景,我只需要加载组件并设置一个名为模型的@Input。

当我尝试时,我必须为每个组件动态创建一个模块,然后将组件添加到其中。但是后来我遇到了问题,说该组件被设置在多个模块中,请尝试在某些地方删除一个不起作用的地方。

显示的代码的主要部分,我从这个链接得到:http://blog.lacolaco.net/post/dynamic-component-creation-in-angular-2-rc-5/

并进行了一些更改。

更新

我设法使它工作,使用以下方法:

create 方法已更改为

private create(cp) 
    @NgModule(
      imports: [BrowserModule, ControlsModule],
      declarations: []
    )
    class DynamicModule 

    this.compiler.compileModuleAndAllComponentsAsync(DynamicModule)
      .then((componentFactories) => 
        const compFactory = componentFactories.find(x => x.componentType === cp);
        const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
        const cmpRef = this.vcRef.createComponent(compFactory, 0, injector, []);
        cmpRef.instance.model = this.model;
      );
  

我发现的大多数地方,设置创建组件并将其设置为 DynamicModule,问题是当您已经在不同的模块中声明相同的组件时,角度会抱怨。在我的情况下,解决方案是导入我的 ControlsModule,该模块已导出我的所有控件。

【问题讨论】:

我正在尝试创建一个类似的东西。我有一个 JSON 文件,可以加载课程及其讲座列表。课程被添加到导航栏,讲座被添加为详细视图中的列表。单击讲座时,应在详细视图中加载组件。对于每个讲座,都有一个不同的组件,我正在考虑为每个讲座(和一个路由器插座)创建一个模块或路由器链接,而不是像您的示例那样仅加载一个组件。我仍在尝试找出加载内容的最佳方式。 【参考方案1】:

更新

NgComponentOutlet 是在 4.0.0-beta.3 https://github.com/angular/angular/commit/8578682

中引入的

即将推出NgComponentOutlet

我看到了两种选择:

1) 使用 ComponentFactoryResolver

它使用已经生成的工厂,代码如下所示:

constructor(private vcRef: ViewContainerRef, private resolver: ComponentFactoryResolver)  
create(comp) 
  const factory = this.resolver.resolveComponentFactory(comp);
  const compRef = this.vcRef.createComponent(factory);

  (<any>compRef).instance.model = this.model;

在这种情况下,我们必须在模块的装饰器中的declarationsentryComponents 属性中定义动态组件

@NgModule(
  imports:      [ BrowserModule ],
  declarations: [ AppComponent, DynamicComponent ],
  entryComponents: [DynamicComponent],
  bootstrap:    [ AppComponent ]
)
export class AppModule  
Dynamically Creating Components With Angular 2.0 Plunker Example

2) 使用编译器

在这种情况下,我们只能使用compiler.compileModuleAndAllComponentsAsync 运行模块编译,然后从componentFactories 数组中查找组件。您的指令可能如下所示:

constructor(private vcRef: ViewContainerRef, private loader: DynamicLoaderService)  

create(comp) 
  this.loader.createComponentFactory(comp).then(factory => 
     const compRef = this.vcRef.createComponent(factory);

    (<any>compRef).instance.model = this.model;
  )

DynamicLoaderService 是一个全局服务,它将加载和存储组件工厂。

@Injectable()
export class DynamicLoaderService 
  constructor(protected compiler: Compiler) 

  private resolveCompHelper$ = new Subject<any>();
  private cache = new Map<string, ComponentFactory<any> | number>();

  public createComponentFactory(type: string) : Promise<ComponentFactory<any>> 
    let factory = this.cache.get(type);

    // if factory has been already loading
    if(factory === 1) 
      return new Promise((resolve) => 
        // waiting compilation of factory
        const subscriber = this.resolveCompHelper$.subscribe((data) => 
          if(type !== data.type) return;
          subscriber.unsubscribe();
          resolve(data.factory);
        );   
      );
     
    // factory exists in cache
    if (factory) 
      return new Promise((resolve) => resolve(factory));
    

    const comp = typeMap[type];
    // factory startes loading
    this.cache.set(type, 1);
    return new Promise((resolve) => 
      this.compiler.compileModuleAndAllComponentsAsync(createComponentModule(comp))
        .then((moduleWithFactories: ModuleWithComponentFactories<any>) =>  
            factory = moduleWithFactories.componentFactories
              .find(x => x.componentType === comp);
            this.cache.set(type, factory);
            this.resolveCompHelper$.next( type, factory);

            resolve(factory);
        );
    );
  

Plunker Example

希望对你有帮助!

【讨论】:

感谢您的精彩解释,您从rango.io 提供的链接正是我需要做的,因为我的组件是一个模式,可以在其中呈现一些控件。非常感谢您提供的 plunker 示例。你帮了我很多。使用 ComponentFactoryResolver 比 Compiler 有什么优势吗?我发现使用第一个更容易。 嘿 Yurzui 有什么优势吗?我看到第二个有缓存,我有点不需要它。我试图找出一种方法来破坏组件,不仅是在我重新创建它们时,而且在我关闭动态模式时也是如此。 非常好的例子.. @Yurzui - 我遇到了一些限制,我不能将一个组件嵌套到另一个组件中,例如创建了另一个组件,它使用 Image 和 Button 一起显示并使用 app.ts 中的新组件。你认为这可能吗?如果是的话,请提供一个例子。【参考方案2】:

看到这个基于指令的选项对我很有用,但我根据组件找到了适合我需求的选项:https://www.ag-grid.com/ag-grid-angular-aot-dynamic-components/

编码愉快!

【讨论】:

以上是关于动态加载现有组件 Angular 2 Final Release的主要内容,如果未能解决你的问题,请参考以下文章

Angular 2 - 使用 ComponentResolver 加载在运行时下载的组件

Angular 5 - 动态组件模板加载

Angular - 动态加载选项卡名称和组件

从 Angular 组件动态加载外部 javascript 文件

在 Angular 中为动态加载的组件提供 @input

在Angular中动态加载组件[重复]