Загружать существующие компоненты динамически Angular 2 Final Release

Я пытаюсь загрузить динамически компонент в финальной версии 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, resolver больше не существует, был заменен компилятором.

Я нашел множество мест, показывающих, как загрузить его с помощью разных кодов, но все они слишком сложны, и я не мог заставить его работать.

Возьмем это, например: Как я могу использовать/создавать динамический шаблон для компиляции динамического компонента с Angular 2.0?

Это выглядит более конкретным для этого сценария, но мне просто нужно загрузить компонент и установить вызываемую модель @Input.

Одна вещь, когда я пытался создать динамически модуль для каждого компонента, а затем добавить к нему компонент. Но потом у меня возникли проблемы с тем, что компонент был установлен в более чем одном модуле, попытайтесь удалить в некотором месте неработоспособное.

Большая часть показанного кода я получаю по этой ссылке: http://blog.lacolaco.net/post/dynamic-component-creation-in-angular-2-rc-5/

И сделал пару изменений.

Update

Мне удалось заставить его работать, используя следующий подход:

Метод 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, проблема в том, что вы уже заявляете, что тот же компонент в другом модуле, Angular собирается жаловаться. Решение в моем случае состояло в том, чтобы импортировать мой ControlsModule, который экспортирует все мои элементы управления.

Ответ 1

Обновить

NgComponentOutlet был представлен в 4.0.0-beta.3 https://github.com/angular/angular/commit/8578682

Скоро NgComponentOutlet

Я вижу два варианта:

1) Использование ComponentFactoryResolver.

Он использует уже сгенерированный factory, и код выглядит примерно так:

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

В этом случае мы должны определить динамическую составляющую в declarations и entryComponents свойствах внутри декоратора модуля

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

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

Пример плунжера

Надеюсь, это поможет вам!

Ответ 2

Мне было полезно увидеть этот вариант на основе директивы, но я нашел ту, которая соответствует моим потребностям на основе Компонентов: https://www.ag-grid.com/ag-grid-angular-aot-dynamic-components/

Счастливое кодирование!