web-dev-qa-db-de.com

ExpressionChangedAfterItHasBeenCheckedError Erklärt

Bitte erklären Sie mir, warum ich diese Fehlermeldung bekomme: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.

Offensichtlich bekomme ich es nur im dev-Modus, das passiert nicht bei meinem Produktionsaufbau, aber es ist sehr ärgerlich und ich verstehe einfach nicht die Vorteile eines Fehlers in meiner dev-Umgebung, der sich nicht auf prod zeigt. -wahrscheinlich wegen meines Unverständnisses.

Normalerweise ist das Update einfach genug. Ich wickle den Fehler, der den Code verursacht, in ein setTimeout wie folgt ein:

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

Oder erzwingen Sie die Erkennung von Änderungen mit einem Konstruktor wie folgt: constructor(private cd: ChangeDetectorRef) {}:

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

Warum stoße ich ständig auf diesen Fehler? Ich möchte es verstehen, damit ich diese hacky Fixes in Zukunft vermeiden kann.

135
Kevin LeStarge

Nachdem ich die Angular Lifecycle Hooks und ihre Beziehung zur Änderungserkennung verstanden hatte, kam viel Verständnis zustande.

Ich habe versucht, Angular zu veranlassen, ein globales Flag zu aktualisieren, das an den *ngIf eines Elements gebunden ist, und ich habe versucht, dieses Flag innerhalb des ngOnInit() Lebenszyklus-Hooks einer anderen Komponente zu ändern. 

Laut der Dokumentation wird diese Methode aufgerufen, nachdem Angular Änderungen bereits erkannt hat:

Einmal aufgerufen, nach dem ersten ngOnChanges ().

Das Aktualisieren des Flag in ngOnChanges() löst also keine Änderungserkennung aus. Sobald die Änderungserkennung natürlich erneut ausgelöst wurde, hat sich der Wert der Flagge geändert und der Fehler wird ausgelöst.

In meinem Fall habe ich das geändert:

constructor(private globalEventsService: GlobalEventsService) {

}

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

Zu diesem:

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

ngOnInit() {

}

und das Problem wurde behoben :)

34
Kevin LeStarge

Ich hatte ein ähnliches Problem. Wenn ich mir die Dokumentation der lifecycle-Hooks ansehe , änderte ich ngAfterViewInit in ngAfterContentInit und es funktionierte.

67
onlyme

Dieser Fehler weist auf ein echtes Problem in Ihrer Anwendung hin. Daher ist es sinnvoll, eine Ausnahme auszulösen.

Bei devMode Change Detection wird nach jedem regulären Change Detection-Lauf eine zusätzliche Drehung hinzugefügt, um zu prüfen, ob das Modell geändert wurde.

Wenn das Modell zwischen dem regulären und dem zusätzlichen Änderungserkennungswechsel gewechselt hat, wird dies angezeigt

  • die Änderungserkennung selbst hat zu einer Änderung geführt
  • eine Methode oder ein Getter gibt bei jedem Aufruf einen anderen Wert zurück

beide sind schlecht, weil es nicht klar ist, wie es weitergehen soll, da sich das Modell möglicherweise niemals stabilisiert. 

Wenn die Änderungserkennung von Angular läuft, bis sich das Modell stabilisiert hat, wird es möglicherweise für immer ausgeführt. Wenn Angular die Änderungserkennung nicht ausführt, spiegelt die Ansicht möglicherweise nicht den aktuellen Status des Modells wider.

Siehe auch Was ist der Unterschied zwischen Produktion und Entwicklungsmodus in Angular2?

58

Update

Ich empfehle dringend, zuerst mit der Selbstantwort des OP zu beginnen: Überlegen Sie sich gut, was in constructor möglich ist, im Vergleich zu dem, was in ngOnChanges() gemacht werden soll.

Original

Dies ist mehr eine Randnotiz als eine Antwort, aber es könnte jemandem helfen. Ich bin über dieses Problem gestolpert, als ich versuchte, das Vorhandensein einer Schaltfläche vom Zustand des Formulars abhängig zu machen:

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

Soweit ich weiß, führt diese Syntax dazu, dass die Schaltfläche auf der Grundlage der Bedingung hinzugefügt und aus dem DOM entfernt wird. Was wiederum zur ExpressionChangedAfterItHasBeenCheckedError führt.

Das Update in meinem Fall (obwohl ich nicht behaupte, die vollen Auswirkungen des Unterschieds zu verstehen), war stattdessen display: none:

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

