Spec-Zone.ru › Angular

Поиск дочерних компонентов с помощью запросов

СОВЕТ: Предполагается, что вы уже прочитали руководство по основам. Если вы только знакомитесь с Angular, сначала прочитайте его.

Компонент может задавать запросы для поиска дочерних элементов и чтения значений из их инжекторов.

Чаще всего разработчики используют запросы, чтобы получать ссылки на дочерние компоненты, директивы, элементы DOM и многое другое.

Все функции запросов возвращают сигналы, отражающие самые актуальные результаты. Результат можно прочитать, вызвав функцию сигнала, в том числе в реактивных контекстах, таких как computed и effect.

Запросы делятся на две категории: запросы представления и запросы содержимого.

Запросы представления

Запросы представления получают результаты из элементов представления компонента — элементов, определённых в собственном шаблоне компонента. Для поиска одного результата можно использовать функцию viewChild.

@Component({
  selector: 'custom-card-header',
  /*...*/
})
export class CustomCardHeader {
  text: string;
}

@Component({
  selector: 'custom-card',
  template: '<custom-card-header>Visit sunny California!</custom-card-header>',
})
export class CustomCard {
  header = viewChild(CustomCardHeader);
  headerText = computed(() => this.header()?.text);
}

В этом примере компонент CustomCard выполняет запрос для поиска дочернего элемента CustomCardHeader и использует результат в computed.

Если запрос не находит результат, его значение равно undefined. Это может произойти, если целевой элемент скрыт с помощью @if. Angular поддерживает результат viewChild в актуальном состоянии по мере изменения состояния приложения.

Для поиска нескольких результатов также можно использовать функцию viewChildren.

@Component({
  selector: 'custom-card-action',
  /*...*/
})
export class CustomCardAction {
  text: string;
}

@Component({
  selector: 'custom-card',
  template: `
    <custom-card-action>Save</custom-card-action>
    <custom-card-action>Cancel</custom-card-action>
  `,
})
export class CustomCard {
  actions = viewChildren(CustomCardAction);
  actionsTexts = computed(() => this.actions().map((action) => action.text));
}

viewChildren создаёт сигнал с Array результатов запроса.

Запросы никогда не пересекают границы компонентов. Запросы представления могут получать результаты только из шаблона компонента.

Запросы содержимого

Запросы содержимого получают результаты из элементов содержимого компонента — элементов, вложенных в компонент в шаблоне, где он используется. Для поиска одного результата можно использовать функцию contentChild.

@Component({
  selector: 'custom-toggle',
  /*...*/
})
export class CustomToggle {
  text: string;
}

@Component({
  selector: 'custom-expando',
  /* ... */
})
export class CustomExpando {
  toggle = contentChild(CustomToggle);
  toggleText = computed(() => this.toggle()?.text);
}

@Component({
  /* ... */
  // CustomToggle is used inside CustomExpando as content.
  template: `
    <custom-expando>
      <custom-toggle>Show</custom-toggle>
    </custom-expando>
  `,
})
export class UserProfile {}

Если запрос не находит результат, его значение равно undefined. Это может произойти, если целевой элемент отсутствует или скрыт с помощью @if. Angular поддерживает результат contentChild в актуальном состоянии по мере изменения состояния приложения.

По умолчанию запросы содержимого находят только непосредственных дочерних элементов компонента и не обходят вложенные элементы.

Для поиска нескольких результатов также можно использовать функцию contentChildren.

@Component({
  selector: 'custom-menu-item',
  /*...*/
})
export class CustomMenuItem {
  text: string;
}

@Component({
  selector: 'custom-menu',
  /*...*/
})
export class CustomMenu {
  items = contentChildren(CustomMenuItem);
  itemTexts = computed(() => this.items().map((item) => item.text));
}

@Component({
  selector: 'user-profile',
  template: `
    <custom-menu>
      <custom-menu-item>Cheese</custom-menu-item>
      <custom-menu-item>Tomato</custom-menu-item>
    </custom-menu>
  `,
})
export class UserProfile {}

contentChildren создаёт сигнал с Array результатов запроса.

Запросы никогда не пересекают границы компонентов. Запросы содержимого могут получать результаты только из того же шаблона, что и сам компонент.

Обязательные запросы

Если запрос дочернего элемента (viewChild или contentChild) не находит результат, его значение равно undefined. Это может произойти, если целевой элемент скрыт оператором управления потоком, например @if или @for. Поэтому запросы дочерних элементов возвращают сигнал, в тип значения которого входит undefined.

В некоторых случаях, особенно при использовании viewChild, вы точно знаете, что определённый дочерний элемент всегда доступен. В других случаях может потребоваться строго гарантировать наличие определённого дочернего элемента. Для этого можно использовать обязательный запрос.

