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

IT技术 javascript angular typescript
2021-03-10 18:38:08

我正在尝试在最终版本 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 最终版本后,解析器不再存在,被编译器取代。

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

以这个为例:如何使用/创建动态模板来编译带有 Angular 2.0 的动态组件?

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

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

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

并做了一些改变。

更新

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

创建方法已更改为

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,问题是当您已经在不同的module中声明相同的组件时,angular 会抱怨。在我的情况下,解决方案是导入我的 ControlsModule,其中所有控件都被导出。

2个回答

更新

NgComponentOutlet4.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;
}

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

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent, DynamicComponent ],
  entryComponents: [DynamicComponent],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

2) 使用编译器

在这种情况下,我们只能通过 using 运行module编译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 示例

希望对你有帮助!

非常好的例子.. @Yurzui - 我遇到了无法将一个组件嵌套到另一个组件的限制,例如创建了另一个组件,它使用图像和按钮一起显示并在 app.ts 中使用新组件。你认为这可能吗?如果是,请提供一个例子。
2021-05-01 18:38:08
感谢您的精彩解释,您从rango.io 提供的链接正是我需要做的,因为我的组件是一个模态,会在内部呈现一些控件。感谢您提供的 plunker 示例。你帮了我很多。使用 ComponentFactoryResolver 比使用 Compiler 有什么优势吗?我发现使用第一个更容易。
2021-05-04 18:38:08
嘿 Yurzui 有什么优势吗?我看到第二个有缓存,我有点不需要它。我试图找出一种不仅在重新创建组件时而且在关闭动态模态时销毁组件的方法。
2021-05-11 18:38:08

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

快乐编码!