ITSkillsCenter
Blog

Angular performance : optimisation pratique 2026

12 min de lecture

Lecture : 13 minutes · Niveau : avancé · Mise à jour : avril 2026

Une application Angular peut être très rapide ou très lente selon les choix de structure et de change detection. Avec les évolutions 2024-2026 (signals, zoneless, hydration), beaucoup d’optimisations qu’on faisait manuellement deviennent automatiques. Ce guide rassemble la démarche pratique pour mesurer, identifier et corriger les problèmes de performance, en distinguant ce qui compte vraiment de ce qui est cargo cult.

Voir aussi → Angular pour entreprise : guide pratique.


Sommaire

  1. Mesurer avant d’optimiser
  2. Change detection : modes et stratégies
  3. OnPush et signals : combo gagnant
  4. Bundle size et lazy loading
  5. Optimisation des images
  6. Server-Side Rendering et hydration
  7. Listes virtualisées
  8. Web Vitals et expérience perçue
  9. Pièges courants
  10. FAQ

1. Mesurer avant d’optimiser

La règle d’or de l’optimisation : ne jamais toucher au code sans avoir mesuré. Beaucoup d’optimisations « intuitives » (OnPush partout, async pipe en boucle) ne servent à rien voire dégradent la performance.

Outils de mesure

  • Angular DevTools (extension Chrome/Firefox) : profiler de change detection, hiérarchie de composants, hooks de cycle de vie
  • Performance tab du navigateur : flame chart détaillé, identifie les long tasks
  • Lighthouse : audit complet performance/accessibilité/SEO
  • Web Vitals extension : LCP, INP, CLS en direct
  • Bundle analyzer : ng build --stats-json puis webpack-bundle-analyzer
  • Sentry, Datadog RUM : performance en production réelle

Le réflexe pratique

Avant de toucher au code, répondre :
– Qu’est-ce qui est lent exactement ? Le chargement initial ? Une interaction ? Le scroll ?
– À partir de quel seuil mesurable ?
– Quel est l’utilisateur affecté (mobile lent, desktop, bande passante limitée) ?

Optimiser sans cible précise, c’est tirer dans le brouillard.


2. Change detection : modes et stratégies

Angular en 2026 supporte trois modes de change detection.

Default (ZoneJS)

Mode historique. Zone.js intercepte tous les événements asynchrones (clic, timeout, HTTP) et déclenche une vérification de tous les composants. Simple à comprendre, mais peu efficace : chaque clic peut déclencher des vérifications de centaines de composants même non-impactés.

OnPush

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  ...
})

Le composant ne se re-vérifie que si :
– Une de ses Inputs change (référence)
– Un événement utilisateur s’y produit
– Un Observable consommé via async pipe émet
– On appelle manuellement markForCheck()

Performances grandement améliorées sur des arbres complexes.

Zoneless (Angular 18+)

provideZonelessChangeDetection()

Plus de Zone.js. Le change detection se déclenche uniquement sur les changements de signals. Performances supérieures, bundle plus petit (Zone.js retiré ~50kb), debugging simplifié.

Compatible avec OnPush et standalone components. Recommandé pour tout nouveau projet en 2026.

Recommandation

  • Nouveau projet : zoneless + signals + OnPush + standalone
  • Projet existant : migrer progressivement, OnPush en premier (composant par composant), puis signals, puis zoneless
  • Projet legacy lourd : maintenir le mode default, optimiser les hot paths avec OnPush ciblé

3. OnPush et signals : combo gagnant