In meinem Fall hatte ich dieses Problem in meiner Spezifikationsdatei, während meine Tests ausgeführt wurden. 

Ich musste ngIf in [hidden] ändern 

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

bis

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

Befolgen Sie die folgenden Schritte:

1 . Verwenden Sie 'ChangeDetectorRef', indem Sie es wie folgt aus @ angle/core importieren:

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

2 . Implementiere es in constructor () wie folgt:

constructor(   private cdRef : ChangeDetectorRef  ) {}

3 ..__ Fügen Sie Ihrer Funktion die folgende Methode hinzu, die Sie für ein Ereignis wie den Klick auf die Schaltfläche aufrufen. So sieht es so aus: 

functionName() {   
    yourCode;  
    //add this line to get rid of the error  
    this.cdRef.detectChanges();     
}
12

Ich hatte das gleiche Problem, als sich der Wert in einem der Arrays in meiner Komponente änderte. Anstatt die Änderungen bei der Wertänderung zu erkennen, änderte ich die Erkennungsstrategie für Komponentenänderungen in onPush (die Änderungen bei der Objektänderung und nicht bei der Wertänderung erkennt). 

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

@Component({
    changeDetection: ChangeDetectionStrategy.OnPush
    selector: -
    ......
})
10
Dheeraj

Es gab interessante Antworten, aber ich schien keine zu finden, die meinen Bedürfnissen entsprach. Die beste Lösung war @ chittrang-mishra, die sich nur auf eine bestimmte Funktion bezieht und nicht wie in meiner App mehrere Schaltflächen.

Ich wollte nicht [hidden] verwenden, um *ngIf nicht einmal zum DOM zu zählen. Daher habe ich die folgende Lösung gefunden, die möglicherweise nicht die beste für alle ist, da sie den Fehler unterdrückt, anstatt ihn zu korrigieren, aber in meinem Fall Wenn das Endergebnis korrekt ist, scheint es für meine App in Ordnung zu sein. 

Was ich tat, war AfterViewChecked, füge constructor(private changeDetector : ChangeDetectorRef ) {} hinzu und dann 

ngAfterViewChecked(){
  this.changeDetector.detectChanges();
}

Ich hoffe, das hilft anderen, da mir viele andere geholfen haben.

9
eper

Unter Bezugnahme auf den Artikel https://blog.angularindepth.com/everything-you-need-to-know-ow-about-hinausdrucksimitAndereinerAusgabeterror-error-e3fd9ce7dbb4

Die Mechanismen hinter der Änderungserkennung arbeiten also tatsächlich so, dass sowohl die Änderungserkennung als auch der Überprüfungsauszug synchron ausgeführt werden. Das heißt, wenn wir die Eigenschaften asynchron aktualisieren, werden die Werte nicht aktualisiert, wenn die Verifizierungsschleife ausgeführt wird, und es wird kein ExpressionChanged...-Fehler angezeigt. Der Grund für diesen Fehler ist, dass Angular während des Überprüfungsprozesses andere Werte sieht als in der Änderungserkennungsphase. Also um das zu vermeiden ....

1) Verwenden Sie changeDetectorRef

2) setTimeOut verwenden. Dadurch wird der Code in einem anderen VM als Makroaufgabe ausgeführt. Angular erkennt diese Änderungen während des Überprüfungsvorgangs nicht und Sie erhalten diesen Fehler nicht.

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

3) Wenn Sie Ihren Code wirklich auf demselben VM ausführen möchten, verwenden Sie wie 

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

Dadurch wird eine Mikroaufgabe erstellt. Die Micro-Task-Warteschlange wird verarbeitet, nachdem die Ausführung des aktuellen synchronen Codes abgeschlossen ist. Daher wird die Aktualisierung der Eigenschaft nach dem Überprüfungsschritt durchgeführt.

9
ATHER

Angular-Läufe ändern die Erkennung und wenn festgestellt wird, dass einige an die untergeordnete Komponente übergebene Werte geändert wurden, führt Angular den Fehler aus. ExpressionChangedAfterItHasBeenCheckedError klicken Sie auf Weitere

Um dies zu korrigieren, können wir den Lebenszyklushaken und den AfterContentChecked verwenden

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

  constructor(
  private cdref: ChangeDetectorRef) { }

  ngAfterContentChecked() {

    this.cdref.detectChanges();

  }
5
Lijo

Mein Problem war offensichtlich, als ich *ngIf hinzufügte, aber das war nicht die Ursache. Der Fehler wurde dadurch verursacht, dass das Modell in {{}}-Tags geändert wurde und anschließend versucht wurde, das geänderte Modell in der *ngIf-Anweisung anzuzeigen. Hier ist ein Beispiel:

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

