Syntaxe de flux de contrôle Angular en 2026 : @if, @for, @switch et questions d'entretien

Guide complet sur la syntaxe de flux de contrôle Angular avec @if, @for et @switch. Apprenez à remplacer les directives structurelles par les blocs de contrôle modernes et préparez-vous aux entretiens techniques.

Syntaxe de flux de contrôle Angular avec @if, @for et @switch

La syntaxe de flux de contrôle Angular (@if, @for, @switch) remplace les directives structurelles *ngIf, *ngFor et *ngSwitch qui dominaient les templates Angular depuis des années. Introduite dans Angular 17, stabilisée depuis Angular 18 et devenue la syntaxe par défaut dans Angular 22, cette approche basée sur des blocs rapproche les templates des constructions de programmation standard tout en permettant de meilleures optimisations de performance.

Migration automatique disponible

Exécutez ng generate @angular/core:control-flow pour migrer automatiquement les templates existants des directives structurelles vers la nouvelle syntaxe de flux de contrôle. Les deux syntaxes coexistent pendant la migration, mais ngIf, ngFor et ngSwitch sont dépréciées de manière douce depuis Angular 19.

Pourquoi Angular a remplacé les directives structurelles par la syntaxe de blocs

Les directives structurelles nécessitaient l'import de CommonModule, utilisaient une microsyntaxe différente de JavaScript et forçaient les développeurs à envelopper le contenu dans des ng-template pour les conditions complexes. La nouvelle syntaxe de flux de contrôle résout ces problèmes :

  • Aucun import requis : @if, @for et @switch sont intégrés au framework
  • Syntaxe proche de JavaScript : les conditions et boucles se lisent comme du code standard
  • Support natif de @else : plus besoin de références ng-template pour les branches else
  • Meilleur tree-shaking : les blocs de flux de contrôle non utilisés n'ont aucun impact sur la taille du bundle

La documentation Angular recommande d'adopter pleinement la syntaxe de blocs dans tous les nouveaux projets et de migrer progressivement les bases de code existantes.

@if : Rendu conditionnel sans ng-template

Le bloc @if rend conditionnellement du contenu basé sur une expression véridique. Contrairement à *ngIf, il supporte les branches @else if et @else directement :

user-status.component.tstypescript
import { Component, input } from '@angular/core';

@Component({
  selector: 'app-user-status',
  template: `
    @if (user().role === 'admin') {
      <app-admin-panel />
    } @else if (user().role === 'editor') {
      <app-editor-panel />
    } @else {
      <app-viewer-panel />
    }
  `
})
export class UserStatusComponent {
  user = input.required<{ role: string }>();
}

Le bloc @if supporte également l'aliasing de variables avec le mot-clé as, qui extrait les valeurs des expressions imbriquées :

html
<!-- Évite les accès répétés aux propriétés -->
@if (user().profile?.settings?.theme; as theme) {
  <p>Thème actuel : {{ theme }}</p>
}

Ce pattern est particulièrement utile lors du travail avec des données asynchrones ou des objets profondément imbriqués, puisque la variable aliasée n'est définie que lorsque la condition est véridique.

@for : Itération avec expression track obligatoire

Le bloc @for itère sur tout itérable JavaScript, avec des optimisations pour les tableaux. Contrairement à *ngFor, il nécessite une expression track :

product-list.component.tstypescript
import { Component, input } from '@angular/core';

interface Product {
  id: string;
  name: string;
  price: number;
}

@Component({
  selector: 'app-product-list',
  template: `
    @for (product of products(); track product.id) {
      <app-product-card [product]="product" />
    } @empty {
      <p>Aucun produit disponible</p>
    }
  `
})
export class ProductListComponent {
  products = input.required<Product[]>();
}

L'expression track indique à Angular comment identifier chaque élément entre les re-rendus. Le choix de la bonne propriété de tracking impacte directement les performances :

Expression TrackCas d'utilisationImpact performance
track item.idÉléments avec identifiants uniquesOptimal : mises à jour DOM minimales
track $indexListes statiques qui ne se réordonnent jamaisAcceptable : re-rendu complet lors du réordonnancement
track itemTracking par référenceMauvais : nouvelle référence = nouveau nœud DOM