@Component({
  selector: "app-cart",
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <p>Articles : {{ count() }}</p>
    <p>Total : {{ total() }} F CFA</p>
    @for (item of items(); track item.id) {
      <div>{{ item.nom }}</div>
    }
  `,
})
export class CartComponent {
  private cart = inject(CartService);
  items = this.cart.items;
  count = computed(() => this.items().length);
  total = computed(() => this.items().reduce((s, i) => s + i.prix, 0));
}

OnPush + signals = re-rendus minimaux. Quand items change, seuls les fragments du template qui dépendent de items(), count() ou total() sont actualisés. Le reste du DOM est intact.

Le piège des objets mutés

// Mauvais : mutation, OnPush ne détecte rien
const items = this.items();
items.push(nouveau);
// Pas de notification

// Bon
this.items.update(list => [...list, nouveau]);

Toujours créer une nouvelle référence. Ce principe vaut pour useState en React aussi : c’est un invariant des modèles réactifs modernes.

track dans les @for

<!-- Mauvais : utilise l'index, recrée tout au moindre changement -->
@for (item of items(); track $index) { ... }

<!-- Bon : utilise un identifiant stable -->
@for (item of items(); track item.id) { ... }

Avec un track stable, Angular ne recrée que les nœuds DOM réellement modifiés. Sur des longues listes, c’est crucial.


4. Bundle size et lazy loading

ng build --configuration production --stats-json
npx webpack-bundle-analyzer dist/mon-app/stats.json

Identifier ce qui pèse vraiment. Patterns récurrents :

  • moment.js : ~70kb. Remplacer par date-fns ou dayjs (~10kb).
  • lodash entier : ~70kb. Importer cible : import debounce from "lodash-es/debounce".
  • Angular Material non lazy : kits UI lourds. Importer composant par composant, pas le module entier.
  • Locales i18n complètes : importer uniquement les locales nécessaires.

Lazy loading

Vu dans Angular architecture modulaire : chaque feature en route lazy-loaded. Le bundle initial ne contient que la première page.

Imports modulaires Angular Material

// Mauvais
import { MatModule } from "@angular/material";

// Bon
import { MatButtonModule } from "@angular/material/button";
import { MatInputModule } from "@angular/material/input";

Avec les standalone components, importer composant par composant :

import { MatButton } from "@angular/material/button";

@Component({
  imports: [MatButton],
  ...
})

Différer le code non-critique

@defer (on viewport) {
  <app-comments [postId]="postId()" />
} @placeholder {
  <div>Charger les commentaires...</div>
}

Le bloc @defer charge le composant uniquement quand visible. Réduit drastiquement le code chargé sur le first paint.


5. Optimisation des images

<img ngSrc="hero.webp" width="1200" height="630" priority />

La directive NgOptimizedImage :
– Lazy loading automatique sauf priority
– Dimensions explicites (évite layout shift)
srcset automatique pour différentes tailles d’écran
– Préconnexion au domaine du loader configuré

Configurer un image loader pour un CDN :

provideImgixLoader("https://example.imgix.net");
// ou Cloudinary, Cloudflare Images, etc.

Pour une seule image au-dessus de la ligne de flottaison : priority accélère le LCP. Pour les autres : laisser le lazy loading faire son travail.

Voir aussi → WordPress vitesse mobile 3G optimiser pour le contexte connexions limitées.


6. Server-Side Rendering et hydration

ng add @angular/ssr

SSR rend le HTML côté serveur avant l’arrivée du JavaScript. Bénéfices :
LCP plus rapide : le contenu est visible avant l’hydratation
SEO : Google voit le contenu directement (utile aussi pour réseaux sociaux et OpenGraph)
First paint perçu plus rapide sur connexions lentes

Hydration

Angular 16+ implémente une hydration efficace : le serveur rend, le client réutilise le DOM existant et ajoute juste l’interactivité. Pas de re-rendu complet côté client.

Streaming SSR

Avec Angular 17+, le SSR peut streamer le HTML : envoyer la coque rapidement puis remplir les parties qui demandent des données plus lentes.

@defer (on idle) {
  <app-recommendations />
}

Combiné avec SSR, le premier byte arrive très rapidement, le contenu interactif suit.

Quand SSR vaut le coup

  • Sites publics avec SEO important
  • Pages où le LCP est critique business
  • Pages partagées sur réseaux sociaux (OpenGraph)

Pour des dashboards authentifiés sans contraintes SEO : SSR ajoute de la complexité sans bénéfice clair. SPA suffit.


7. Listes virtualisées

Rendre 10 000 lignes dans le DOM est lent. La virtualisation ne rend que les éléments visibles.

import { CdkVirtualScrollViewport } from "@angular/cdk/scrolling";

@Component({
  selector: "app-virtual-list",
  standalone: true,
  imports: [CdkVirtualScrollViewport, ...],
  template: `
    <cdk-virtual-scroll-viewport itemSize="50" style="height: 400px">
      @for (item of items; track item.id) {
        <div class="row">{{ item.nom }}</div>
      }
    </cdk-virtual-scroll-viewport>
  `,
})

À utiliser dès qu’une liste dépasse 100-200 items affichables. Indispensable pour des dashboards avec longues tables.

Pour des grilles ou des layouts plus complexes : @angular/cdk/scrolling propose des viewports personnalisables, ou des libs tierces comme ngx-virtual-scroller.


8. Web Vitals et expérience perçue

Les trois indicateurs de Google :

  • LCP (Largest Contentful Paint) : temps avant affichage du plus gros élément. Cible : < 2.5s
  • INP (Interaction to Next Paint) : temps de réaction aux interactions. Cible : < 200ms
  • CLS (Cumulative Layout Shift) : stabilité visuelle au chargement. Cible : < 0.1

Améliorer LCP

  • Image principale en priority avec NgOptimizedImage
  • SSR pour rendre le contenu serveur
  • Réduire le bundle initial (lazy loading des features non-critiques)
  • CDN pour les assets statiques
  • Préconnect aux domaines critiques

Améliorer INP

  • OnPush + signals limitent le travail de change detection sur interactions
  • Décomposer les long tasks (>50ms) avec setTimeout ou requestIdleCallback
  • Web Workers pour les calculs CPU intensifs
  • Mode zoneless pour éliminer le surcoût de Zone.js

Améliorer CLS

  • Dimensions explicites sur images, iframes, vidéos
  • Réserver l’espace pour le contenu chargé en différé (skeletons)
  • Éviter d’insérer du contenu au-dessus de ce que l’utilisateur regarde
  • Police web avec font-display: swap et fallback de taille similaire

9. Pièges courants

Subscriptions sans unsubscribe

// Mauvais : memory leak garanti
this.api.getData().subscribe(data => this.data = data);

// Bon : auto-unsubscribe au destroy
this.api.getData().pipe(takeUntilDestroyed()).subscribe(data => this.data = data);

// Encore mieux : signal via toSignal
data = toSignal(this.api.getData(), { initialValue: [] });

Calculs lourds dans le template

<!-- Mauvais : appelé à chaque change detection -->
<div>{{ calculateExpensiveValue(items) }}</div>

<!-- Bon : computed signal -->
<div>{{ expensiveValue() }}</div>
expensiveValue = computed(() => calculateExpensiveValue(this.items()));

Le computed mémorise et recalcule uniquement si les deps changent.

Async pipe en boucle

<!-- Mauvais : nouvelle subscription par item -->
@for (id of ids; track id) {
  <div>{{ getUser(id) | async | json }}</div>
}

<!-- Bon : combiner les requêtes en amont -->
users = toSignal(forkJoin(ids.map(id => this.api.getUser(id))));

Pas de track dans @for

Sans track, Angular recrée tous les nœuds DOM à chaque mise à jour de la liste. Sur 100 lignes c’est mineur, sur 10 000 c’est catastrophique.

Voir aussi → Angular signals et RxJS en pratique pour les patterns de subscription.


10. FAQ

Mon Angular est lent, par où commencer ?

  1. Profiler Angular DevTools sur l’action lente
  2. Identifier les composants qui se re-rendent inutilement
  3. Vérifier les track sur les @for longs
  4. Vérifier les subscriptions oubliées
  5. Mesurer le bundle initial avec bundle-analyzer

Optimisation guidée par mesure, pas par intuition.

OnPush partout ou ciblé ?

Avec signals : OnPush partout est le défaut moderne et bénéfique. Sans signals (legacy) : OnPush ciblé sur les composants qui re-rendent inutilement, identifiés via le profiler.

Zoneless est-il production-ready ?

Oui depuis Angular 18+. À combiner avec composants signal-based. Quelques bibliothèques tierces peuvent encore avoir des problèmes (rares en 2026), à tester avant migration totale.

Mon bundle initial fait 800kb, comment le réduire ?

Étapes : 1) Bundle analyzer pour voir ce qui pèse, 2) Lazy load les routes, 3) Importer les composants Material individuellement, 4) Remplacer moment.js par date-fns, 5) Tree-shake lodash, 6) Différer les composants non-critiques avec @defer.

SSR ralentit-il l’app pour les utilisateurs déjà sur le site ?

Pas si l’hydration est bien faite. Le SSR apporte le first paint plus vite, l’hydration ajoute l’interactivité ensuite. Bien implémenté, c’est mieux que SPA pure pour la plupart des cas publics.

Animations qui rament : que faire ?

Préférer les animations CSS aux animations Angular Animations pour les cas simples (transform, opacity). Pour des animations complexes : Web Animations API natif ou GSAP. Animer transform plutôt que top/left/width (qui forcent un layout).

Comment surveiller la performance en production ?

Sentry Performance, Datadog RUM, ou Google’s web-vitals lib pour envoyer les métriques à un dashboard. Identifier les régressions liées aux déploiements. Lighthouse CI pour ne pas régresser sur chaque PR.


Articles liés (cluster Angular)


Article mis à jour le 25 avril 2026. Pour signaler une erreur ou suggérer une amélioration, écrivez-nous.

Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité