Angular 22 : OnPush par défaut, le guide pratique (Front)

Angular 22 : OnPush par défaut, le guide pratique (Front)

19 ago
|
angularforall
|
Front

19 ago

angularforall

Front

Angular Angular-22 Onpush Change-Detection Changedetectionstrategy Eager Markforcheck Signals Performance Zoneless Migration Typescript Front-End Detectchanges

Maitrisez OnPush devenu strategie par defaut en Angular 22 : declencheurs de detection, echappatoire Eager, pieges de migration, markForCheck et Signals avec exemples.

Sommaire

- Le grand basculement d'Angular 22
- Rappel : Default vs OnPush
- Ce qui change concrètement en v22
- Ce qui rend une vue « sale » en OnPush
- L'échappatoire : ChangeDetectionStrategy.Eager
- Les pièges courants après migration
- markForCheck() et Signals : qui fait quoi
- Migrer son application étape par étape
- Tester un composant OnPush
- Performance : mesurer le gain réel
- Conclusion

Le grand basculement d'Angular 22 Pendant des années, créer un composant Angular performant impliquait un geste rituel : ajouter changeDetection: ChangeDetectionStrategy.OnPush dans chaque @Component. Oublier ce réglage condamnait l'application à vérifier l'intégralité de l'arbre de composants à chaque clic, chaque setTimeout, chaque requête HTTP. Angular 22 met fin à ce rituel : OnPush devient la stratégie de détection de changement par défaut .

Ce n'est pas un détail cosmétique. C'est l'un des changements les plus structurants de la version, dans la continuité des Signals (Angular 16+), de la détection sans zone ( zoneless , Angular 18+) et de la stabilisation du graphe réactif. Le message d'Angular est clair : la réactivité fine pilotée par Signals remplace la détection globale héritée de Zone.js. Faire de OnPush le défaut, c'est aligner le comportement par défaut du framework sur ses propres recommandations de performance.

Mais un changement de défaut est aussi un breaking change . Des composants qui « marchaient » uniquement parce qu'Angular vérifiait tout en permanence peuvent cesser de se rafraîchir. Cet article explique le mécanisme, ce qui change précisément, comment repérer et réparer les composants cassés, et comment utiliser l'échappatoire Eager pendant la transition.

En une phrase : en Angular 22, un composant ne déclenche le rendu que lorsqu'un Signal lu dans son template change, qu'un de ses @Input() reçoit une nouvelle référence, qu'un événement DOM se produit dans son template, ou qu'un async pipe émet — exactement comme OnPush le faisait, mais désormais sans rien écrire.

Rappel : Default vs OnPush

Pour comprendre l'impact du nouveau défaut, il faut distinguer les deux stratégies historiques. Avec la stratégie Default (aussi appelée CheckAlways ), Angular re-vérifie un composant à chaque cycle de détection global , déclenché par n'importe quel événement intercepté par Zone.js : clic, timer, promesse résolue, requête XHR. Avec OnPush, Angular ne vérifie le composant que si une condition de « salissure » ( dirty ) précise est remplie.

Tableau comparatif des deux stratégies

Critère Default (CheckAlways) OnPush Fréquence de vérification À chaque cycle global Seulement si la vue est « sale » Coût CPU sur grand arbre Élevé (O(n) à chaque tick) Faible (sous-arbres ignorés) Mutation d'objet sans nouvelle référence Détectée Ignorée (piège classique) Signals lus dans le template Détectés Détectés (marquage auto) Statut en Angular ≤ 21 Défaut Opt-in manuel Statut en Angular 22 Eager (opt-in) Défaut Le même composant, deux comportements

// counter.component.ts — comportement identique en v22 par défaut // qu'avec un OnPush explicite en v21 import { Component, signal } from '@angular/core'; @Component({ selector: 'app-counter', standalone: true, // En Angular 21 il fallait écrire ceci pour être performant : // changeDetection: ChangeDetectionStrategy.OnPush, // En Angular 22, c'est déjà le comportement par défaut. template: `Compteur : {{ count() }}`, }) export class CounterComponent { // Signal : sa lecture dans le template marque la vue automatiquement count = signal(0); increment(): void { // .update() change la valeur du Signal -> la vue est marquée sale this.count.update(n => n + 1); } }

