ExpressionChangedAfterItHasBeenCheckederror Expliqué

s'il vous plaît, expliquez-moi pourquoi je continue à avoir cette erreur: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.

évidemment, je ne l'obtiens qu'en mode dev, cela 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 ne se montrera pas sur prod --probablement en raison de mon manque de compréhension.

habituellement, le correctif est assez facile, je viens d'envelopper le code de cause d'erreur dans un setTimeout comme ceci:

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

Ou de la force de détecter les changements avec un constructeur comme ceci: constructor(private cd: ChangeDetectorRef) {} :

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

mais pourquoi est-ce que je tombe constamment sur cette erreur? Je veux le comprendre pour éviter ces corrections à l'avenir.

93
demandé sur Rohit Sharma 2017-04-12 19:59:34

14 réponses

j'ai eu un problème similaire. En regardant la documentation lifecycle hooks , j'ai changé ngAfterViewInit en ngAfterContentInit et ça a marché.

51
répondu onlyme 2018-03-22 04:24:24

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

dans devMode la détection de changement ajoute un virage supplémentaire après chaque course régulière de détection de changement pour vérifier si le modèle a changé.

si le modèle a changé entre le virage régulier et le virage additionnel de détection de changement, cela indique que soit

  • variation 151990920"
  • une méthode ou getter renvoie une valeur différente chaque fois qu'elle est appelée

qui sont à la fois mauvais, parce qu'il n'est pas clair comment procéder parce que le modèle pourrait ne jamais se stabiliser.

si les points angulaires changent de détection jusqu'à ce que le modèle se stabilise, il pourrait fonctionner pour toujours. Si Angular ne lance pas la détection de changement, alors la vue pourrait ne pas refléter l'état actuel du modèle.

voir aussi Quelle est la différence entre le mode de production et le mode de développement en Angular2?

49
répondu Günter Zöchbauer 2017-05-23 11:47:26

C'est plus une remarque qu'une réponse, mais il pourrait aider quelqu'un. Je suis tombé sur ce problème en essayant de faire la présence d'un bouton dépend de l'état de la forme:

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

pour autant que je sache, cette syntaxe conduit à ajouter le bouton et à le supprimer du DOM en fonction de la condition. Ce qui à son tour conduit au ExpressionChangedAfterItHasBeenCheckedError .

la solution dans mon cas (bien que je ne prétends 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>
20
répondu Arnaud P 2017-06-01 07:06:46

beaucoup de compréhension est venue une fois que j'ai compris les crochets de cycle de vie angulaire et leur relation avec la détection de changement.

j'essayais d'obtenir un angle pour mettre à jour un drapeau global lié au *ngIf d'un élément, et j'essayais de changer ce drapeau à l'intérieur du ngOnInit() crochet du cycle de vie d'un autre composant.

selon la documentation, cette méthode est appelée après Angular a déjà changements détectés:

Appelé une fois, après la première ngOnChanges().

donc mettre à jour le drapeau à l'intérieur de ngOnChanges() ne déclenchera pas la détection de changement. Ensuite, une fois la détection de changement déclenchée naturellement à nouveau, la valeur du drapeau a changé et l'erreur est lancée.

dans mon cas, j'ai changé ceci:

constructor(private globalEventsService: GlobalEventsService) {

}

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

à ceci:

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

ngOnInit() {

}

et il corrigé le problème:)

20
répondu Kevin LeStarge 2018-01-11 21:52:18

dans mon cas, j'ai eu ce problème dans mon fichier spec, pendant que je faisais mes tests.

j'ai dû changer ngIf en [hidden]

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

à

<app-loading [hidden]="!isLoading"></app-loading>
12
répondu Andre Evangelista 2018-05-10 21:48:21

je faisais face au même problème que la valeur changeait dans un des tableaux de ma composante. Mais au lieu de détecter les changements sur le changement de valeur, j'ai changé la stratégie de détection de changement de Composant en onPush (qui détectera les changements sur le changement d'objet et non sur le changement de valeur).

import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';

@Component({
    changeDetection: ChangeDetectionStrategy.OnPush
    selector: -
    ......
})
8
répondu Dheeraj 2018-05-12 22:40:11

suivre les étapes suivantes:

1. Utilisez "ChangeDetectorRef" en l'important de @angular / core comme suit:

