Как работать с @ContentChildren и QueryList?angular-59

@ContentChildren и QueryList — это мощные инструменты Angular для работы с проекцией контента (content projection) и динамическими дочерними элементами. Они позволяют компоненту взаимодействовать с элементами, которые передаются в его слот <ng-content>.

@ContentChildren - декларация дочерних элементов

@ContentChildren — это декоратор, который выполняет запрос к проекционному контенту компонента.

Основные особенности:

  • Получает список дочерних элементов/директив/компонентов
  • Работает только с проекционным контентом (<ng-content>)
  • Возвращает QueryList — специальную коллекцию Angular
  • Запрос выполняется после инициализации контента (ngAfterContentInit)

Синтаксис:

@ContentChildren(selector, options?) propertyName: QueryList<T>;

Параметры:

  1. selector - может быть:
    • Тип директивы/компонента
    • Строковый селектор (как в CSS)
    • TemplateRef
    • ViewContainerRef
  2. options (опционально) - дополнительные параметры:
    • descendants: boolean - включать ли потомков
    • read: any - получить конкретный тип (например, ViewContainerRef)

QueryList - динамическая коллекция

QueryList — это специальная коллекция Angular, которая:

  • Содержит результаты запроса @ContentChildren
  • Автоматически обновляется при изменении DOM
  • Предоставляет методы для работы с коллекцией
  • Имеет свойство changes — Observable для отслеживания изменений

Основные методы и свойства:

interface QueryList<T> {
  length: number;
  first: T;
  last: T;
  get(index: number): T;
  forEach(fn: (item: T, index: number) => void): void;
  map<U>(fn: (item: T, index: number) => U): U[];
  filter(fn: (item: T, index: number) => boolean): T[];
  find(fn: (item: T, index: number) => boolean): T|undefined;
  reduce<U>(fn: (prevValue: U, curValue: T, curIndex: number) => U, init: U): U;
  some(fn: (value: T, index: number) => boolean): boolean;
  toArray(): T[];
  changes: Observable<any>;
}

Практические примеры

Пример 1: Базовое использование

@Component({
  selector: 'app-tab-group',
  template: `
    <div class="tab-header">
      <ng-content select="app-tab-header"></ng-content>
    </div>
    <div class="tab-content">
      <ng-content select="app-tab-content"></ng-content>
    </div>
  `
})
export class TabGroupComponent implements AfterContentInit {
  @ContentChildren(TabHeaderComponent) headers: QueryList<TabHeaderComponent>;
  @ContentChildren(TabContentComponent) contents: QueryList<TabContentComponent>;

  ngAfterContentInit() {
    // Синхронизация заголовков и содержимого вкладок
    this.headers.forEach((header, index) => {
      header.click.subscribe(() => this.activateTab(index));
    });
  }

  activateTab(index: number) {
    this.contents.forEach((content, i) => content.isActive = i === index);
  }
}

Пример 2: Отслеживание изменений

@Component({
  selector: 'app-list',
  template: `<ng-content></ng-content>`
})
export class ListComponent implements AfterContentInit {
  @ContentChildren(ListItemDirective) items: QueryList<ListItemDirective>;

  ngAfterContentInit() {
    this.items.changes.subscribe(() => {
      console.log('Количество элементов изменилось:', this.items.length);
    });
  }
}

Пример 3: Работа с разными типами селекторов

@Component({
  selector: 'app-example',
  template: `
    <ng-content></ng-content>
    <ng-content select=".special"></ng-content>
  `
})
export class ExampleComponent {
  // Запрос по типу директивы
  @ContentChildren(HighlightDirective) highlights: QueryList<HighlightDirective>;

  // Запрос по CSS-селектору
  @ContentChildren('.special') specialItems: QueryList<ElementRef>;

  // Получение ViewContainerRef
  @ContentChildren(TemplateRef, {read: ViewContainerRef}) templates: QueryList<ViewContainerRef>;
}

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

  1. Используйте ngAfterContentInit для начального доступа к QueryList
  2. Отписывайтесь от Observable при уничтожении компонента
  3. Избегайте частых проверок changes в цикле изменений
  4. Используйте read параметр для точного указания типа
  5. Кешируйте результаты если они используются часто

Резюмируем

  1. @ContentChildren используется для запроса проекционного контента
  2. QueryList предоставляет динамическую коллекцию элементов
  3. Основные сценарии использования:
    • Взаимодействие с вложенными компонентами
    • Создание сложных составных компонентов (табы, аккордеоны)
    • Динамическое управление проекционным контентом
  4. Жизненный цикл:
    • Запрос инициализируется в ngAfterContentInit
    • Изменения отслеживаются через changes Observable
  5. Оптимизация:
    • Избегайте лишних подписок
    • Используйте правильные селекторы
    • Учитывайте производительность при работе с большими коллекциями

Эти инструменты особенно полезны при создании библиотек компонентов и сложных динамических интерфейсов.