Как реализовать динамические компоненты?
В Angular динамическими называются компоненты, которые создаются и вставляются в DOM во время выполнения (runtime), а не заранее определены в шаблоне. Это позволяет реализовывать такие сценарии, как диалоговые окна, виджеты, табы, динамические формы, шаблоны на основе конфигурации, плагин-системы и т.д.
1. Базовая реализация через ViewContainerRef + ComponentRef
1.1. Подготовка компонента для динамической вставки
@Component({
selector: 'app-dynamic-child',
template: \`<p>Я динамический компонент</p>\`
})
export class DynamicChildComponent {}
1.2. Контейнер в шаблоне хоста
<ng-template #container></ng-template>
1.3. Логика создания компонента
@ViewChild('container', { read: ViewContainerRef, static: true })
container!: ViewContainerRef;
constructor(private cfr: ComponentFactoryResolver) {}
loadComponent() {
this.container.clear();
const factory = this.cfr.resolveComponentFactory(DynamicChildComponent);
const componentRef = this.container.createComponent(factory);
}
2. Использование createComponent без ComponentFactory (Angular 13+)
С версии Angular 13 ComponentFactoryResolver не обязателен.
import { DynamicChildComponent } from './dynamic-child.component';
@ViewChild('container', { read: ViewContainerRef, static: true })
vcRef!: ViewContainerRef;
loadComponent() {
this.vcRef.clear();
const componentRef = this.vcRef.createComponent(DynamicChildComponent);
componentRef.instance.property = 'значение';
}
3. Передача данных в динамический компонент
Если у динамического компонента есть входные свойства (@Input()), их можно задать после создания:
componentRef.instance.title = 'Динамический заголовок';
componentRef.instance.items = \['a', 'b', 'c'\];
4. Получение событий через @Output
componentRef.instance.closed.subscribe(() => {
this.vcRef.clear();
});
Обязательно отписываться или использовать takeUntil внутри компонента.
5. Использование ngComponentOutlet
Более декларативный способ — использовать директиву ngComponentOutlet.
<ng-container \*ngComponentOutlet="currentComponent"></ng-container>
В TS:
currentComponent = DynamicChildComponent;
Дополнительно можно передавать:
-
ngComponentOutletInjector — инжектор;
-
ngComponentOutletContent — контент для <ng-content>;
-
ngComponentOutletNgModuleFactory — модуль.
6. Создание компонентов по конфигурации
Допустим, есть конфигурация:
const config = \[
{ type: ChartComponent, data: { chartId: 1 } },
{ type: TableComponent, data: { columns: 3 } }
\];
Цикл создания:
for (let item of config) {
const ref = this.vcRef.createComponent(item.type);
Object.assign(ref.instance, item.data);
}
7. Модульная загрузка (Lazy Loading) динамических компонентов
С Angular 13+ можно лениво загружать компоненты:
const { LazyComponent } = await import('./lazy.component');
this.vcRef.createComponent(LazyComponent);
Это особенно полезно для снижения размера основного бандла.
8. Встраивание контента через ng-content
Компоненты с ng-content можно наполнять через ngComponentOutletContent.
<ng-container \*ngComponentOutlet="comp; ngComponentOutletContent: \[\[tpl\]\]"></ng-container>
<ng-template #tpl>
<p>Вставленный HTML</p>
</ng-template>
9. Использование Injector, EnvironmentInjector и ChangeDetectorRef
Если необходимо передать зависимости:
const injector = Injector.create({
providers: \[
{ provide: SomeService, useClass: SomeService, deps: \[\] }
\]
});
this.vcRef.createComponent(DynamicComponent, {
injector
});
Можно использовать EnvironmentInjector из Angular 14+ для создания компонента вне контекста модуля.
10. Хранение ссылок на компоненты
Иногда полезно хранить ComponentRef в массиве, чтобы управлять созданными экземплярами:
components: ComponentRef<any>\[\] = \[\];
const ref = this.vcRef.createComponent(DynamicComponent);
this.components.push(ref);
// Позднее можно вызвать destroy
this.components.forEach(c => c.destroy());
11. Удаление компонента
ref.destroy(); // уничтожить конкретный
this.vcRef.clear(); // удалить всё из контейнера
12. Общий компонент-обёртка для создания по типу
Создание универсального DynamicHostComponent, который принимает тип компонента и входные данные:
@Component({
selector: 'app-dynamic-host',
template: \`<ng-template #host></ng-template>\`
})
export class DynamicHostComponent {
@ViewChild('host', { read: ViewContainerRef }) host!: ViewContainerRef;
load<T>(component: Type<T>, data: Partial<T>) {
const ref = this.host.createComponent(component);
Object.assign(ref.instance, data);
}
}
13. Интеграция с диалогами (например, Angular Material)
this.dialog.open(SomeComponent, {
data: { userId: 42 },
width: '600px'
});
Внутри SomeComponent:
constructor(@Inject(MAT_DIALOG_DATA) public data: any) {}
14. Плагины или кастомные виджеты
Можно создать реестр:
const registry = {
chart: ChartComponent,
table: TableComponent
};
const type = registry\[config.type\];
const ref = this.vcRef.createComponent(type);
Такой подход удобен для CMS, форм-конструкторов и административных панелей.
15. Динамические модули
С loadRemoteModule() или NgModuleFactory, можно загружать не только компонент, но и его модуль. Это используется в micro frontend архитектуре или для плагин-систем.
Динамические компоненты в Angular позволяют строить гибкие и расширяемые приложения, управлять отображением компонентов во время выполнения, использовать шаблоны и конфигурации, а также разделять крупные модули на независимые части.