import{ ChangeDetectorRef } from '@angular/core';

2. L'implémenter dans le constructeur () comme suit:

constructor(   private cdRef : ChangeDetectorRef  ) {}

3. Ajoutez la méthode suivante à votre fonction que vous appelez sur un événement de clic de bouton. De sorte qu'il ressemble à ceci:

functionName() {   
    yourCode;  
    //add this line to get rid of the error  
    this.cdRef.detectChanges();     
}
7
répondu Chittrang Mishra 2018-05-01 12:08:45

se référant à l'article https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

donc la mécanique derrière la détection de changement fonctionne réellement d'une manière que la détection de changement et les digestions de vérification sont effectuées de manière synchrone. Cela signifie que, si nous mettons à jour les propriétés de manière asynchrone, les valeurs ne seront pas mises à jour lorsque la boucle de vérification est en cours d'exécution et que nous n'obtiendra pas l'erreur ExpressionChanged... . La raison pour laquelle nous obtenons cette erreur est, pendant le processus de vérification, angulaire voit des valeurs différentes puis ce qu'il a enregistré pendant la phase de détection de changement. Donc pour éviter ça....

1) utiliser changeDetectorRef

2) utiliser setTimeOut. Ceci exécutera votre code dans une autre VM comme tâche macro. Angulaire verrez pas ces modifications au cours du processus de vérification et vous n'obtiendrez pas cette erreur.

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

3) Si vous voulez vraiment exécuter votre code sur la même VM utilisez comme

Promise.resolve(null).then(() => this.isLoading = true);

cela créera une micro-tâche. La file d'attente de micro-tâches est traitée après l'exécution du code synchrone en cours. par conséquent, la mise à jour de la propriété se fera après l'étape de vérification.

4
répondu ATHER 2018-06-12 21:55:13

j'ai eu ce genre d'erreur dans Ionic3 (qui utilise L'angle 4 comme une partie de sa pile de technologie).

Pour moi, c'était cela:

<ion-icon [name]="getFavIconName()"></ion-icon>

alors j'ai essayé de changer conditionnellement le type d'un ion-icon d'un pin à un remove-circle , par un mode sur lequel un écran fonctionnait.

je suppose que je vais devoir ajouter un *ngIF à la place.

1
répondu JGFMK 2017-10-18 17:58:08

pour mon numéro, je lisais github - "ExpressionChangedAfterItHasBeenCheckederror lors de la modification d'un composant 'valeur non modèle' dans afterViewInit" et a décidé d'ajouter le ngModel

<input type="hidden" ngModel #clientName />

il a réglé mon problème, j'espère qu'il aide quelqu'un.

0
répondu Demodave 2018-04-13 14:57:31

Voici mes pensées sur ce qui se passe. Je n'ai pas lu la documentation mais je suis sûr que c'est en partie pourquoi le message d'erreur est affiché.

*ngIf="isProcessing()" 

lors de l'utilisation de *ngIf, il modifie physiquement le DOM en ajoutant ou en supprimant l'élément chaque fois que la condition change. Ainsi, si la condition change avant d'être rendue à la vue (ce qui est très possible dans le monde D'Angular), l'erreur est jetée. Voir explication ici entre développement et des modes de production.

[hidden]="isProcessing()"

en utilisant [caché] il ne change pas physiquement le DOM mais simplement cacher l'élément de la vue, très probablement en utilisant CSS dans le dos. L'élément est toujours là dans le DOM mais pas visible selon la valeur de la condition. C'est pourquoi l'erreur ne se produit pas lors de l'utilisation de [caché].

0
répondu Kobus 2018-05-07 03:55:25

@HostBinding peut être une source de confusion de cette erreur.

par exemple, disons que vous avez la liaison hôte suivante dans un composant

// image-carousel.component.ts
@HostBinding('style.background') 
style_groupBG: string;

par souci de simplicité, disons que cette propriété est mise à jour via la propriété input suivante:

@Input('carouselConfig')
public set carouselConfig(carouselConfig: string) 
{
    this.style_groupBG = carouselConfig.bgColor;   
}

dans le composant parent vous le programmez dans ngAfterViewInit

@ViewChild(ImageCarousel) carousel: ImageCarousel;

ngAfterViewInit()
{
    this.carousel.carouselConfig = { bgColor: 'red' };
}