Um das Problem zu beheben, änderte ich, wo ich changeMyModelValue () anrief, einen Ort, der mehr Sinn machte. 

In meiner Situation wollte ich changeMyModelValue () immer dann aufrufen, wenn eine untergeordnete Komponente die Daten geändert hat. Dazu muss ich ein Ereignis in der untergeordneten Komponente erstellen und ausgeben, damit das übergeordnete Element damit umgehen kann (durch Aufrufen von changeMyModelValue (). Siehe https://angular.io/guide/component-interaction#parent-listens-for-child- Veranstaltung

1
goku_da_master

In meinem Fall hatte ich eine asynchrone Eigenschaft in LoadingService mit einem BehavioralSubject isLoading

Die Verwendung des [hidden] -Modells funktioniert, aber * ngIf schlägt fehl

    <h1 [hidden]="!(loaderService.isLoading | async)">
        THIS WORKS FINE
        (Loading Data)
    </h1>

    <h1 *ngIf="!(loaderService.isLoading | async)">
        THIS THROWS ERROR
        (Loading Data)
    </h1>
1
Mahesh

Ich hatte diese Art von Fehler in Ionic3 (der Angular 4 als Teil seines Technologie-Stacks verwendet).

Für mich war es so:

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

Also habe ich versucht, den Typ eines Ionen-Symbol von einem pin in einen remove-circle Zu ändern, je nachdem, in welchem ​​Modus ein Bildschirm betrieben wurde.

Ich schätze, ich muss stattdessen ein * ngIF hinzufügen.

1
JGFMK

Hier sind meine Gedanken darüber, was passiert. Ich habe die Dokumentation nicht gelesen, bin aber sicher, dass der Fehler angezeigt wird. 

*ngIf="isProcessing()" 

Bei Verwendung von * ngIf wird der DOM physisch geändert, indem das Element bei jeder Änderung der Bedingung hinzugefügt oder entfernt wird. Wenn sich also die Bedingung ändert, bevor sie für die Ansicht gerendert wird (was in Angulars Welt sehr gut möglich ist), wird der Fehler ausgelöst. Siehe Erklärung hier zwischen Entwicklungs- und Produktionsmodus.

[hidden]="isProcessing()"

Bei der Verwendung von [ausgeblendet] wird das DOM nicht physisch geändert, sondern es wird lediglich das Element aus der Ansicht ausgeblendet, höchstwahrscheinlich mit CSS in der Rückseite. Das Element ist immer noch im DOM vorhanden, aber abhängig vom Wert der Bedingung nicht sichtbar. Deshalb tritt der Fehler nicht auf, wenn [ausgeblendet] verwendet wird.

1
Kobus

Eine Lösung, die bei mir mit rxjs funktioniert hat

import { startWith, tap, delay } from 'rxjs/operators';

// Data field used to populate on the html
dataSource: any;

....

ngAfterViewInit() {
  this.yourAsyncData.
      .pipe(
          startWith(null),
          delay(0),
          tap((res) => this.dataSource = res)
      ).subscribe();
}
1
Sandeep K Nair

Für mein Problem las ich github - "ExpressionChangedAfterItHasBeenCheckedError beim Ändern eines Komponenten-Nicht-Modell-Werts in afterViewInit" und entschied mich, das ngModel hinzuzufügen

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

Es hat mein Problem behoben, ich hoffe es hilft jemandem.

1
Demodave

Ich habe diesen Fehler erhalten, weil ich eine Variable in component.html verwendet habe, die in component.ts nicht deklariert wurde. Nachdem ich den Teil in HTML entfernt hatte, war dieser Fehler behoben.

0
shreekar hegde

@HostBinding kann eine verwirrende Quelle für diesen Fehler sein.

Angenommen, Sie haben die folgende Host-Bindung in einer Komponente

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

Nehmen wir zur Vereinfachung an, dass diese Eigenschaft über die folgende Eingabeeigenschaft aktualisiert wird:

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

In der übergeordneten Komponente setzen Sie es programmatisch in ngAfterViewInit.

@ViewChild(ImageCarousel) carousel: ImageCarousel;

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

Folgendes passiert:

  • Ihre übergeordnete Komponente wird erstellt
  • Die ImageCarousel-Komponente wird erstellt und carousel zugewiesen (über ViewChild).
  • Wir können nicht auf carousel zugreifen, bis ngAfterViewInit() (es wird null sein)
  • Wir weisen die Konfiguration zu, die style_groupBG = 'red' setzt.
  • Dies setzt wiederum background: red für die Host ImageCarousel-Komponente
  • Diese Komponente befindet sich im Besitz der übergeordneten Komponente. Wenn sie nach Änderungen sucht, findet sie eine Änderung in carousel.style.background und ist nicht klug genug, um zu wissen, dass dies kein Problem ist, und es wird eine Ausnahme ausgelöst.

Eine Lösung besteht darin, einen anderen Wrapper-Div-Insider ImageCarousel einzuführen und die Hintergrundfarbe darauf festzulegen, aber dann werden einige Vorteile der Verwendung von HostBinding nicht erzielt (z. B. wenn der übergeordnete Benutzer die gesamten Objektgrenzen steuern kann).

Die bessere Lösung in der übergeordneten Komponente besteht darin, detectChanges () nach dem Festlegen der Konfiguration hinzuzufügen.

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

Dies mag auf den ersten Blick ziemlich offensichtlich aussehen und anderen Antworten sehr ähnlich sein, aber es gibt einen subtilen Unterschied.

Betrachten Sie den Fall, in dem Sie @HostBinding erst später während der Entwicklung hinzufügen. Plötzlich erhalten Sie diesen Fehler und es scheint keinen Sinn zu ergeben.

0
Simon_Weaver

Ich habe diesen Fehler erhalten, weil ich Redux-Aktionen modal gesendet habe und modal zu diesem Zeitpunkt nicht geöffnet war. In dem Moment, in dem eine modale Komponente Eingaben erhält, habe ich Aktionen ausgelöst. Also habe ich setTimeout dort abgelegt, um sicherzustellen, dass das Modal geöffnet ist und dann die Aktionen abgesetzt werden.

0
Muneem Habib

Die Lösung ... services und rxjs ... Event-Emitter und Property-Binding verwenden beide rxjs. Denken Sie daran, dass Event-Emitter rxjs verwenden. Erstellen Sie einfach einen Service und lassen Sie jede Komponente innerhalb eines Observablen den Observer abonnieren und je nach Bedarf entweder einen neuen Wert oder einen cosume-Wert übergeben

0
user998548

Ich hatte einen Codeblock im Subskriptionsbereich. Ich habe ihn nur in den oberen Bereich geändert und er funktioniert einwandfrei.

Tipps zum Debuggen

Dieser Fehler kann ziemlich verwirrend sein, und es ist leicht, eine falsche Annahme zu treffen, wann genau er auftritt. Ich finde es hilfreich, viele Debugging-Anweisungen in den betroffenen Komponenten an den entsprechenden Stellen hinzuzufügen. Dies hilft, den Fluss zu verstehen.

Fügen Sie in der übergeordneten Anweisung solche Anweisungen ein (die genaue Zeichenfolge 'EXPRESSIONCHANGED' ist wichtig), aber ansonsten sind dies nur Beispiele:

    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');

In den Child/Services/Timer-Rückrufen:

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

Wenn Sie detectChanges manuell ausführen, fügen Sie auch hierfür die Protokollierung hinzu:

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

Dann filtern Sie im Chrome-Debugger einfach nach 'EXPRESSIONCHANGES'. Dies zeigt Ihnen genau den Fluss und die Reihenfolge von allem, was gesetzt wird, und auch genau an welchem ​​Punkt Angular den Fehler auslöst.

 enter image description here

Sie können auch auf die grauen Links klicken, um Haltepunkte einzufügen.

Eine weitere Sache, die Sie beachten sollten, wenn Sie Eigenschaften in Ihrer Anwendung ähnlich benannt haben (z. B. style.background), stellen Sie sicher, dass Sie das debuggen, von dem Sie denken, dass Sie es denken - indem Sie es auf einen undeutlichen Farbwert setzen.

0
Simon_Weaver

Ich hoffe, dies hilft jemandem, der hierher kommt: Wir führen Serviceaufrufe in ngOnInit auf folgende Weise durch und verwenden eine Variable displayMain, um die Montage der Elemente in das DOM zu steuern.

component.ts

  displayMain: boolean;
  ngOnInit() {
    this.displayMain = false;
    // Service Calls go here
    // Service Call 1
    // Service Call 2
    // ...
    this.displayMain = true;
  }

und component.html

<div *ngIf="displayMain"> <!-- This is the Root Element -->
 <!-- All the HTML Goes here -->
</div>
0
retr0