Un composant écrit « proprement » avec des Signals se comporte de façon strictement identique en Default ou OnPush. C'est précisément pour cela qu'Angular peut basculer le défaut sans casser les applications déjà modernisées : si vous utilisez Signals et async pipe, vous ne verrez aucune différence — seulement un gain de performance.

Ce qui change concrètement en v22

Concrètement, tout composant déclaré sans propriété changeDetection est désormais traité comme OnPush. Vous n'avez plus rien à écrire pour bénéficier de la stratégie performante. À l'inverse, les composants qui dépendaient implicitement de CheckAlways doivent être identifiés.

Avant / Après — la déclaration de composant

// ❌ AVANT (Angular ≤ 21) — OnPush devait être explicite import { Component, ChangeDetectionStrategy } from '@angular/core'; @Component({ selector: 'app-product-list', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, // boilerplate répété partout template: '...', }) export class ProductListComponent {} // ✅ APRÈS (Angular 22) — OnPush implicite, zéro boilerplate import { Component } from '@angular/core'; @Component({ selector: 'app-product-list', standalone: true, // Plus besoin de changeDetection : OnPush est le défaut template: '...', }) export class ProductListComponent {}

Les trois catégories de composants

Au moment de migrer, classez mentalement vos composants en trois familles. Cette grille de lecture accélère énormément l'audit.

Famille Caractéristique Action en v22 Déjà OnPush changeDetection: OnPush déjà présent Rien à faire — comportement inchangé Moderne implicite Signals + async pipe, pas de mutation Rien à faire — devient OnPush sans effet visible Legacy fragile Mutations, timers, callbacks impératifs Réparer (Signals/markForCheck) ou Eager

Bonne nouvelle : dans une application Angular maintenue depuis la v17, la grande majorité des composants tombe dans les deux premières familles. Le travail de migration se concentre sur une minorité de composants legacy fragiles — souvent ceux qui intègrent une librairie JavaScript tierce.

Ce qui rend une vue « sale » en OnPush En OnPush, Angular ne re-rend un composant que si l'une de ces conditions est remplie. Connaître cette liste par cœur évite 90 % des bugs de « vue qui ne se rafraîchit pas ».

Les Déclencheurs De Détection En OnPush

- Un Signal lu dans le template change de valeur
- Un @Input() reçoit une nouvelle référence (pas une mutation)
- Un événement DOM se déclenche dans le template du composant ((click), (input)…)

- Un async pipe émet une nouvelle valeur
- On appelle explicitement ChangeDetectorRef.markForCheck()

Le piège n°1 : la mutation d'objet // user-card.component.ts import { Component, input } from '@angular/core'; @Component({ selector: 'app-user-card', standalone: true, template: `

{{ user().name }} — {{ user().role }}

