478 votes

L'erreur ExpressionChangedAfterItHasBeenCheckedError expliquée

Veuillez m'expliquer pourquoi je continue à obtenir cette erreur : ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.

Évidemment, je ne l'obtiens qu'en mode dev, il ne se produit pas sur ma construction de production, mais c'est très ennuyeux et je ne comprends tout simplement pas les avantages d'avoir une erreur dans mon environnement dev qui n'apparaîtra pas sur prod --probablement en raison de mon manque de compréhension.

En général, la solution est assez simple, il suffit d'envelopper le code qui provoque l'erreur dans un setTimeout comme ceci :

setTimeout(()=> {
    this.isLoading = true;
}, 0);

Ou forcez la détection des changements avec un constructeur comme celui-ci : constructor(private cd: ChangeDetectorRef) {} :

this.isLoading = true;
this.cd.detectChanges();

Mais pourquoi est-ce que je rencontre constamment cette erreur ? Je veux le comprendre afin d'éviter ces corrections improvisées à l'avenir.

23 votes

0 votes

Je suis également confronté au même problème, qui ne se produit que sur DEV et n'affecte que les journaux de ma console. Au lieu d'introduire un nouveau code dans le projet, je cache/bloque simplement cette erreur dans la console.

2 votes

Directive officielle d'Angular - angular.io/errors/NG0100

186voto

onlyme Points 1266

J'ai eu un problème similaire. En regardant le documentation sur les crochets du cycle de vie J'ai changé ngAfterViewInit à ngAfterContentInit et ça a marché.

3 votes

@PhilipEnc mon problème était lié à un changement déclenché par le changement du DOM. Lorsque le DOM changeait, l'objet QueryList (qui provenait d'une propriété @ContentChildren) se mettait à jour et, à l'intérieur de la méthode appelée par la mise à jour, la propriété liée bidirectionnelle était modifiée. Cela a créé le problème que je rencontrais. Envelopper cette modification de la propriété à deux voies avec setTimeout comme vous le montrez ci-dessus a fait l'affaire. Merci !

1 votes

Dans mon cas, j'avais placé un code qui modifiait la valeur du tableau de la grille primeng dans ngAfterContentInit, j'ai placé le code dans ngOnInit et cela a fonctionné.

3 votes

ngAfterContentChecked fonctionne ici tandis que ngAfterContentInit L'erreur persiste.

143voto

Günter Zöchbauer Points 21340

Cette erreur indique un réel problème dans votre application, il est donc logique de lancer une exception.

Sur devMode La détection des changements ajoute un tour supplémentaire après chaque cycle régulier de détection des changements pour vérifier si le modèle a changé.

Si le modèle a changé entre le tour régulier et le tour supplémentaire de détection des changements, cela indique que soit

  • la détection du changement elle-même a provoqué un changement
  • une méthode ou un getter renvoie une valeur différente à chaque fois qu'il est appelé

qui sont toutes deux mauvaises, car on ne sait pas comment procéder puisque le modèle pourrait ne jamais se stabiliser.

Si Angular exécute la détection des changements jusqu'à ce que le modèle se stabilise, cela peut durer éternellement. Si Angular n'exécute pas la détection des changements, la vue risque de ne pas refléter l'état actuel du modèle.

Voir aussi Quelle est la différence entre le mode production et le mode développement dans Angular2 ?

7 votes

Comment puis-je éviter de voir cette erreur à l'avenir ? Y a-t-il une autre façon de penser à mon code pour m'assurer de ne pas faire les mêmes erreurs ?

30 votes

