Leçon 05 · Phase 0 — Socle Control flow · ~17 min

Le nouveau control flow : @if / @for / @switch

Afficher une donnée, c'est la leçon précédente. Maintenant : afficher conditionnellement, répéter une liste, choisir une branche. Depuis Angular 17, ça se fait avec une syntaxe à base de @ intégrée au template — @if, @for, @switch — qui remplace les vieilles directives *ngIf, *ngFor, *ngSwitch. C'est la syntaxe que tu liras et écriras chez Alliance.

Pourquoi ça compte pour Alliance Un ERP, c'est 90 % de listes (postes, mesures, lots, alertes) et de conditionnel (afficher le tableau seulement si les données sont chargées, badge rouge si hors seuil, vue différente selon le statut d'un lot). Tu ne peux pas reviewer un écran métier sans lire couramment ces trois blocs — et le piège n°1 de perf en revue Angular se cache précisément dans un @for mal écrit. C'est la grammaire des écrans.

1. Pourquoi une nouvelle syntaxe ?

Historiquement, le conditionnel et les boucles passaient par des directives structurelles avec une étoile : *ngIf, *ngFor. Ça marchait, mais il fallait les importer (CommonModule, NgIf, NgFor) dans chaque composant, la syntaxe du else était lourde (ng-template + référence), et c'était une source de bugs de perf. Le nouveau control flow est intégré au compilateur : rien à importer, plus lisible, plus rapide, et il comprend mieux tes types.

Analogie C# / Razor Tu retrouves exactement tes structures de contrôle : @if = le if, @for = le foreach, @switch = le switch. Et le @ n'est pas un hasard : c'est très proche de Razor (@if (…) { }, @foreach (var x in …) { }). Si tu sais écrire une vue Razor, tu sais déjà lire ce template. La différence reste la même qu'en leçon 04 : Razor s'exécute une fois côté serveur, Angular reste vivant et réévalue dès que la donnée change.

2. @if / @else if / @else

On affiche un bloc seulement si la condition est vraie. Le @else est enfin naturel à écrire.

<!-- chargement → données → vide : un cas par branche -->
@if (chargement) {
  <p>Chargement des postes…</p>
} @else if (postes.length === 0) {
  <p>Aucun poste actif.</p>
} @else {
  <table>…</table>
}
Bonus type-safety (un vrai plus pour un lead) Si poste est de type Poste | null, écrire @if (poste) { {{ poste.nom }} } fait que, à l'intérieur du bloc, Angular sait que poste n'est plus null. C'est le même narrowing qu'un if (poste !== null) en TypeScript (leçon 02). Tu peux même nommer le résultat : @if (user(); as u) { {{ u.nom }} } — pratique avec les signals (phase 1).

3. @for — répéter une liste (avec track obligatoire)

On itère sur une collection. Nouveauté majeure : le track est obligatoire. Il dit à Angular comment identifier chaque élément pour ne re-rendre que ce qui a vraiment changé.

@for (poste of postes; track poste.id) {
  <tr>
    <td>{{ poste.nom }}</td>
    <td>{{ poste.mesure }} °C</td>
  </tr>
} @empty {
  <tr><td colspan="2">Liste vide</td></tr>
}

Le bloc @empty (optionnel) s'affiche quand la collection est vide — fini le @if (liste.length === 0) séparé. À l'intérieur du @for, tu disposes de variables contextuelles : $index, $first, $last, $even, $odd, $count.

@for (poste of postes; track poste.id) {
  <tr [class.pair]="$even">
    <td>{{ $index + 1 }}</td>
    <td>{{ poste.nom }}</td>
  </tr>
}
À quoi sert track — l'analogie clé track poste.id, c'est comme une clé primaire : un identifiant stable qui dit « cette ligne EST ce poste ». Quand la liste change (tri, ajout, filtre), Angular compare les id et ne touche que le DOM nécessaire au lieu de tout reconstruire. C'est l'équivalent moderne du trackBy de l'ancien *ngFor — sauf qu'avant c'était optionnel (et souvent oublié), maintenant c'est imposé. Choisis toujours un identifiant stable et unique (l'id métier), pas $index.

4. @switch / @case / @default

Pour choisir une branche parmi plusieurs selon une valeur — typiquement un statut.

@switch (poste.statut) {
  @case ('en-service') { <span class="ok">En service</span> }
  @case ('alerte')    { <span class="ko">⚠ Hors seuil</span> }
  @default           { <span>Arrêté</span> }
}

Contrairement au switch C#, pas de break : un seul @case correspond, point. Le @default est optionnel mais recommandé (le « sinon »).

5. Ancien vs nouveau — d'un coup d'œil

BesoinLegacy (à savoir lire)Moderne (à écrire)
Conditionnel*ngIf@if / @else
Boucle*ngFor + trackBy@for … ; track
Choix multiple[ngSwitch]@switch / @case
Import requisCommonModuleaucun (intégré)

Tu croiseras encore du *ngIf dans du code existant : sache le lire. Mais pour tout nouveau code, c'est @if/@for/@switch.

Œil de tech lead (1) Le piège n°1 en revue : track $index sur une liste qui bouge (tri, filtre, suppression). L'index n'est pas stable → Angular réassocie le mauvais élément au mauvais DOM, d'où des bugs visuels (mauvais champ édité, état qui « saute »). Exige un id métier stable. (2) Pas de logique lourde dans la condition d'un @if ni dans l'expression de @for : comme en leçon 04, c'est réévalué à chaque cycle de détection — précalcule (ou computed(), phase 1). (3) Toujours prévoir le cas vide (@empty) et le cas chargement : un écran ERP sans état vide, c'est un écran cassé en démo.

6. Vérifie ta compréhension

Quelle est la grosse nouveauté obligatoire du @for par rapport à l'ancien *ngFor ?
Une liste de postes est triable et filtrable. Quel track choisir ?
Tu veux afficher un message quand la liste est vide. Le plus idiomatique ?
À retenir Depuis Angular 17, le control flow est intégré au template : @if / @else if / @else (conditionnel, avec narrowing de type), @for (x of liste; track x.id) { } @empty { } (boucle — track obligatoire, variables $index/$first/$last/$even), @switch / @case / @default (choix multiple, sans break). Aucun import requis. Remplace *ngIf / *ngFor / [ngSwitch] (à savoir lire dans le legacy). Piège de lead : track sur un id stable, jamais $index sur une liste qui bouge.
Source primaire à lire (10 min)

Angular — Control flow (@if, @for, @switch) (officiel). Courte et complète. Pour le détail du re-rendu et du track : la section @for.

Je suis ton prof — pose-moi des questions. Tu veux qu'on construise ensemble un vrai tableau de postes Alliance (chargement → liste avec badge de statut → état vide), ou que je te montre concrètement le bug visuel que provoque un track $index mal choisi ? Demande-moi, ici, maintenant.
◀ 04 · Data binding dans le template Suivant : 06 · Signals #1 : signal() & computed() ▶