`, }) export class UserCardComponent { user = input.required<{ name: string; role: string }>(); } // parent.component.ts export class ParentComponent { user = { name: 'Said', role: 'dev' }; promote(): void { // ❌ MUTATION : même référence d'objet -> OnPush ignore le changement // La carte enfant n'affichera JAMAIS le nouveau rôle this.user.role = 'lead'; } promoteCorrect(): void { // ✅ NOUVELLE RÉFÉRENCE : OnPush détecte le changement de @Input this.user = { ...this.user, role: 'lead' }; } }

Cette règle d'immutabilité existait déjà pour les composants OnPush en v21. Ce qui change en v22, c'est qu'elle s'applique désormais par défaut à tous vos composants . Si votre code mute des objets passés en @Input(), c'est le moment de passer à l'immutabilité — ou aux Signals, qui rendent le problème caduc.

La solution durable : passer l'état en Signal

// parent.component.ts — version Signals (recommandée en v22) import { Component, signal } from '@angular/core'; export class ParentComponent { // Le state vit dans un Signal writable user = signal({ name: 'Said', role: 'dev' }); promote(): void { // .update() produit une nouvelle référence ET notifie le graphe réactif this.user.update(u => ({ ...u, role: 'lead' })); } }

L'échappatoire : ChangeDetectionStrategy.Eager

Angular 22 introduit une nouvelle valeur explicite, ChangeDetectionStrategy.Eager, qui restaure le comportement historique CheckAlways . C'est l'inverse de la démarche d'avant :



hier on activait OnPush pour être performant, aujourd'hui on active Eager pour revenir en arrière de manière ciblée.

// legacy-widget.component.ts // Composant qui intègre une vieille librairie jQuery mutant le DOM import { Component, ChangeDetectionStrategy } from '@angular/core'; @Component({ selector: 'app-legacy-widget', standalone: true, // Filet de sécurité temporaire : on garde la vérification systématique changeDetection: ChangeDetectionStrategy.Eager, template: '...', }) export class LegacyWidgetComponent {}

Eager est une béquille, pas une destination. Il permet de migrer une grosse application sans tout réécrire le jour J. Mais chaque composant Eager annule le gain de performance d'OnPush et conserve la dette technique. Traitez-les comme une liste TODO : convertissez-les progressivement vers Signals, puis retirez le Eager.

Stratégie de migration progressive

Plan De Bascule Sécurisé

- Mettre à jour vers Angular 22 et lancer la suite de tests E2E
- Marquer en Eager tout composant qui régresse visuellement
- Créer un ticket par composant Eager (dette à résorber)
- Convertir l'état mutable vers des Signals, sprint après sprint
- Retirer le Eager et valider que la vue reste correcte

Les pièges courants après migration Trois familles de bugs reviennent systématiquement après le passage en v22. Les reconnaître permet de les corriger en quelques minutes.

Piège 1 — État modifié dans un timer

// clock.component.ts import { Component, signal, OnInit } from '@angular/core'; @Component({ selector: 'app-clock', standalone: true, template: `{{ time() }}`, }) export class ClockComponent implements OnInit { time = signal(new Date().toLocaleTimeString()); ngOnInit(): void { // ✅ setInterval écrit dans un Signal -> la vue se met à jour // (même en zoneless, car c'est le Signal qui marque la vue) setInterval(() => { this.time.set(new Date().toLocaleTimeString()); }, 1000); } } // ❌ Version cassée en v22 : propriété simple au lieu d'un Signal export class BrokenClockComponent implements OnInit { time = new Date().toLocaleTimeString(); // propriété brute ngOnInit(): void { setInterval(() => { // La propriété change mais rien ne marque la vue : l'horloge fige this.time = new Date().toLocaleTimeString(); }, 1000); } }

Piège 2 — Callback d'une librairie tierce

// map.component.ts — intégration d'une carte (Leaflet, Mapbox…) import { Component, signal, inject, ChangeDetectorRef } from '@angular/core'; @Component({ selector: 'app-map', standalone: true, template: `

Zoom : {{ zoom() }}