@Component({
  /*...*/
})
export class CustomCard {
  header = viewChild.required(CustomCardHeader);
  body = contentChild.required(CustomCardBody);
}

Если обязательный запрос не находит соответствующий результат, Angular сообщает об ошибке. Поскольку это гарантирует наличие результата, тип значения сигнала обязательного запроса не включает undefined автоматически.

Локаторы запросов

Первый параметр каждого декоратора запроса — это его локатор.

Чаще всего в качестве локатора используют компонент или директиву.

Также можно указать строковый локатор, соответствующий переменной ссылки на шаблон.

@Component({
  /*...*/
  template: `
    <button #save>Save</button>
    <button #cancel>Cancel</button>
  `,
})
export class ActionBar {
  saveButton = viewChild<ElementRef<HTMLButtonElement>>('save');
}

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

Angular не поддерживает CSS-селекторы в качестве локаторов запросов.

Запросы и дерево инжекторов

СОВЕТ: Подробнее о провайдерах и дереве внедрения зависимостей Angular см. в разделе Внедрение зависимостей.

В более сложных случаях в качестве локатора можно использовать любой ProviderToken. Это позволяет находить элементы по провайдерам компонентов и директив.

const SUB_ITEM = new InjectionToken<string>('sub-item');

@Component({
  /*...*/
  providers: [{provide: SUB_ITEM, useValue: 'special-item'}],
})
export class SpecialItem {}

@Component({
  /*...*/
})
export class CustomList {
  subItemType = contentChild(SUB_ITEM);
}

В примере выше в качестве локатора используется InjectionToken, но для поиска конкретных элементов можно использовать любой ProviderToken.

Параметры запросов

Все функции запросов принимают объект параметров вторым аргументом. Эти параметры определяют, как запрос находит результаты.

Чтение конкретных значений из инжектора элемента

По умолчанию локатор запроса задаёт и элемент, который нужно найти, и значение, которое следует получить. Вместо этого можно указать параметр read, чтобы получить другое значение из элемента, найденного по локатору.

@Component({
  /*...*/
})
export class CustomExpando {
  toggle = contentChild(ExpandoContent, {read: TemplateRef});
}

В приведённом выше примере находится элемент с директивой ExpandoContent и извлекается связанный с этим элементом объект TemplateRef.

Чаще всего разработчики используют read для получения ElementRef и TemplateRef.

Вложенные элементы содержимого

По умолчанию запросы contentChildren находят только непосредственных дочерних элементов компонента и не обходят вложенные элементы. Запросы contentChild по умолчанию обходят вложенные элементы.

@Component({
  selector: 'custom-expando',
  /*...*/
})
export class CustomExpando {
  toggle = contentChildren(CustomToggle); // none found
  // toggle = contentChild(CustomToggle); // found
}

@Component({
  selector: 'user-profile',
  template: `
    <custom-expando>
      <some-other-component>
        <custom-toggle>Show</custom-toggle>
      </some-other-component>
    </custom-expando>
  `,
})
export class UserProfile {}

В примере выше CustomExpando не может найти <custom-toggle> с помощью contentChildren, поскольку он не является непосредственным дочерним элементом <custom-expando>. Задав descendants: true, вы настроите запрос на обход всех вложенных элементов в том же шаблоне. Однако запросы никогда не проникают внутрь компонентов для обхода элементов в других шаблонах.

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

Запросы на основе декораторов

СОВЕТ: Команда Angular рекомендует использовать функции запросов на основе сигналов в новых проектах, однако исходные API запросов на основе декораторов по-прежнему полностью поддерживаются.

Вместо этого можно объявить запросы, добавив соответствующий декоратор к свойству. Запросы на основе декораторов работают так же, как запросы на основе сигналов, за исключением описанных ниже случаев.

Запросы представления

Для поиска одного результата можно использовать декоратор @ViewChild.

@Component({
  selector: 'custom-card-header',
  /*...*/
})
export class CustomCardHeader {
  text: string;
}

@Component({
  selector: 'custom-card',
  template: '<custom-card-header>Visit sunny California!</custom-card-header>',
})
export class CustomCard implements AfterViewInit {
  @ViewChild(CustomCardHeader) header: CustomCardHeader;

  ngAfterViewInit() {
    console.log(this.header.text);
  }
}

В этом примере компонент CustomCard выполняет запрос для поиска дочернего элемента CustomCardHeader и обращается к результату в ngAfterViewInit.

Angular поддерживает результат @ViewChild в актуальном состоянии по мере изменения состояния приложения.

