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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39678963/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 03:52:21  来源:igfitidea点击:

Load existing components dynamically Angular 2 Final Release

javascriptangulartypescript

提问by Abner

I'm trying to load dynamically a component in the final release 2.0.0.

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

Using RC5 I was loading using the following code:

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

Create a directive to load the controls:

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

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

Then loaded that directive in my page like this:

然后在我的页面中加载该指令,如下所示:

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

But after updating from rc5 to 2.0.0 final release, the resolver doesn't exist anymore, was replaced by compiler.

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

I found loads of places showing how to load it using different codes, but all those too complex and I couldn't make it work.

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

Take this for instance: How can I use/create dynamic template to compile dynamic Component with Angular 2.0?

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

It looks more specific to that scenario, my one I just need to load the component and set an @Input called model.

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

One thing when I was trying I had to create dynamically a module for each component, then add the component to it. But then I had issues saying that the component was being set in more than one Module, try to remove in some place an not working.

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

The major part of the code shown, I get from this link: http://blog.lacolaco.net/post/dynamic-component-creation-in-angular-2-rc-5/

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

And did a couple of changes.

并做了一些改变。

Update

更新

I manage to make it work, using the following approach:

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

The create method has been changed to

创建方法已更改为

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;
      });
  }

Most places I've found, set create the Component and set it to the DynamicModule, the issue with that is when you are already declaring that same component in a different module, angular is going to complain about. The solution in my case was to import the my ControlsModule that has all my controls being exported.

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

回答by yurzui

Update

更新

NgComponentOutletwas introduced in 4.0.0-beta.3https://github.com/angular/angular/commit/8578682

NgComponentOutlet4.0.0-beta.3 中引入https://github.com/angular/angular/commit/8578682

Coming soon NgComponentOutlet

即将推出NgComponentOutlet

I see two options to do that:

我看到两个选项可以做到这一点:

1) Using ComponentFactoryResolver.

1) 使用ComponentFactoryResolver

It uses the already generated factory and the code looks something like this:

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

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

In this case we have to define dynamic component in declarationsand entryComponentsproperties within decorator of module

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

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

2) Using Compiler

2) 使用编译器

In this case we can only run module compilation by using compiler.compileModuleAndAllComponentsAsyncand then find component from componentFactories array. Your directive might look like this:

在这种情况下,我们只能通过 using 运行模块编译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;
  })
}

DynamicLoaderServiceis a global service that will load and store component factories.

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

Plunker 示例

Hope it helps you!

希望对你有帮助!

回答by Mateo Tibaquira

It was useful for me to see this option based on a Directive, but I found the one that fit my needs based on Components: https://www.ag-grid.com/ag-grid-angular-aot-dynamic-components/

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

Happy coding!

快乐编码!