Replay

Apprendre Angular en 1h

Je me donne un objectif: vous faire découvrir et apprendre Angular en 1 heure: composant, syntaxe dans les templates, les directives, les signaux, les routeurs, les services, l'injection de dépendances, les observables et les requêtes HTTP. Le nécessaire pour faire une application Angular !.

Skip to content

Vous souhaitez recevoir de l'aide sur ce sujet ? rejoignez la communauté Angular.fr sur Discord.

Comment utiliser contentChildren() sur Angular ?

Le contentChildren() est une fonction qui permet d'accéder à plusieurs éléments enfants d'un composant. Imaginez une liste de tâches où vous devez accéder à toutes les tâches individuelles pour les manipuler : la liste (composant parent) doit pouvoir interagir avec chaque tâche (composants enfants).

Utiliser les composants avec des signaux

Depuis Angular 17.x, les signaux sont introduits en tant que réactifs comme alternative aux décorateurs @Input(), @Output(), etc.

Donc, pour utiliser input(), output(), model(), contentChild(), contentChildren(), viewChild() et viewChildren(), il est essentiel de comprendre la notion des signaux.

Lisez l'article sur les signaux pour en savoir plus.

Voir préalablement Maîtriser la projection de contenu avec ng-content dans Angular

Utiliser des sélecteurs spécifiques

Vous pouvez également utiliser contentChildren() pour sélectionner des éléments spécifiques en utilisant des références template :

ts
import { Component } from '@angular/core';
import { TaskListComponent } from './task-list.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TaskListComponent],
  template: `
    <app-task-list>
      <div #task class="high-priority">Tâche urgente</div>
      <div #task>Tâche normale</div>
      <div #task class="high-priority">Autre tâche urgente</div>
    </app-task-list>
  `
})
export class AppComponent {}
ts
import { Component, ElementRef, contentChildren, computed } from '@angular/core';

@Component({
  selector: 'app-task-list',
  standalone: true,
  template: `
    <div class="task-list">
      <h2>Mes tâches</h2>
      <ng-content />
      
      <div class="priority-tasks">
        <h3>Tâches prioritaires ({{ priorityTasksCount() }})</h3>
        @for (task of priorityTasks(); track task) {
          <div>{{ task.nativeElement.textContent }}</div>
        }
      </div>
    </div>
  `
})
export class TaskListComponent {
  tasks = contentChildren<ElementRef>('task');
  
  priorityTasks = computed(() => 
    this.tasks().filter(task => 
      task.nativeElement.classList.contains('high-priority')
    )
  );
  
  priorityTasksCount = computed(() => this.priorityTasks().length);
}

Ce code met en place un système de gestion et de filtrage des tâches en utilisant les signaux d'Angular. Il commence par collecter toutes les tâches marquées avec la référence #task, puis crée deux signaux dérivés : un pour filtrer les tâches prioritaires (celles avec la classe 'high-priority') et un autre pour compter ces tâches prioritaires.

L'intérêt principal est la réactivité : chaque fois que la liste des tâches change ou qu'une tâche devient prioritaire/non-prioritaire, les compteurs se mettent à jour automatiquement grâce aux signaux computed().

Récupérer componsants enfants projetés

Imaginons que vous ayez une liste d'utilisateurs où vous souhaitez accéder à tous les éléments projetés. C'est comme si vous aviez une boîte contenant plusieurs cartes de visite et que vous vouliez pouvoir les consulter toutes.

Créons un exemple simple avec une liste d'utilisateurs qui projette plusieurs cartes :

ts
import { Component } from '@angular/core';
import { UserListComponent } from './user-list.component';
import { UserCardComponent } from './user-card.component';
import { User } from './user';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [UserListComponent, UserCardComponent],
  template: `
    <app-user-list>
      @for (user of users; track user) {
        <app-user-card [user]="user" />
      }
    </app-user-list>
  `
})
export class AppComponent {
  users: User[] = [
    { id: 1, name: 'John Doe', username: 'john', email: 'john@example.com' },
    { id: 2, name: 'Jane Smith', username: 'jane', email: 'jane@example.com' },
    { id: 3, name: 'Bob Wilson', username: 'bob', email: 'bob@example.com' }
  ];
}
ts
import { Component, computed, contentChildren } from '@angular/core';
import { UserCardComponent } from './user-card.component';

@Component({
  selector: 'app-user-list',
  standalone: true,
  template: `
    <div class="user-list">
      <h2>Liste des utilisateurs ({{ userCount() }})</h2>
      <ng-content />
      
      <div class="summary">
        @if (activeUsers().length > 0) {
          <p>Utilisateurs actifs : {{ activeUsers().length }}</p>
        }
      </div>
    </div>
  `
})
export class UserListComponent {
  userCards = contentChildren(UserCardComponent);
  
  userCount = computed(() => this.userCards().length);
  activeUsers = computed(() => 
    this.userCards().filter(card => card.isActive)
  );
}
ts
import { Component, input } from '@angular/core';

@Component({
  selector: 'app-user-card',
  standalone: true,
  template: `
    <div class="card">
      <h3>{{ user.name }}</h3>
      <p>{{ user.email }}</p>
      <button (click)="toggleActive()">
        {{ isActive ? 'Actif' : 'Inactif' }}
      </button>
    </div>
  `
})
export class UserCardComponent {
  user = input.required<User>({} as User)
  isActive = false;

  toggleActive() {
    this.isActive = !this.isActive;
  }
}
ts
export interface User {
    id: number;
    name: string;
    username?: string;
    email: string;
    address?: {
        street: string;
        suite: string;
        city: string;
        zipcode: string;
        geo: {
            lat: string;
            lng: string;
        }
    };
    phone?: string;
    website?: string;
    company?: {
        name: string;
        catchPhrase: string;
        bs: string;
    };
}

L'architecture se compose de trois composants qui travaillent ensemble :

  1. AppComponent (Le conteneur principal)

    • Détient la source de données (liste des utilisateurs)
    • Projette dynamiquement des cartes utilisateur dans la liste
    • Utilise @for pour créer une carte pour chaque utilisateur
  2. UserListComponent (Le gestionnaire de projection)

    • Agit comme un conteneur qui reçoit les cartes projetées
    • Utilise contentChildren() pour accéder à toutes les cartes
    • Maintient des statistiques sur les cartes (total, actifs)
    • Utilise ng-content pour afficher les cartes à l'endroit souhaité
  3. UserCardComponent (L'élément projeté)

    • Représente une carte individuelle
    • Reçoit les données d'un utilisateur via input
    • Gère son propre état (actif/inactif)

Astuce

contentChildren() retourne un signal contenant un tableau des éléments enfants, ce qui permet de réagir automatiquement aux changements dans la liste des enfants.

Utiliser le décorateur @ContentChildren

Avant Angular 17

Le décorateur @ContentChildren est la façon traditionnelle de définir des requêtes de vue multiples dans Angular avant la version 17.

Voici comment utiliser le décorateur @ContentChildren pour le même exemple :

ts
import { Component, ContentChildren, QueryList, AfterContentInit } from '@angular/core';
import { UserCardComponent } from './user-card.component';

@Component({
  selector: 'app-user-list',
  template: `
    <div class="user-list">
      <ng-content></ng-content>
    </div>
  `
})
export class UserListComponent implements AfterContentInit {
  @ContentChildren(UserCardComponent) userCards!: QueryList<UserCardComponent>;

  ngAfterContentInit() {
    const array = this.userCards.toArray();
    console.log(array);
  }
}

QueryList

QueryList est une classe qui maintient une liste des éléments qui correspondent à une requête de contenu. Elle émet des événements lorsque la liste change.