Guides approfondis
Composants

Référencer les enfants d'un composant avec des queries

Astuce : Ce guide suppose que vous avez déjà lu le guide des Fondamentaux. Lisez-le d'abord si vous découvrez Angular.

Un composant peut définir des queries qui trouvent des éléments enfants et lisent des valeurs depuis leurs injectors.

Les développeurs utilisent le plus souvent les queries pour récupérer des références vers des composants enfants, des directives, des éléments du DOM, et plus encore.

Toutes les fonctions de query renvoient des signals qui reflètent les résultats les plus récents. Vous pouvez lire le résultat en appelant la fonction du signal, y compris dans des contextes réactifs comme computed et effect.

Il existe deux catégories de queries : les view queries et les content queries.

View queries

Les view queries récupèrent des résultats parmi les éléments de la vue du composant — les éléments définis dans le propre template du composant. Vous pouvez rechercher un résultat unique avec la fonction 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);
}

Dans cet exemple, le composant CustomCard recherche un enfant CustomCardHeader et utilise le résultat dans un computed.

Si la query ne trouve aucun résultat, sa valeur est undefined. Cela peut se produire si l'élément ciblé est masqué par @if. Angular maintient le résultat de viewChild à jour à mesure que l'état de votre application change.

Vous pouvez aussi rechercher plusieurs résultats avec la fonction 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 crée un signal contenant un Array des résultats de la query.

Les queries ne traversent jamais les frontières des composants. Les view queries ne peuvent récupérer des résultats que dans le template du composant.

Content queries

Les content queries récupèrent des résultats parmi les éléments du contenu du composant — les éléments imbriqués dans le composant, dans le template où il est utilisé. Vous pouvez rechercher un résultat unique avec la fonction 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 {}

Si la query ne trouve aucun résultat, sa valeur est undefined. Cela peut se produire si l'élément ciblé est absent ou masqué par @if. Angular maintient le résultat de contentChild à jour à mesure que l'état de votre application change.

Par défaut, les queries contentChild parcourent les descendants, tandis que les queries contentChildren ne trouvent que les enfants directs. Consultez Descendants du contenu.

Vous pouvez aussi rechercher plusieurs résultats avec la fonction 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 crée un signal contenant un Array des résultats de la query.

Les queries ne traversent jamais les frontières des composants. Les content queries ne peuvent récupérer des résultats que dans le même template que le composant lui-même.

Queries obligatoires

Si une child query (viewChild ou contentChild) ne trouve aucun résultat, sa valeur est undefined. Cela peut se produire si l'élément ciblé est masqué par une instruction de control flow comme @if ou @for. C'est pourquoi les child queries renvoient un signal dont le type de valeur inclut undefined.

Dans certains cas, en particulier avec viewChild, vous savez avec certitude qu'un enfant donné est toujours disponible. Dans d'autres cas, vous pouvez vouloir imposer strictement la présence d'un enfant donné. Dans ces cas, vous pouvez utiliser une query obligatoire.

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

Si une query obligatoire ne trouve aucun résultat correspondant, Angular signale une erreur. Comme cela garantit qu'un résultat est disponible, les queries obligatoires n'incluent pas automatiquement undefined dans le type de valeur du signal.

Query locators

Le premier paramètre de chaque décorateur de query est son locator.

La plupart du temps, vous utiliserez un composant ou une directive comme locator.

Vous pouvez aussi spécifier un locator sous forme de chaîne correspondant à une variable de référence de template.

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

Si plusieurs éléments définissent la même variable de référence de template, la query récupère le premier élément correspondant.

Angular ne prend pas en charge les selectors CSS comme query locators.

Les queries et l'arbre des injectors

Astuce : Consultez Injection de dépendances pour en savoir plus sur les providers et l'arbre d'injection d'Angular.

Pour des cas plus avancés, vous pouvez utiliser n'importe quel ProviderToken comme locator. Cela vous permet de localiser des éléments en fonction des providers des composants et des directives.

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);
}

L'exemple ci-dessus utilise un InjectionToken comme locator, mais vous pouvez utiliser n'importe quel ProviderToken pour localiser des éléments spécifiques.

Options de query

Toutes les fonctions de query acceptent un objet d'options comme second paramètre. Ces options contrôlent la manière dont la query trouve ses résultats.

Lire des valeurs spécifiques depuis l'injector d'un élément

Par défaut, le query locator indique à la fois l'élément recherché et la valeur récupérée. Vous pouvez aussi spécifier l'option read pour récupérer une autre valeur depuis l'élément trouvé par le locator.

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

L'exemple ci-dessus localise un élément portant la directive ExpandoContent et récupère le TemplateRef associé à cet élément.

Les développeurs utilisent le plus souvent read pour récupérer ElementRef et TemplateRef.

Vous pouvez aussi passer Injector à read.

@Component({
  selector: 'custom-table',
  template: `
    <third-party-table #inner>
      <ng-template>
        <ng-container [ngTemplateOutlet]="columns()" [ngTemplateOutletInjector]="innerInjector()" />
      </ng-template>
    </third-party-table>
  `,
})
export class CustomTable {
  columns = contentChild(TemplateRef);
  innerInjector = viewChild('inner', {read: Injector});
}

