« Expression has changed after it was checked » — pourquoi cela se produit et comment vivre avec ?
\n\nSi vous avez déjà écrit une application en Angular, vous avez sûrement rencontré ce message inquiétant dans la console :
\n\nERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'.\n\nCette erreur est un véritable « fléau » pour les développeurs. Des milliers de questions sur Stack Overflow, des centaines d'heures de débogage, des dizaines de crises de nerfs. Mais la bonne nouvelle : on peut non seulement la corriger, mais aussi comprendre pourquoi elle survient. Dans cet article, nous analyserons les principales causes de l'erreur Expression has changed after it was checked, écrirons des exemples en TypeScript et donnerons des solutions concrètes. C'est parti !
\n\nQue signifie exactement cette erreur ?
\n\nAngular fonctionne en deux modes : développement (dev mode) et production. En mode développement, le framework effectue une vérification supplémentaire — après chaque cycle de détection des changements, il lance un second passage pour s'assurer que les données n'ont pas changé « à la volée ». Si la valeur change entre le premier et le second passage — Angular plante avec l'erreur ExpressionChangedAfterItHasBeenCheckedError.
\n\nEn termes simples : vous avez dit à Angular « voici les données », il les a vérifiées, puis vous les avez modifiées. Angular se fâche et crie.
\n\nTop 3 causes de l'erreur
\n\n1. Modification des données dans les hooks du cycle de vie après ngAfterViewInit
\n\nLa cause la plus fréquente — la tentative de modifier une propriété du composant dans le hook ngAfterViewInit, ngAfterContentInit ou ngAfterViewChecked. À ce moment, Angular a déjà terminé la vérification de la vue, et toute modification provoque l'erreur.
Exemple en TypeScript :
\n\nimport { Component, AfterViewInit } from '@angular/core';\n\n@Component({\n selector: 'app-example',\n template: '<p>{{ message }}</p>'\n})\nexport class ExampleComponent implements AfterViewInit {\n message = 'Valeur initiale';\n\n ngAfterViewInit(): void {\n // Erreur ! Modification de la propriété après la vérification de la vue\n this.message = 'Valeur modifiée';\n }\n}\n\nIci, Angular vérifie d'abord message (valeur « Valeur initiale »), puis appelle ngAfterViewInit, où vous le modifiez en « Valeur modifiée ». Lors de la seconde vérification, Angular constate une divergence et génère l'erreur.
2. Opérations asynchrones à l'intérieur du cycle de vie
\n\nSi vous exécutez du code asynchrone (setTimeout, Promise, Observable) à l'intérieur d'un hook, la mise à jour des données se produit en dehors de la zone de contrôle d'Angular.
\n\nExemple :
\n\nimport { Component, OnInit } from '@angular/core';\n\n@Component({\n selector: 'app-async',\n template: '<p>{{ data }}</p>'\n})\nexport class AsyncComponent implements OnInit {\n data = 'Début';\n\n ngOnInit(): void {\n setTimeout(() => {\n this.data = 'Nouvelle valeur';\n }, 0);\n }\n}\n\nBien que setTimeout avec un délai de 0 ms s'exécute presque immédiatement, Angular a déjà terminé la vérification. En mode développement, cela entraînera une erreur.
\n\n3. Composants imbriqués et transmission de données via Input
\n\nLorsque le composant parent transmet des données au composant enfant via @Input, et que le composant enfant tente de modifier ces données dans son propre cycle de vie, un conflit survient.
Exemple :
\n\n// Composant parent\n@Component({\n selector: 'app-parent',\n template: '<app-child [value]="parentValue"></app-child>'\n})\nexport class ParentComponent {\n parentValue = 'Original';\n}\n\n// Composant enfant\n@Component({\n selector: 'app-child',\n template: '<p>{{ value }}</p>'\n})\nexport class ChildComponent implements AfterViewInit {\n @Input() value: string = '';\n\n ngAfterViewInit(): void {\n this.value = 'Modifié dans le