Variables contextuelles dans les blocs @for

Angular fournit des variables implicites à l'intérieur des blocs @for qui exposent les métadonnées d'itération :

html
@for (item of items(); track item.id; let i = $index, isLast = $last) {
  <li class="item" [class.last]="isLast">
    {{ i + 1 }}. {{ item.name }}
  </li>
}

Variables contextuelles disponibles :

  • $index : position base zéro
  • $count : nombre total d'éléments
  • $first, $last : flags booléens pour le premier/dernier élément
  • $even, $odd : flags booléens basés sur la parité de l'index

Prêt à réussir tes entretiens Angular ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

@switch : Branchement conditionnel type-safe

Le bloc @switch fournit un pattern matching exhaustif avec comparaison d'égalité stricte (===) :

status-badge.component.tstypescript
import { Component, input } from '@angular/core';

type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered';

@Component({
  selector: 'app-status-badge',
  template: `
    @switch (status()) {
      @case ('pending') {
        <span class="badge badge-gray">En attente</span>
      }
      @case ('processing') {
        <span class="badge badge-blue">En cours</span>
      }
      @case ('shipped') {
        <span class="badge badge-yellow">Expédié</span>
      }
      @case ('delivered') {
        <span class="badge badge-green">Livré</span>
      }
    }
  `
})
export class StatusBadgeComponent {
  status = input.required<OrderStatus>();
}

Contrairement au switch JavaScript, le @switch d'Angular n'a pas de comportement fallthrough. Les conditions multiples ciblant le même bloc nécessitent des instructions @case consécutives :

html
@switch (userRole()) {
  @case ('admin')
  @case ('superadmin') {
    <app-admin-dashboard />
  }
  @case ('editor')
  @case ('reviewer') {
    <app-editor-dashboard />
  }
  @default {
    <app-viewer-dashboard />
  }
}

Vérification de type exhaustive avec @default never

Angular 22 supporte la vérification d'exhaustivité à la compilation. L'utilisation de @default never; déclare qu'aucun cas restant ne devrait exister :

typescript
type Theme = 'light' | 'dark' | 'system';

// Erreur de compilation si une valeur Theme manque dans les blocs @case
@switch (theme()) {
  @case ('light') { /* ... */ }
  @case ('dark') { /* ... */ }
  @case ('system') { /* ... */ }
  @default never;
}

L'ajout d'une nouvelle valeur à l'union Theme déclenche une erreur de compilation, forçant les développeurs à gérer tous les cas.

Intégration de la syntaxe de flux de contrôle avec les Signals Angular

Les blocs de flux de contrôle s'intègrent parfaitement avec les Signals Angular, permettant une réactivité fine :

dashboard.component.tstypescript
import { Component, computed, signal } from '@angular/core';

@Component({
  selector: 'app-dashboard',
  template: `
    @if (isLoading()) {
      <app-skeleton />
    } @else if (hasError()) {
      <app-error [message]="errorMessage()" />
    } @else {
      @for (item of filteredItems(); track item.id) {
        <app-item-card [item]="item" />
      } @empty {
        <p>Aucun élément ne correspond à vos filtres</p>
      }
    }
  `
})
export class DashboardComponent {
  items = signal<Item[]>([]);
  filter = signal('');
  isLoading = signal(true);
  hasError = signal(false);
  errorMessage = signal('');

  // Le signal computed ne recalcule que lorsque les dépendances changent
  filteredItems = computed(() =>
    this.items().filter(item =>
      item.name.toLowerCase().includes(this.filter().toLowerCase())
    )
  );
}

Lorsqu'un signal se met à jour, Angular réévalue uniquement les blocs de flux de contrôle affectés. Combiné avec la détection de changement zoneless d'Angular, cela permet un rendu hautement optimisé.

Questions d'entretien fréquentes sur le flux de contrôle Angular

Les entretiens techniques testent fréquemment la compréhension de la syntaxe de flux de contrôle, particulièrement les différences avec les directives structurelles.