`, }) export class MapComponent { zoom = signal(10); private cdr = inject(ChangeDetectorRef); initMap(mapLib: any): void { mapLib.on('zoomend', (e: { level: number }) => { // Le callback vient d'un code hors Angular. // En écrivant dans un Signal, la vue est marquée automatiquement : this.zoom.set(e.level); // Si l'on devait modifier une propriété brute, il faudrait : // this.cdr.markForCheck(); }); } }

Piège 3 — Référence d'@Input non renouvelée

// ❌ Le parent pousse dans le même tableau export class ListParentComponent { items: string[] = ['a', 'b']; add(): void { this.items.push('c'); // mutation : l'enfant OnPush ne voit rien } addCorrect(): void { this.items = [...this.items, 'c']; // nouvelle référence : OK } }

Point commun des trois pièges : un changement d'état hors du graphe réactif . La parade universelle en v22 est de faire vivre l'état dans des Signals. Le markForCheck() reste le plan B pour le code que vous ne pouvez pas signaliser immédiatement.

markForCheck() et Signals : qui fait quoi

Avec OnPush par défaut, deux mécanismes coexistent pour notifier Angular qu'une vue doit être re-rendue. Comprendre lequel utiliser évite à la fois les bugs et la sur-ingénierie.

Signals — le mécanisme par défaut

// notifications.component.ts import { Component, signal, computed } from '@angular/core'; @Component({ selector: 'app-notifications', standalone: true, template: ` {{ unreadCount() }} @for (n of notifications(); track n.id) {

{{ n.text }}

} `, }) export class NotificationsComponent { notifications = signal<{ id: number; text: string; read: boolean }[]>([]); // computed() est lu dans le template : il marque la vue automatiquement unreadCount = computed(() => this.notifications().filter(n => !n.read).length); push(text: string): void { // Nouvelle référence + notification du graphe : rendu garanti this.notifications.update(list => [ ...list, { id: Date.now(), text, read: false }, ]); } }

markForCheck() — pour le code impératif résiduel

// price-ticker.component.ts — flux WebSocket brut, sans Signal import { Component, inject, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core'; @Component({ selector: 'app-price-ticker', standalone: true, template: `{{ price }} €`, }) export class PriceTickerComponent implements OnInit, OnDestroy { price = 0; // propriété brute (legacy) private socket?: WebSocket; private cdr = inject(ChangeDetectorRef); ngOnInit(): void { this.socket = new WebSocket('wss://api.example.com/prices'); this.socket.onmessage = (event) => { this.price = JSON.parse(event.data).value; // Changement hors graphe réactif : on marque la vue manuellement this.cdr.markForCheck(); }; } ngOnDestroy(): void { this.socket?.close(); } }

detectChanges() vs markForCheck()

Méthode Effet Quand l'utiliser markForCheck() Marque le composant et ses ancêtres « à vérifier » au prochain cycle 99 % des cas en OnPush detectChanges() Force un cycle de détection immédiat et synchrone Cas rares : avant une capture/mesure DOM synchrone Signal lu dans le template Marque la vue automatiquement à chaque changement Le défaut recommandé en v22

Règle de décision : si vous pouvez exprimer l'état avec un Signal, faites-le et oubliez markForCheck(). Réservez markForCheck() au code impératif que vous ne contrôlez pas entièrement (librairies, WebSocket bruts). N'utilisez detectChanges() que si vous avez besoin du DOM mis à jour dans la même tâche synchrone .

Migrer son application étape par étape La mise à jour vers Angular 22 passe par l'outillage officiel. La commande ng update applique les schématics et signale les points d'attention.

Mettre à jour le framework et le CLI vers la v22 ng update @angular/core@22 @angular/cli@22 # Lancer le build pour repérer les erreurs de typage liées à la v22 ng build # Lancer la suite de tests pour détecter les régressions de rendu ng test

Détecter les composants à risque par recherche Avant même de lancer l'application, une recherche statique repère les patterns dangereux. Les mutations sur des entrées et l'usage de timers sont les premiers suspects.

Repérer les mutations de tableaux (push, splice) — candidats au bug OnPush grep -rn ".push(|.splice(|.pop(" src/app --include=".ts" # Repérer les composants encore en CheckAlways implicite (sans changeDetection) # puis vérifier manuellement ceux qui manipulent du DOM ou des timers grep -rLn "changeDetection" src/app --include=".component.ts" # Repérer les timers susceptibles d'écrire dans des propriétés brutes grep -rn "setInterval|setTimeout" src/app --include="*.ts"

Convertir un composant legacy — exemple complet // ❌ AVANT — dépendait de CheckAlways implicite (cassé en v22) import { Component, Input } from '@angular/core'; @Component({ selector: 'app-cart-summary', standalone: true, template: `

Articles : {{ lines.length }}

Total : {{ computeTotal() }} €

`, }) export class CartSummaryComponent { @Input() lines: { qty: number; price: number }[] = []; // Recalculé à chaque cycle global — coûteux et dépend de CheckAlways computeTotal(): number { return this.lines.reduce((s, l) => s + l.qty * l.price, 0); } } // ✅ APRÈS — Signals + computed, parfaitement OnPush-compatible import { Component, input, computed } from '@angular/core'; @Component({ selector: 'app-cart-summary', standalone: true, template: `

Articles : {{ count() }}

Total : {{ total() }} €