Результаты запросов представления становятся доступны в методе жизненного цикла ngAfterViewInit. До этого момента значение равно undefined. Подробную информацию о жизненном цикле компонента см. в разделе Жизненный цикл.

Для поиска нескольких результатов также можно использовать декоратор @ViewChildren.

@Component({
  selector: 'custom-card-action',
  /*...*/
})
export class CustomCardAction {
  text: string;
}

@Component({
  selector: 'custom-card',
  template: `
    <custom-card-action>Save</custom-card-action>
    <custom-card-action>Cancel</custom-card-action>
  `,
})
export class CustomCard implements AfterViewInit {
  @ViewChildren(CustomCardAction) actions: QueryList<CustomCardAction>;

  ngAfterViewInit() {
    this.actions.forEach((action) => {
      console.log(action.text);
    });
  }
}

@ViewChildren создаёт объект QueryList, содержащий результаты запроса. Подписаться на изменения результатов запроса со временем можно с помощью свойства changes.

Запросы содержимого

Для поиска одного результата можно использовать декоратор @ContentChild.

@Component({
  selector: 'custom-toggle',
  /*...*/
})
export class CustomToggle {
  text: string;
}

@Component({
  selector: 'custom-expando',
  /*...*/
})
export class CustomExpando implements AfterContentInit {
  @ContentChild(CustomToggle) toggle: CustomToggle;

  ngAfterContentInit() {
    console.log(this.toggle.text);
  }
}

@Component({
  selector: 'user-profile',
  template: `
    <custom-expando>
      <custom-toggle>Show</custom-toggle>
    </custom-expando>
  `,
})
export class UserProfile {}

В этом примере компонент CustomExpando выполняет запрос для поиска дочернего элемента CustomToggle и обращается к результату в ngAfterContentInit.

Angular поддерживает результат @ContentChild в актуальном состоянии по мере изменения состояния приложения.

Результаты запросов содержимого становятся доступны в методе жизненного цикла ngAfterContentInit. До этого момента значение равно undefined. Подробную информацию о жизненном цикле компонента см. в разделе Жизненный цикл.

Для поиска нескольких результатов также можно использовать декоратор @ContentChildren.

@Component({
  selector: 'custom-menu-item',
  /*...*/
})
export class CustomMenuItem {
  text: string;
}

@Component({
  selector: 'custom-menu',
  /*...*/
})
export class CustomMenu implements AfterContentInit {
  @ContentChildren(CustomMenuItem) items: QueryList<CustomMenuItem>;

  ngAfterContentInit() {
    this.items.forEach((item) => {
      console.log(item.text);
    });
  }
}

@Component({
  selector: 'user-profile',
  template: `
    <custom-menu>
      <custom-menu-item>Cheese</custom-menu-item>
      <custom-menu-item>Tomato</custom-menu-item>
    </custom-menu>
  `,
})
export class UserProfile {}

@ContentChildren создаёт объект QueryList, содержащий результаты запроса. Подписаться на изменения результатов запроса со временем можно с помощью свойства changes.

Параметры запросов на основе декораторов

Все декораторы запросов принимают объект параметров вторым аргументом. Эти параметры работают так же, как параметры запросов на основе сигналов, за исключением описанных ниже случаев.

Статические запросы

Декораторы @ViewChild и @ContentChild принимают параметр static.

@Component({
  selector: 'custom-card',
  template: '<custom-card-header>Visit sunny California!</custom-card-header>',
})
export class CustomCard implements OnInit {
  @ViewChild(CustomCardHeader, {static: true}) header: CustomCardHeader;

  ngOnInit() {
    console.log(this.header.text);
  }
}

Задав static: true, вы гарантируете Angular, что целевой элемент этого запроса всегда присутствует и не отображается условно. Благодаря этому результат становится доступен раньше — в методе жизненного цикла ngOnInit.

Результаты статических запросов не обновляются после инициализации.

Параметр static недоступен для запросов @ViewChildren и @ContentChildren.

Использование QueryList

@ViewChildren и @ContentChildren предоставляют объект QueryList, содержащий список результатов.

QueryList предоставляет ряд удобных API для работы с результатами как с массивом, например map, reduce и forEach. Получить массив текущих результатов можно, вызвав toArray.

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

Типичные ошибки при работе с запросами

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

Всегда используйте единый источник истины для состояния, общего для нескольких компонентов. Это позволяет избежать ситуаций, когда повторяющееся состояние в разных компонентах рассинхронизируется.

Не записывайте состояние напрямую в дочерние компоненты. Такой подход может привести к хрупкому, сложному для понимания коду и ошибкам ExpressionChangedAfterItHasBeenChecked.

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

Super-powered by Google ©2010–2025.
Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0.
https://angular.dev/guide/components/queries

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API