Control Flow Angular : le futur de *ngIf, *ngFor et *ngSwitch
Par Etienne Perianayagassamy - IT Consultant | Front-end Training Manager
Si vous développez avec Angular, vous avez forcément passé des années à importer le CommonModule juste pour pouvoir afficher un élément sous condition ou boucler sur un tableau. Les directives *ngIf, *ngFor et *ngSwitch ont fait les beaux jours du framework, mais elles traînaient avec elles une syntaxe parfois lourde et des contraintes techniques historiques.
Depuis quelques versions, Angular a opéré une véritable révolution interne. Parmi les nouveautés majeures, le Control Flow natif redéfinit complètement la manière dont on gère la logique d'affichage dans les templates.
Voici ce que cette syntaxe change concrètement, et comment l'adopter dès aujourd'hui.
Un peu de contexte : depuis quelle version ?
Avant d'entrer dans le vif du sujet, situons les choses :
- Angular v17 (8 novembre 2023) : le Control Flow est introduit en developer preview. Dans les premières versions de la preview, il devait être explicitement activé.
- Angular v18 (23 mai 2024) : il devient stable. Vous pouvez l'utiliser sereinement en production.
- Angular v20 (28 mai 2025) : marque officiellement la dépréciation des directives historiques
*ngIf,*ngForet*ngSwitchau profit du Control Flow. Elles continuent néanmoins de fonctionner, et leur suppression n'est pas prévue à court terme.
Autrement dit, ce n'est plus seulement une syntaxe « plus jolie » : c'est désormais la voie recommandée pour tout nouveau développement.
Pourquoi une nouvelle syntaxe ?
L'ancienne approche basée sur les directives structurelles (*ngIf, etc.) présentait plusieurs inconvénients :
- Une micro-syntaxe complexe : La micro-syntaxe de
*ngFor(aveclet item of items; trackBy: trackByFn) n'était pas du JavaScript standard et s'avérait verbeuse. - Imports obligatoires : Même en composants Standalone, il fallait penser à importer
CommonModuleou individuellementNgIf/NgFor. - Le calvaire du bloc « Else » : Pour gérer un simple
else, il fallait passer par une balise<ng-template>externe avec une référence locale, physiquement séparée du bloc conditionnel — souvent reléguée en bas du fichier. - Performance et typage : Avec
*ngFor, le suivi des éléments reposait sur une fonctiontrackBy: une méthode à déclarer dans le composant et à référencer explicitement dans la directive (trackBy: trackByFn), chargée d'indiquer à Angular quelle propriété identifiait chaque élément de la liste. Cette option était purement optionnelle, et donc souvent oubliée — alors qu'elle est déterminante sur les grandes listes pour éviter des re-rendus inutiles du DOM. À noter également qu'une limitation historique de l'implémentation de*ngFora été corrigée dans le nouveau moteur utilisé par@for.
Le nouveau Control Flow résout tous ces problèmes grâce à une syntaxe intégrée au compilateur, inspirée des langages de programmation modernes.
À garder en tête : même si la syntaxe ressemble fortement à du JavaScript ou du TypeScript, ces blocs restent des constructions propres au compilateur Angular. Ils ne sont pas exécutés comme du JavaScript classique dans le navigateur : Angular les traduit en instructions de rendu optimisées à la compilation.
Côté performances, le bloc @for embarque un nouvel algorithme de réconciliation. Contrairement à *ngFor, qui s'appuyait sur IterableDiffers, le nouveau moteur utilise un algorithme spécialisé permettant de réduire les opérations DOM lors des ajouts, suppressions et réordonnancements d'éléments.
1. Le conditionnel : @if / @else
Parlons du problème qui m'a le plus irrité au fil des années : le bloc else avec *ngIf.
En code review, lire un template HTML avec *ngIf ressemblait à un jeu de piste. On parcourt le fichier de haut en bas, on tombe sur *ngIf="isLoggedIn; else loginTemplate" — et là, pause forcée. Il faut faire défiler tout le fichier jusqu'en bas pour retrouver la balise <ng-template #loginTemplate>, comprendre son contenu, puis remonter là où on était. La lecture linéaire est complètement brisée. Un simple conditionnel transformait la relecture du template en aller-retour permanent.
Avec @if, c'est terminé. Le @else est posé immédiatement après le bloc @if, là où l'œil s'y attend naturellement. On lit enfin le HTML comme un livre : du haut vers le bas, sans jamais avoir besoin de revenir en arrière.
Avant (avec *ngIf)
<div *ngIf="isLoggedIn; else loginTemplate">
<p>Bienvenue dans votre tableau de bord !</p>
</div>
<ng-template #loginTemplate>
<button (click)="login()">Se connecter</button>
</ng-template>
Maintenant (avec @if)
@if (isLoggedIn) {
<div>
<p>Bienvenue dans votre tableau de bord !</p>
</div>
} @else {
<button (click)="login()">Se connecter</button>
}
Le petit plus : vous pouvez enchaîner les @else if (condition) de manière totalement native, ce qui était un calvaire auparavant.
@if (user.role === 'admin') {
<app-admin-panel />
} @else if (user.role === 'manager') {
<app-manager-panel />
} @else {
<app-user-panel />
}
2. Les boucles : @for et la puissance du @empty
La boucle @for apporte deux améliorations majeures : l'exigence d'une clé de suivi (track) pour de meilleures performances, et l'intégration native d'un bloc de repli optionnel rendu si la liste est vide (@empty).
Avant (avec *ngFor)
<ul *ngIf="products.length > 0; else noProducts">
<li *ngFor="let product of products; trackBy: trackById">
{{ product.name }}
</li>
</ul>
<ng-template #noProducts>
<p>Aucun produit disponible.</p>
</ng-template>
Maintenant (avec @for)
<ul>
@for (product of products; track product.id) {
<li>{{ product.name }}</li>
} @empty {
<li>Aucun produit disponible.</li>
}
</ul>
Bien choisir son track
Le compilateur Angular exige désormais l'utilisation d'une expression track dans les boucles @for. Le rôle de cette clé est d'identifier chaque élément de manière unique, afin qu'Angular sache lesquels ont été ajoutés, supprimés ou déplacés. Encore faut-il choisir la bonne clé : pour une liste dynamique, utilisez une propriété unique et stable de l'élément (track product.id).
Attention : un track mal choisi (par exemple track $index sur une liste réordonnée) peut au contraire dégrader les performances et perturber l'état du DOM.
Si vous oubliez le track, pas d'inquiétude : la compilation échoue avec une erreur explicite (NG5002: @for loop must have a "track" expression).
Peut-on utiliser track $index partout ?
Non. Réservez track $index aux cas où toutes ces conditions sont réunies :
- la liste est statique ;
- aucun élément n'est ajouté ou supprimé ;
- l'ordre des éléments ne change jamais.
Dans tous les autres cas, préférez une clé métier stable comme product.id.
Subtilité de migration : contrairement àtrackBy, si une propriété utilisée dans letrackchange alors que la référence de l'objet reste la même (modification in-place),@formet à jour les bindings de la vue existante au lieu de la détruire et la recréer. Un comportement à connaître lors d'une migration.
Les variables contextuelles dans @for
Comme avec l'ancienne directive, vous avez toujours accès aux propriétés de la boucle, mais avec une syntaxe simplifiée (le let i = index de l'ancien *ngFor laisse place au let i = $index préfixé par $) :
@for (product of products; track product.id; let i = $index, first = $first) {
<p>Index {{ i }} : {{ product.name }} @if (first) { (Nouveau !) }</p>
}
Variables disponibles : $index, $count, $first, $last, $even, $odd.
3. L'aiguillage : @switch
Le @switch natif élimine le besoin de poser des directives sur chaque sous-élément et améliore les vérifications de type réalisées par le compilateur Angular.
Avant (avec *ngSwitch)
<div [ngSwitch]="userRole">
<app-admin-panel *ngSwitchCase="'admin'"></app-admin-panel>
<app-user-panel *ngSwitchCase="'user'"></app-user-panel>
<app-guest-panel *ngSwitchDefault></app-guest-panel>
</div>
Maintenant (avec @switch)
@switch (userRole) {
@case ('admin') {
<app-admin-panel />
}
@case ('user') {
<app-user-panel />
}
@default {
<app-guest-panel />
}
}
4. Les bénéfices immédiats pour vos projets
- Moins d'imports : Le Control Flow fait partie de la syntaxe native du template Angular. Plus besoin d'importer
NgIf/NgFor/NgSwitch(ni leCommonModulejuste pour eux) dans vos composants Standalone. À noter : si vous utilisez encore des pipes (async,date...) ou des directives comme[ngClass]/[ngStyle], vous devrez toujours les importer, car ils ne font pas partie du Control Flow. - Performances accrues : L'expression
trackexigée dans le@for, combinée au nouvel algorithme de réconciliation d'Angular, optimise les cycles de rendu. Le Control Flow étant intégré directement au compilateur, Angular n'a par ailleurs plus besoin d'instancier des directives structurelles pour gérer ces blocs. - Lisibilité maximale : Le code ressemble enfin à du code applicatif standard. Le nesting est plus propre et la maintenance s'en trouve facilitée.
- Meilleur support des outils : L'inférence de type est plus robuste au sein des blocs, offrant une meilleure auto-complétion et une détection des erreurs plus fine dans vos IDE.
5. Comment migrer facilement ?
Pas de panique, vous n'avez pas à réécrire tous vos templates à la main. L'équipe d'Angular a tout prévu avec la CLI.
Pour convertir automatiquement l'ensemble de votre projet vers la nouvelle syntaxe, exécutez simplement la commande suivante à la racine de votre application :
ng generate @angular/core:control-flow
Depuis Angular v20, cette migration est par ailleurs exécutée automatiquement lors d'un ng update @angular/core.
Au-delà de cette transformation spécifique, il vaut la peine de comprendre ce que ng update fait réellement. La commande ne se contente pas de bumper les numéros de version dans votre package.json : elle exécute les migration schematics définis par chaque package mis à jour.
Concrètement, Angular calcule le delta entre votre version actuelle et la version cible, puis applique dans l'ordre toutes les transformations de code correspondantes — renommages d'API, changements de syntaxe, suppressions de fonctionnalités dépréciées. Si vous montez directement de v17 à v20, les migrations intermédiaires (v18, v19) sont toutes appliquées en séquence, sans intervention manuelle.
La migration Control Flow n'est qu'un exemple parmi les nombreuses transformations que Angular livre ainsi automatiquement à chaque version majeure.
En bonus : @let
Dans la même dynamique de modernisation des templates, Angular a introduit @let, qui permet de déclarer des variables locales directement dans le template — fini les pipes as à rallonge ou les sous-@if uniquement destinés à stocker une valeur.
@let user = user$ | async;
@if (user) {
<p>{{ user.name }}</p>
}
Combiné à @if, @for et @switch, @let contribue à rendre les templates Angular plus déclaratifs et plus proches d'un véritable langage de template moderne.
Conclusion
Le Control Flow natif n'est pas un simple sucre syntaxique : il est plus lisible, plus performant, mieux typé, et il devient le standard de fait maintenant que les directives historiques sont dépréciées. Avec une migration automatisée par la CLI, il n'y a plus aucune raison de s'en priver. Si ce n'est pas déjà fait, lancez la commande de migration sur votre projet et profitez d'un template plus propre dès aujourd'hui.
Pour les nouveaux développeurs Angular, apprendre directement le Control Flow est aujourd'hui le meilleur choix. Les directives historiques restent utiles pour comprendre les bases du framework et maintenir les projets existants, mais tous les nouveaux développements devraient privilégier la syntaxe native.