L'exemple ci-dessus récupère le node injector de l'élément third-party-table, c'est-à-dire l'injector tel qu'il est vu depuis la position de cet élément dans l'arbre. Le transmettre à NgTemplateOutlet via ngTemplateOutletInjector permet aux directives du template projeté d'injecter des valeurs que le composant tiers fournit.

Descendants du contenu

Par défaut, les queries contentChildren ne trouvent que les enfants directs du composant et ne parcourent pas les descendants. Les queries contentChild parcourent, elles, les descendants par défaut.

@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 {}

Dans l'exemple ci-dessus, CustomExpando ne peut pas trouver <custom-toggle> avec contentChildren, car ce n'est pas un enfant direct de <custom-expando>. En définissant descendants: true, vous configurez la query pour qu'elle parcoure tous les descendants dans le même template. Les queries ne pénètrent toutefois jamais dans les composants pour parcourir des éléments d'autres templates.

Les view queries n'ont pas cette option, car elles parcourent toujours les descendants.

Queries basées sur des décorateurs

Astuce : Bien que l'équipe Angular recommande d'utiliser les fonctions de query basées sur les signals pour les nouveaux projets, les API de query d'origine basées sur des décorateurs restent entièrement prises en charge.

Vous pouvez aussi déclarer des queries en ajoutant le décorateur correspondant à une propriété. Les queries basées sur des décorateurs se comportent comme les queries basées sur les signals, sauf dans les cas décrits ci-dessous.

View queries

Vous pouvez rechercher un résultat unique avec le décorateur @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);
  }
}

Dans cet exemple, le composant CustomCard recherche un enfant CustomCardHeader et accède au résultat dans ngAfterViewInit.

Angular maintient le résultat de @ViewChild à jour à mesure que l'état de votre application change.

Les résultats des view queries deviennent disponibles dans la méthode de cycle de vie ngAfterViewInit. Avant ce moment, la valeur est undefined. Consultez la section Cycle de vie pour en savoir plus sur le cycle de vie des composants.

Vous pouvez aussi rechercher plusieurs résultats avec le décorateur @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 crée un objet QueryList qui contient les résultats de la query. Vous pouvez vous abonner aux changements des résultats de la query au fil du temps via la propriété changes.

Content queries

Vous pouvez rechercher un résultat unique avec le décorateur @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 {}

Dans cet exemple, le composant CustomExpando recherche un enfant CustomToggle et accède au résultat dans ngAfterContentInit.

Angular maintient le résultat de @ContentChild à jour à mesure que l'état de votre application change.

Les résultats des content queries deviennent disponibles dans la méthode de cycle de vie ngAfterContentInit. Avant ce moment, la valeur est undefined. Consultez la section Cycle de vie pour en savoir plus sur le cycle de vie des composants.

Vous pouvez aussi rechercher plusieurs résultats avec le décorateur @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 crée un objet QueryList qui contient les résultats de la query. Vous pouvez vous abonner aux changements des résultats de la query au fil du temps via la propriété changes.

Options des queries basées sur des décorateurs

Tous les décorateurs de query acceptent un objet d'options comme second paramètre. Ces options fonctionnent de la même manière que pour les queries basées sur les signals, sauf dans les cas décrits ci-dessous.

Queries statiques

Les décorateurs @ViewChild et @ContentChild acceptent l'option 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);
  }
}

En définissant static: true, vous garantissez à Angular que la cible de cette query est toujours présente et n'est pas rendue de façon conditionnelle. Le résultat devient ainsi disponible plus tôt, dans la méthode de cycle de vie ngOnInit.

Les résultats des queries statiques ne sont pas mis à jour après l'initialisation.

L'option static n'est pas disponible pour les queries @ViewChildren et @ContentChildren.

Utiliser QueryList

@ViewChildren et @ContentChildren fournissent tous deux un objet QueryList qui contient une liste de résultats.

QueryList propose plusieurs API pratiques pour manipuler les résultats comme un tableau, telles que map, reduce et forEach. Vous pouvez obtenir un tableau des résultats actuels en appelant toArray.

Vous pouvez vous abonner à la propriété changes pour effectuer une action chaque fois que les résultats changent.

Pièges courants avec les queries

Lorsque vous utilisez des queries, certains pièges courants peuvent rendre votre code plus difficile à comprendre et à maintenir.

Conservez toujours une source de vérité unique pour l'état partagé entre plusieurs composants. Cela évite les situations où un état dupliqué dans différents composants se désynchronise.

Évitez d'écrire directement un état dans des composants enfants. Ce pattern peut produire du code fragile, difficile à comprendre et sujet aux erreurs ExpressionChangedAfterItHasBeenChecked.

N'écrivez jamais directement un état dans des composants parents ou ancêtres. Ce pattern peut produire du code fragile, difficile à comprendre et sujet aux erreurs ExpressionChangedAfterItHasBeenChecked.