Как динамически создавать компоненты с ComponentFactoryResolver?angular-51

Динамическое создание компонентов в Angular с использованием ComponentFactoryResolver — мощный механизм для рендеринга компонентов во время выполнения. Этот подход особенно полезен для:

  • Модальных окон
  • Динамических форм
  • Виджет-систем
  • Прочих сценариев, когда компоненты неизвестны на этапе компиляции

Шаги для динамического создания компонента

1. Подготовка компонента

Сначала создадим компонент, который будем рендерить динамически:

import { Component } from '@angular/core';

@Component({
  selector: 'app-dynamic',
  template: `<h2>Динамический компонент</h2>
             <p>Создан в runtime!</p>`
})
export class DynamicComponent {}

2. Добавление компонента в entryComponents

В более старых версиях Angular нужно было добавлять динамический компонент в entryComponents модуля:

@NgModule({
  declarations: [DynamicComponent],
  entryComponents: [DynamicComponent] // Требовалось до Angular 13
})
export class AppModule {}

В Angular 13+ это больше не требуется благодаря Ivy.

3. Основной процесс создания компонента

import { Component, ComponentFactoryResolver, ViewChild, ViewContainerRef, AfterViewInit } from '@angular/core';
import { DynamicComponent } from './dynamic.component';

@Component({
  selector: 'app-host',
  template: `<ng-container #container></ng-container>`
})
export class HostComponent implements AfterViewInit {
  @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;

  constructor(private resolver: ComponentFactoryResolver) {}

  ngAfterViewInit() {
    this.createDynamicComponent();
  }

  createDynamicComponent() {
    // 1. Очищаем контейнер
    this.container.clear();

    // 2. Получаем фабрику компонента
    const factory = this.resolver.resolveComponentFactory(DynamicComponent);

    // 3. Создаем компонент
    const componentRef = this.container.createComponent(factory);

    // 4. Работаем с экземпляром компонента
    componentRef.instance.someProperty = 'Значение';

    // 5. Подписываемся на события
    componentRef.instance.someEvent.subscribe(...);
  }
}

Важные моменты реализации

1. ViewContainerRef

ViewContainerRef — это контейнер, куда будет вставлен компонент. Получаем его через @ViewChild.

2. ComponentFactoryResolver

Сервис, который создает фабрику компонента по его типу.

3. ComponentRef

Результат создания компонента, содержащий:

  • instance — экземпляр компонента
  • hostView — представление компонента
  • destroy() — метод для удаления компонента

4. Передача входных данных

// Если DynamicComponent имеет @Input() inputData;
componentRef.instance.inputData = { key: 'value' };

5. Подписка на выходные события

// Если DynamicComponent имеет @Output() action = new EventEmitter();
componentRef.instance.action.subscribe(value => {
  console.log('Получено значение:', value);
});

6. Уничтожение компонента

componentRef.destroy();

Современный подход с Angular 13+

С введением Ivy API упростилось:

createDynamicComponent() {
  this.container.clear();
  const componentRef = this.container.createComponent(DynamicComponent);

  // Работа с компонентом остается такой же
  componentRef.instance.title = 'Динамический заголовок';
}

Оптимизация и лучшие практики

  1. Управление памятью
    Всегда вызывайте destroy() для динамических компонентов при уничтожении родителя.

  2. Change Detection
    Динамические компоненты участвуют в проверке изменений. Для оптимизации можно использовать OnPush.

  3. Инжекция зависимостей
    Динамические компоненты получают инжектор от родительского компонента.

  4. Множественные компоненты
    Можно создавать несколько экземпляров в одном контейнере:

const componentRef1 = this.container.createComponent(factory, 0);
const componentRef2 = this.container.createComponent(factory, 1);

Альтернативы

Для простых случаев можно использовать *ngComponentOutlet:

<ng-container *ngComponentOutlet="dynamicComponent"></ng-container>

Резюмируем

ComponentFactoryResolver (в старых версиях Angular) или прямое использование ViewContainerRef.createComponent() (в Ivy) позволяют динамически создавать и управлять компонентами во время выполнения. Это мощный инструмент для сложных динамических интерфейсов, требующий внимательного управления жизненным циклом создаваемых компонентов.