Question 1 : Pourquoi track est-il obligatoire dans @for ?

L'expression track fournit à Angular une identité stable pour chaque élément. Sans elle, Angular ne peut pas déterminer efficacement quels nœuds DOM créer, mettre à jour ou détruire lorsque la collection change. Rendre track obligatoire force les développeurs à prendre une décision explicite sur l'identité, évitant les pièges de performance du tracking implicite par référence.

Question 2 : Comment @if diffère-t-il de *ngIf avec async pipe ?

Les deux peuvent gérer les observables, mais @if avec les signals fournit un accès synchrone aux valeurs :

typescript
// Avec *ngIf et async pipe (legacy)
<div *ngIf="user$ | async as user">{{ user.name }}</div>

// Avec @if et signals (moderne)
@if (user(); as user) {
  <div>{{ user.name }}</div>
}

L'approche avec signals évite la gestion des subscriptions et s'intègre mieux avec la détection de changement d'Angular.

Question 3 : @switch peut-il remplacer les chaînes complexes @if/@else if ?

@switch devrait être utilisé lors de la comparaison d'une seule expression avec plusieurs valeurs discrètes. Pour les conditions booléennes complexes impliquant différentes expressions, @if/@else if reste plus approprié :

html
<!-- Utiliser @switch pour une seule expression, plusieurs valeurs -->
@switch (status()) {
  @case ('active') { ... }
  @case ('inactive') { ... }
}

<!-- Utiliser @if pour plusieurs expressions -->
@if (isAdmin() && hasPermission('write')) {
  ...
} @else if (isEditor()) {
  ...
}

Question 4 : Que se passe-t-il avec @for lorsque la collection est vide ?

Lorsque l'itérable ne contient aucun élément, Angular ignore entièrement le contenu du @for. Le bloc optionnel @empty rend du contenu de secours dans ce cas. Sans @empty, rien n'est rendu, ce qui diffère de certains frameworks qui rendent un conteneur vide.

Migration des directives structurelles vers le flux de contrôle

La CLI Angular fournit un schématic de migration automatisé :

bash
# Migrer le projet entier
ng generate @angular/core:control-flow

# Migrer un répertoire spécifique
ng generate @angular/core:control-flow --path=src/app/features

Le schématic gère automatiquement la plupart des transformations, mais il faut vérifier la sortie pour les cas limites :

Directive structurelleÉquivalent flux de contrôle
*ngIf="condition"@if (condition) { }
*ngIf="condition; else elseBlock"@if (condition) { } @else { }
*ngFor="let item of items"@for (item of items; track item) { }
*ngFor="let item of items; index as i"@for (item of items; track item; let i = $index) { }
[ngSwitch] + *ngSwitchCase@switch + @case

Après la migration, supprimez les imports CommonModule des composants standalone qui n'utilisent plus les directives structurelles.

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Points clés sur la syntaxe de flux de contrôle Angular

  • La syntaxe de flux de contrôle Angular (@if, @for, @switch) est l'approche recommandée depuis Angular 19, remplaçant les directives structurelles
  • L'expression track dans @for est obligatoire : utilisez des identifiants uniques comme item.id pour des performances optimales, évitez track item qui cause des re-rendus basés sur les références
  • @if supporte les branches natives @else if et @else sans ng-template, et le mot-clé as crée des alias pour les valeurs véridiques pour réutilisation
  • @switch utilise l'égalité stricte et n'a pas de fallthrough : les instructions @case consécutives ciblent le même bloc, et @default never; active la vérification de type exhaustive
  • Les blocs de flux de contrôle s'intègrent avec les Signals pour une réactivité fine : seuls les blocs affectés se re-rendent lorsque les signals se mettent à jour
  • Exécutez ng generate @angular/core:control-flow pour migrer automatiquement les templates existants
Défi du jour

Tu saurais repérer le bug en Angular ?

Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Anthony Fillion-Maillet

Écrit par

Anthony Fillion-Maillet

Fondateur de SharpSkill

Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.

Mis à jour le 3 septembre 2026

Tags

#angular
#control-flow
#templates
#signals

Partager

Articles similaires