Les blocs @if, @for et @switch sont la syntaxe de contrôle native des templates (depuis la v17, stable depuis la v18). Ils remplacent les directives structurelles *ngIf, *ngFor et ngSwitch : pas besoin de les importer, un meilleur typage, plus rapides et plus faciles à lire.
@if : conditions, else if, alias
@if (user(); as u) {
<p>Bonjour, {{ u.name }} !</p>
} @else if (loading()) {
<app-spinner />
} @else {
<a routerLink="/login">Se connecter</a>
}
<!-- `as` crée un alias typé (non nul) pour le bloc courant -->
@if (order()?.items?.length; as count) {
<span>{{ count }} articles</span>
}@for : des listes avec un track obligatoire
track est obligatoire. Il indique à Angular comment reconnaître un élément entre deux rendus, pour qu’il réutilise le DOM au lieu de le recréer. Le bloc @empty apparaît quand la liste est vide.
<ul>
@for (task of tasks(); track task.id; let i = $index, last = $last) {
<li [class.last]="last">{{ i + 1 }}. {{ task.title }}</li>
} @empty {
<li>Vous n’avez aucune tâche.</li>
}
</ul>
<!-- Variables disponibles dans @for :
$index $count $first $last $even $odd -->- 1/3 · Migrer *ngIf vers @if$first
- 2/3 · Ajouter track à chaque @for
- 3/3 · Supprimer CommonModule$last
Comment choisir l’expression de track
// ✓ id stable et unique — le bon choix dans 95 % des cas
@for (user of users(); track user.id) { ... }
// ✓ Valeurs primitives uniques (chaînes, nombres)
@for (tag of tags(); track tag) { ... }
// $index — uniquement pour les listes statiques jamais réordonnées / filtrées
@for (step of wizardSteps; track $index) { ... }
// ✗ L’objet entier (identité) quand la liste est recréée de façon immuable ou vient du serveur :
// chaque nouvelle récupération = de nouveaux objets = TOUT le DOM est détruit et recréé
@for (user of users(); track user) { ... }1. Écrivez une note à côté d’Ana. 2. Cliquez sur « Déplacer le premier à la fin ». Où la note s’est-elle retrouvée ?
Un catalogue complet avec recherche, tri et pages, suivi par id, se trouve dans Listes. Le même bug avec $index fait partie des quatre de Votre première application.
@switch, cas multiples et vérification exhaustivev21
@switch (user().role) {
@case ('admin') { <app-admin-dashboard /> }
@case ('editor') { <app-editor-dashboard /> }
@default { <app-viewer-dashboard /> }
}Depuis la v21.1, vous pouvez regrouper plusieurs blocs @case consécutifs pour un même contenu, et depuis la v21.2, @default never; transforme un cas oublié en erreur de compilation.
<!-- v21.1 : plusieurs blocs @case consécutifs pour le même contenu -->
<!-- v21.2 : @default never; → erreur de COMPILATION si vous oubliez un cas -->
@let current = status();
@switch (current) {
@case ('draft')
@case ('pending') { <span>En attente</span> }
@case ('paid') { <span>Payée</span> }
@case ('shipped')
@case ('delivered') { <span>Expédiée</span> }
@case ('cancelled') { <span>Annulée</span> }
@default never;
}Ancienne syntaxe et migration
<!-- ✗ Ancienne syntaxe (fonctionne toujours, mais elle est dépréciée du point de vue du style) -->
<div *ngIf="user$ | async as user; else loading">{{ user.name }}</div>
<ng-template #loading><app-spinner /></ng-template>
<li *ngFor="let t of tasks; trackBy: trackById; let i = index">{{ t.title }}</li>
<div [ngSwitch]="role">
<app-admin *ngSwitchCase="'admin'" />
<app-viewer *ngSwitchDefault />
</div>Lancez ng generate @angular/core:control-flow — la migration est automatique et sûre. Ensuite, vous pouvez généralement retirer CommonModule / NgIf / NgFor de imports.
Pièges fréquents
track $indexsur des listes dynamiques : l’état du DOM (inputs, focus, animations) reste à la position, pas sur l’élément.track itemsur des données immuables : chaque mise à jour crée de nouveaux objets → tout le DOM est recréé.- Appels coûteux dans les conditions :
@if (computeSomething())s’exécute à chaque Change Detection. Déplacez la logique dans uncomputed(). @switch (status())+@default never: le type ne se restreint pas sur un appel de signal ; utilisez@let s = status();et@switch (s).- Caractères spéciaux : dans un template, un
@et des{ }littéraux doivent s’écrire sous forme d’entités (@,{).
Aide-mémoire
@if (cond) { } @else if (other) { } @else { }
@if (expr; as alias) { }
@for (item of items; track item.id; let i = $index, f = $first) { } @empty { }
// $index $count $first $last $even $odd
@switch (value) {
@case (a) @case (b) { } // v21.1 : cas multiples
@default { } // ou : @default never; (v21.2, exhaustif)
}
// migration : ng generate @angular/core:control-flow