Загружать существующие компоненты динамически 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/
Счастливое кодирование!