`, }) export class CartSummaryComponent { // input() : le @Input devient un Signal,



le parent renouvelle la référence lines = input.required(); // computed() : recalculé seulement si lines() change, et marque la vue count = computed(() => this.lines().length); total = computed(() => this.lines().reduce((s, l) => s + l.qty * l.price, 0) ); }

Remplacer un appel de méthode (computeTotal()) par un computed() est un double gain : compatibilité OnPush et performance, car le calcul est mémoïsé au lieu d'être ré-exécuté à chaque détection.

Tester un composant OnPush

Les tests unitaires sont sensibles au changement de défaut. En OnPush, modifier une propriété puis appeler fixture.detectChanges() ne suffit pas toujours : il faut que la vue ait été marquée. Avec les Signals, c'est transparent ; avec des entrées classiques, il faut renouveler la référence.

// cart-summary.component.spec.ts import { TestBed } from '@angular/core/testing'; import { CartSummaryComponent } from './cart-summary.component'; describe('CartSummaryComponent (OnPush)', () => { it('affiche le total après changement de lines', () => { const fixture = TestBed.createComponent(CartSummaryComponent); // setInput() renouvelle la référence du signal input -> vue marquée fixture.componentRef.setInput('lines', [ { qty: 2, price: 10 }, { qty: 1, price: 5 }, ]); // detectChanges() applique le rendu de la vue désormais marquée sale fixture.detectChanges(); const text = fixture.nativeElement.textContent as string; expect(text).toContain('Total : 25'); expect(text).toContain('Articles : 2'); }); });

Bonne pratique de test en v22 : utilisez systématiquement fixture.componentRef.setInput() plutôt que d'assigner directement fixture.componentInstance.lines = .... setInput() simule le binding réel d'un parent et marque correctement la vue OnPush — ce que l'assignation directe ne fait pas.

Tester un état mis à jour de façon asynchrone

// counter.component.spec.ts it('incrémente le compteur au clic', () => { const fixture = TestBed.createComponent(CounterComponent); fixture.detectChanges(); const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); button.click(); // événement DOM -> marque la vue (OnPush) fixture.detectChanges(); // applique le rendu expect(button.textContent).toContain('Compteur : 1'); });

Performance : mesurer le gain réel

Le bénéfice de OnPush par défaut se mesure au nombre de composants vérifiés par cycle de détection. Sur une application réelle (tableau de bord avec listes, graphiques et widgets), passer de Default à OnPush divise typiquement par 5 à 20 le nombre de vérifications par interaction utilisateur.

Profiler avec Angular DevTools

// Activer le profiling de détection de changement en développement // dans main.ts (à retirer en production) import { enableProfiling } from '@angular/core'; if (!environmentIsProd) { // Affiche dans l'onglet « Profiler » d'Angular DevTools // le temps passé en détection par composant enableProfiling(); }

Ordre de grandeur observé

Scénario (dashboard ~400 composants) Default (v21) OnPush défaut (v22) Composants vérifiés par clic isolé ~400 ~15 à 40 Temps de détection moyen / tick 8–14 ms 1–3 ms Saccades sur frappe clavier rapide Perceptibles Quasi nulles

Ces chiffres sont des ordres de grandeur indicatifs, pas une garantie : le gain dépend de la profondeur de l'arbre et du nombre de bindings. Mesurez toujours sur votre application avec Angular DevTools avant et après, plutôt que de vous fier à des moyennes génériques.

Combiner OnPush et zoneless pour le maximum

// app.config.ts — détection sans Zone.js, alignée avec OnPush par défaut import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core'; export const appConfig: ApplicationConfig = { providers: [ // Supprime Zone.js : la détection est pilotée 100 % par les Signals // C'est la combinaison la plus performante en Angular 22 provideZonelessChangeDetection(), ], };

Le combo gagnant en v22 : OnPush par défaut + mode zoneless + état en Signals. À ce stade, Angular ne déclenche un rendu que lorsqu'une donnée réellement affichée change. C'est le modèle de réactivité fine vers lequel le framework converge depuis la v16.

Conclusion

Faire de OnPush la stratégie par défaut est l'aboutissement logique de la trajectoire d'Angular : Signals, zoneless, graphe réactif. Pour les projets modernes — ceux qui utilisent Signals et async pipe — la migration vers Angular 22 est transparente et apporte un gain de performance immédiat, sans une ligne de code à écrire. Le boilerplate changeDetection: ChangeDetectionStrategy.OnPush répété dans chaque composant appartient désormais au passé.

Pour les bases de code legacy, le travail consiste à identifier la minorité de composants fragiles — mutations d'objets, timers, callbacks de librairies tierces — et à les convertir aux Signals. L'échappatoire ChangeDetectionStrategy.Eager offre un filet de sécurité temporaire pour migrer sans tout réécrire d'un bloc, mais elle doit rester transitoire : chaque Eager est une dette de performance à résorber.

Récapitulatif Des Actions Clés

- Mettre à jour avec ng update @angular/core@22 @angular/cli@22
- Supprimer le changeDetection: OnPush redondant des composants modernes
- Auditer les mutations d'@Input(), les timers et les callbacks tiers
- Faire vivre l'état dans des Signals — c'est la solution durable
- Garder markForCheck() pour le code impératif résiduel
- Utiliser Eager comme béquille temporaire, jamais comme cible
- Tester avec fixture.componentRef.setInput()
- Mesurer le gain avec Angular DevTools et envisager le mode zoneless

En traitant OnPush comme le nouveau socle de votre architecture plutôt que comme une contrainte, vous obtenez des applications Angular 22 plus rapides, plus prévisibles, et parfaitement alignées avec l'avenir réactif du framework. Précédent Migration Angular 21 vers 22 : guide pas à pas

Angular Angular-22 Angular-21

Suivant Angular : oubliez $any(), utilisez l'optional chaining

Angular Any Optional-Chaining

Questions fréquentes

Avec la généralisation des Signals et le mode zoneless, la détection systématique (CheckAlways) devient un coût inutile. OnPush par défaut réduit drastiquement le nombre de cycles de détection, ce qui améliore les performances de rendu sans configuration manuelle composant par composant.

Déclarez changeDetection: ChangeDetectionStrategy.Eager dans le décorateur @Component. Eager restaure le comportement CheckAlways (vérification à chaque cycle). C'est l'échappatoire officielle pour les composants legacy non compatibles OnPush.

Ceux qui mutent un objet @Input() sans changer sa référence, ceux qui modifient l'état depuis setInterval ou un callback de librairie tierce, et ceux qui s'appuient sur un side-effect hors du graphe réactif. Sans markForCheck() ou Signals, leur vue ne se rafraîchit plus.

Non pour les Signals lus dans le template : ils marquent le composant automatiquement. markForCheck() reste pratico pour les mises à jour pilotées hors graphe réactif (timers, événements WebSocket bruts, callbacks impératifs) qui n'utilisent ni Signal ni async pipe.

Publié/Développé par Mezgani Said

Développeur web full‑stack et formateur spécialisé en Angular, TypeScript et Node.js, il partage sur AngularForAll des ressources complètes pour les développeurs : tutoriels pratiques, guides pas‑à‑pas, outils gratuits, exercices, QCM techniques et retours d’expérience issus de projets réels (SaaS, micro‑frontends, architectures modulaires, applications d’entreprise).

Le site couvre l’ensemble de l’écosystème web : front‑end, intégration HTML/CSS, Composants réutilisables, SEO, back‑end, cloud, IA, CLI, productivité, bien‑être, outils web et métiers du digital.

Un espace pensé pour générer, apprendre, progresser et développer des projets modernes avec des bonnes pratiques professionnelles.

Partager

LinkedIn Facebook Twitter / X Pinterest WhatsApp TikTok Email

URL copiée dans le presse-papiers !

Voir aussi

Front

ChangeDetectionStrategy en Angular : DetectChanges et OnPush

Front

Angular 22 : toutes les nouveautés

Front

Migration Angular 21 vers 22 : guide pas à pas

Front

Angular 2026 : tendances et sujets recherchés

Front

Angular 22 : composants selectorless expliqués

Front

Angular 22 : skills IA pour coder avec des agents

Front

Angular 2026 : roadmap d'apprentissage complet

Front

Angular hostDirectives : composer ses directives

Front

Angular + Apollo GraphQL : requêtes typées

Front

Angular 18 : nouvelles fonctionnalités et améliorations

📌 Angular 22 : OnPush par défaut, le guide pratique (Front)
🏢 angularforall
📍 Front

Candidati a questo annuncio

Mostra le tue capacità professionali all'azienda, compila il form e lascia un tocco personale nella lettera di presentazione, aiuterà il recruiter nella scelta del candidato.

Iscriviti a questa job alert:

Ricevi via email le nuove offerte di lavoro per: angular 22 : onpush par défaut, le guide pratique (front) / front

Iscriviti a questa job alert:

Ricevi via email le nuove offerte di lavoro per: angular 22 : onpush par défaut, le guide pratique (front) / front