Angular est-il vraiment plus compliqué que React ?

Quand on débute avec Angular, il est facile de se sentir découragé face à la multitude de concepts à assimiler. Cette complexité peut inciter à se tourner vers des frameworks comme React, qui semblent plus simples à première vue. Mais est-ce vraiment le cas ?

Abonnez-vous à notre chaîne

Pour profiter des prochaines vidéos sur Angular, abonnez-vous à la nouvelle chaîne YouTube !

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: '[email protected]' },
    { id: 2, name: 'Jane Smith', username: 'jane', email: '[email protected]' },
    { id: 3, name: 'Bob Wilson', username: 'bob', email: '[email protected]' }
  ];
}
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.

Chaque mois, recevez en avant-première notre newsletter avec les dernières actualités, tutoriels, astuces et ressources Angular directement par email !