Généralement, cela est dû à des rappels de cycle de vie comme ngOnInit ou ngOnChanges pour modifier le modèle (certains callbacks du cycle de vie permettent de modifier le modèle, d'autres non, je ne me souviens pas exactement lesquels le font ou ne le font pas). Ne vous liez pas à des méthodes ou à des fonctions dans la vue, mais plutôt à des champs et mettez ces derniers à jour dans des gestionnaires d'événements. Si vous devez vous lier à des méthodes, assurez-vous qu'elles renvoient toujours la même instance de valeur tant qu'il n'y a pas eu de changement. La détection des modifications fera souvent appel à ces méthodes.

0 votes

Pour toute personne arrivant ici qui obtient cette erreur en utilisant la bibliothèque ngx-toaster, voici le rapport de bogue : github.com/scttcper/ngx-toastr/issues/160

134voto

Kevin LeStarge Points 1377

Beaucoup de compréhension est venue une fois que j'ai compris les crochets du cycle de vie d'Angular et leur relation avec la détection des changements.

J'essayais d'obtenir d'Angular qu'il mette à jour un drapeau global lié à l'adresse de l'utilisateur. *ngIf d'un élément, et j'essayais de changer ce drapeau à l'intérieur de l'élément ngOnInit() le crochet du cycle de vie d'un autre composant.

Selon la documentation, cette méthode est appelée après qu'Angular ait déjà détecté des changements :

Appelé une fois, après le premier ngOnChanges().

Ainsi, la mise à jour du drapeau dans ngOnChanges() ne déclenchera pas la détection des changements. Ensuite, une fois que la détection des modifications a été déclenchée naturellement, la valeur de l'indicateur a changé et l'erreur est levée.

Dans mon cas, j'ai changé cela :

constructor(private globalEventsService: GlobalEventsService) {

}

ngOnInit() {
    this.globalEventsService.showCheckoutHeader = true;
}

A ceci :

constructor(private globalEventsService: GlobalEventsService) {
    this.globalEventsService.showCheckoutHeader = true;
}

ngOnInit() {

}

et cela a réglé le problème :)

4 votes

Mon problème était similaire. Que j'ai fait une erreur après de longues heures, et défini une variable en dehors de la fonction ngOnInit et du constructeur. Cela reçoit les changements de données d'un observable, qui est placé dans la fonction d'initialisation. J'ai fait la même chose que vous pour corriger l'erreur.

1 votes

Très similaire dans l'ensemble, mais j'essayais de faire défiler ( router.navigate ) lors du chargement vers un fragment s'il est présent dans l'URL. Ce code était initialement placé dans AfterViewInit où je recevais l'erreur, puis je suis passé comme vous le dites au constructeur mais il ne respectait pas le fragment. Le déplacement vers ngOnInit résolu :) merci !

0 votes

Si mon html est lié à un getter retournant l'heure sous forme de "HH:MM" via get ClockValue() { return DateTime.TimeAMPM(new Date()) } il finira par se déclencher lorsque les minutes changeront pendant que la détection est en cours, comment puis-je corriger cela ?

46voto

Arnaud P Points 613

Mise à jour

Je recommande vivement de commencer par l'auto-réponse du PO Tout d'abord : réfléchir correctement à ce qui peut être fait dans la constructor par rapport à ce qui devrait être fait dans ngOnChanges() .

Original

Il s'agit plus d'un aparté que d'une réponse, mais cela pourrait aider quelqu'un. Je suis tombé sur ce problème en essayant de faire en sorte que la présence d'un bouton dépende de l'état du formulaire :

<button *ngIf="form.pristine">Yo</button>

Pour autant que je sache, cette syntaxe conduit à ce que le bouton soit ajouté et retiré du DOM en fonction de la condition. Ce qui, à son tour, conduit à la fonction ExpressionChangedAfterItHasBeenCheckedError .

La solution dans mon cas (bien que je ne prétende pas saisir toutes les implications de la différence), était d'utiliser display: none à la place :

<button [style.display]="form.pristine ? 'inline' : 'none'">Yo</button>

6 votes

Si je comprends bien la différence entre le ngIf et le style, le ngIf n'inclut pas le HTML dans la page tant que la condition n'est pas vraie, ce qui réduit un peu le "poids de la page", tandis que la technique du style fait que le HTML est toujours dans la page et qu'il est simplement caché ou affiché en fonction de la valeur de form.pristine.

5 votes

Vous pourriez aussi bien utiliser [hidden] au lieu de la très verbeuse [style.display] partie. :)

2 votes

Pourquoi pas. Bien que, comme mentionné par @Simon_Weaver dans un autre commentaire sur cette page, [hidden] n'auront pas toujours le même comportement comme display: none

28voto

Andre Evangelista Points 907

Dans mon cas, j'ai eu ce problème dans mon fichier spec, lors de l'exécution de mes tests.

J'ai dû changer ngIf à [hidden]

<app-loading *ngIf="isLoading"></app-loading>

à

<app-loading [hidden]="!isLoading"></app-loading>

1 votes

2 votes

La différence ici est que *ngIf modifie le DOM, en ajoutant et en supprimant l'élément de la page, tandis que l'option [hidden] modifie la visibilité de l'élément tout en ne le supprimant pas du DOM.

6 votes

Mais, cela n'a pas vraiment réglé le vrai problème... ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X