voici ce qui se passe:

  • votre composant parent est créé
  • le composant ImageCarousel est créé, et assigné à carousel (via ViewChild)
  • Nous ne pouvons pas accéder à carousel jusqu'à ngAfterViewInit() (il sera null)
  • nous assignons la configuration, qui définit style_groupBG = 'red'
  • ce qui à son tour met background: red sur le composant ImageCarousel hôte
  • ce composant est 'possédé' par votre composant parent, donc quand il vérifie les changements, il trouve un changement sur carousel.style.background et n'est pas assez intelligent pour savoir que ce n'est pas un problème donc il jette l'exception.

une solution est d'introduire un autre ImageCarousel wrapper div insider et de mettre la couleur de fond sur cela, mais alors vous ne recevez pas certains des avantages de l'utilisation de HostBinding (tel que permettre au parent de contrôler les limites complètes de l'objet).

la meilleure solution, dans le composant parent est d'ajouter detectChanges() après avoir paramétré la config.

ngAfterViewInit()
{
    this.carousel.carouselConfig = { ... };
    this.cdr.detectChanges();
}

cela peut sembler évident exposé comme ceci, et très similaire à d'autres réponses, mais il ya une différence subtile.

envisager le cas où vous ne pas ajouter @HostBinding jusqu'à plus tard au cours du développement. Soudain, vous avez cette erreur et elle ne semble pas avoir de sens.

0
répondu Simon_Weaver 2018-08-02 21:01:21

conseils de débogage

cette erreur peut être assez confuse, et il est facile de faire une fausse hypothèse sur exactement quand elle se produit. Je trouve utile d'ajouter beaucoup d'instructions de débogage comme celles-ci à travers les composants affectés aux endroits appropriés. Cela aide à comprendre le flux.

dans le parent mettre des déclarations comme ceci (la chaîne exacte 'EXPRESSIONCHANGED' est importante), mais à part que ce ne sont que des exemples:

    console.log('EXPRESSIONCHANGED - HomePageComponent: constructor');
    console.log('EXPRESSIONCHANGED - HomePageComponent: setting config', newConfig);
    console.log('EXPRESSIONCHANGED - HomePageComponent: setting config ok');
    console.log('EXPRESSIONCHANGED - HomePageComponent: running detectchanges');

Dans l'enfant / services / rappels de la minuterie:

    console.log('EXPRESSIONCHANGED - ChildComponent: setting config');
    console.log('EXPRESSIONCHANGED - ChildComponent: setting config ok');

si vous exécutez detectChanges ajouter manuellement la journalisation pour cela aussi:

    console.log('EXPRESSIONCHANGED - ChildComponent: running detectchanges');
    this.cdr.detectChanges();

puis dans le débogueur Chrome il suffit de filtrer par 'EXPRESIONCHANGES'. Cela va vous montrer exactement le flux et l'ordre de tout ce qui est défini, et aussi exactement à quel moment Angulaire génère l'erreur.

enter image description here

vous pouvez également cliquer sur les liens gris pour mettre les points d'arrêt.

autre chose à surveiller si vous avez des propriétés nommées de la même façon dans votre application (comme style.background ) assurez - vous que vous déboguez celle que vous pensez-en la mettant à une valeur de couleur obscure.

0
répondu Simon_Weaver 2018-08-02 21:19:58

ma question était évidente quand j'ai ajouté *ngIf mais ce n'était pas la cause. L'erreur a été causée en changeant le modèle dans {{}} tags puis en essayant d'afficher le modèle modifié dans la déclaration *ngIf plus tard. Voici un exemple:

<div>{{changeMyModelValue()}}</div> <!--don't do this!  or you could get error: ExpressionChangedAfterItHasBeenCheckedError-->
....
<div *ngIf="true">{{myModel.value}}</div>

pour régler le problème, j'ai changé l'endroit où j'ai appelé changeMyModelValue() en un endroit qui avait plus de sens.

dans ma situation je voulais changeMyModelValue() appelé chaque fois qu'un enfant composant changé les données. Cela nécessitait que je crée et émette un événement dans le composant enfant afin que le parent puisse le gérer (en appelant changeMyModelValue ()). voir https://angular.io/guide/component-interaction#parent-listens-for-child-event

0
répondu goku_da_master 2018-09-11 18:50:50