Angular 4 - "Ausdruck hat sich geändert, nachdem es überprüft wurde" -Fehler bei der Verwendung von NG-IF

8

Ich habe ein service eingerichtet, um die angemeldeten Benutzer zu verfolgen. Dieser Service gibt ein Observable zurück und alle Komponenten, denen subscribe zugeordnet sind, werden (bisher ist nur eine einzelne Komponente abonniert) .

Service:

%Vor%

Root-App-Komponente: (diese Komponente abonniert die Observable)

%Vor%

Willkommen Komponente:

%Vor%

App-Komponente Html: (hier tritt der Fehler auf)

%Vor%

Was ich versuche zu tun:

Ich möchte, dass jede Komponente ( außer der Root-App-Komponente ) den angemeldeten Benutzerstatus an die Root-App-Komponente sendet, damit ich die Benutzeroberfläche innerhalb der Root-App-Komponente HTML bearbeiten kann.

>

Das Problem:

Ich erhalte den folgenden Fehler, wenn die Willkommen Komponente initialisiert wird.

%Vor%

Bitte beachten Sie, dass dieser Fehler bei diesem *ngIf="user" -Ausdruck auftritt, der sich in der HTML-Datei der Root-App-Komponenten befindet.

Kann jemand den Grund für diesen Fehler erklären und wie kann ich das beheben?

Nebenbei bemerkt: Wenn Sie denken, dass es einen besseren Weg gibt, um das zu erreichen, was ich versuche, lassen Sie es mich wissen.

Update 1:

Wenn Sie Folgendes in constructor einfügen, wird das Problem gelöst, aber die constructor wird für diesen Zweck nicht verwendet. Es scheint also keine gute Lösung zu sein.

Willkommen Komponente:

%Vor%

Root-App-Komponente:

%Vor%

Update 2:

Hier ist der Plunkr . Um den Fehler zu sehen, überprüfen Sie die Browser-Konsole. Wenn die App lädt, sollte ein boolescher Wert von true angezeigt werden, aber ich bekomme den Fehler in der Konsole.

Bitte beachten Sie, dass dieser Plunkr eine sehr einfache Version meiner Haupt-App ist. Da die App etwas groß ist, konnte ich nicht den ganzen Code hochladen. Aber der Plunkr demonstriert den Fehler perfekt.

    
Skywalker 01.11.2017, 18:37
quelle

5 Antworten

5

Dies bedeutet, dass der Änderungserkennungszyklus selbst eine Änderung verursacht hat, die zufällig (dh der Änderungserkennungszyklus hat sie irgendwie verursacht) oder beabsichtigt verursacht wurde. Wenn Sie absichtlich etwas in einem Änderungserkennungszyklus ändern, sollte dies eine neue Runde der Änderungserkennung auslösen, die hier nicht stattfindet. Dieser Fehler wird im Prod-Modus unterdrückt, bedeutet aber, dass Sie Probleme in Ihrem Code haben und mysteriöse Probleme verursachen.

In diesem Fall besteht das spezielle Problem darin, dass Sie etwas im Änderungserkennungszyklus eines Kindes ändern, das sich auf das übergeordnete Element auswirkt. Dadurch wird die Änderungserkennung des übergeordneten Elements nicht erneut ausgelöst, obwohl asynchrone Trigger wie Observables dies normalerweise tun. Der Grund dafür, dass der Zyklus des Elternteils nicht erneut ausgelöst wird, ist, dass dies den unidirektionalen Datenfluss verletzt und eine Situation verursachen könnte, in der ein Kind einen Elternänderungs-Erkennungszyklus erneut auslöst, der das Kind und dann das Elternelement erneut auslöst usw. eine Endlosschleife zur Erkennung von Änderungen in Ihrer App

Es klingt vielleicht so, als würde ich sagen, dass ein Kind keine Nachrichten an eine Elternkomponente senden kann, aber das ist nicht der Fall, das Problem ist, dass ein Kind während einer Änderungserkennung keine Nachricht an einen Elternteil senden kann Zyklus (z. B. Lebenszyklus-Hooks), muss es außerhalb passieren, als in Reaktion auf ein Benutzerereignis.

Die beste Lösung besteht darin, die Verletzung des unidirektionalen Datenflusses zu stoppen, indem Sie eine neue Komponente erstellen, die kein übergeordnetes Element der Komponente ist, die die Aktualisierung verursacht, so dass keine Endlosänderungs-Erkennungsschleife erstellt werden kann. Dies wird in der unten stehenden Liste gezeigt.

Neue app.component mit Kind hinzugefügt:

%Vor%

Nachrichtenkomponente:

%Vor%

Vorlage:

%Vor%

leicht geänderter Nachrichtendienst (nur eine etwas sauberere Struktur):

%Vor%

Dies hat mehr Vorteile, als nur die Erkennungsfunktion korrekt ändern zu lassen, ohne das Risiko, Endlosschleifen zu erzeugen. Es macht auch Ihren Code modularer und isoliert die Verantwortlichkeit besser.

Zypern

    
bryan60 01.11.2017, 18:59
quelle
1

Deklarieren Sie diese Zeile im Konstruktor

%Vor%

danach in ngAfterviewInit Anruf wie folgt

%Vor%

Es wird Ihr Problem lösen, weil der boolesche Wert des DOM-Elements keine Änderung erhält. also seine throw exception

Ihre Plunkr Antwort hier Bitte überprüfen Sie mit AppComponent

%Vor%     
Piyush Patel 03.11.2017 09:06
quelle
1

Nette Frage, also, was verursacht das Problem? Was ist der Grund für diesen Fehler? Wir müssen verstehen, wie die Erkennung der Winkeländerung funktioniert, ich werde kurz erklären:

  • Sie binden eine Eigenschaft an eine Komponente
  • Sie führen eine Anwendung
  • aus
  • Ein Ereignis tritt auf (Timeouts, Ajax-Aufrufe, DOM-Ereignisse, ...)
  • Die gebundene Eigenschaft wird als Auswirkung des Ereignisses
  • geändert
  • Angular hört auch auf das Ereignis und führt eine Änderungserkennungsrunde aus
  • Angular aktualisiert die Ansicht
  • Angular ruft die Lebenszyklus-Hooks ngOnInit , ngOnChanges und ngDoCheck auf.
  • Winkeln Sie eine Change Detection Round in allen untergeordneten Komponenten
  • Angular ruft die Lebenszyklus-Hooks ngAfterViewInit auf

Aber was ist, wenn ein Lifecycle-Hook einen Code enthält, der die Eigenschaft erneut ändert und eine Änderungserkennungsrunde nicht ausgeführt wird? Oder was ist, wenn ein Lebenszyklus-Hook einen Code enthält, der eine andere Änderungserkennungsrunde verursacht und der Code in eine Schleife eintritt? Dies ist eine gefährliche Eventualität und Angular verhindert, dass es darauf achtet, die Eigenschaft nicht in der Zeit oder unmittelbar danach zu ändern. Dies wird erreicht, indem eine zweite Änderungserkennungsrunde nach der ersten durchgeführt wird, um sicherzustellen, dass nichts geändert wird. Achtung: Dies geschieht nur im Entwicklungsmodus.

Wenn Sie zwei Ereignisse gleichzeitig (oder in einem sehr kleinen Zeitrahmen) auslösen, wird Angular zwei Änderungserkennungszyklen zur gleichen Zeit auslösen, und es gibt in diesem Fall keine Probleme, da Angular seit der beiden Ereignisse a auslösen Change Detection Round und Angular sind intelligent genug, um zu verstehen, was passiert.

Aber nicht alle Ereignisse verursachen eine Change Detection Round , und Ihre ist ein Beispiel: Ein Observable löst keine Änderungserkennungsstrategie aus.

Was Sie tun müssen, ist zu wecken Angular, das eine Runde der Änderungserkennung auslöst. Sie können EventEmitter , ein Timeout, was auch immer verwendet, um ein Ereignis auszulösen.

Meine Lieblingslösung ist window.setTimeout :

%Vor%

Dies löst das Problem.

    
Cristian Traìna 03.11.2017 19:55
quelle
0

Um den Fehler zu verstehen, lesen Sie:

Sie fallen unter die Kategorie Synchronous event broadcasting :

  

Dieses Muster wird von diesem Plünderer illustriert. Die Anwendung ist   entworfen, um eine Kindkomponente zu haben, die ein Ereignis und ein Elternteil ausstrahlt   Komponente dieses Ereignis anhören. Das Ereignis verursacht einige der übergeordneten Elemente   zu aktualisierende Eigenschaften. Und diese Eigenschaften werden als Eingabe verwendet   Bindung für die untergeordnete Komponente . Dies ist auch ein indirektes Elternteil   Eigenschaftenaktualisierung.

In Ihrem Fall ist die Eigenschaft der übergeordneten Komponente, die aktualisiert wird, user und diese Eigenschaft wird als Eingabebindung für *ngIf="user" verwendet. Das Problem ist, dass Sie ein Ereignis this._authService.sendMessage(checkStatus) als Teil des Änderungserkennungszyklus auslösen, da Sie dies vom Lebenszyklus-Hook aus tun.

Wie in dem Artikel erläutert, gibt es zwei allgemeine Vorgehensweisen, um diesen Fehler zu umgehen:

  • Asynchrone Aktualisierung - dies ermöglicht das Auslösen eines Ereignisses außerhalb des Änderungserkennungsprozesses
  • Änderungserkennung erzwingen - Dies fügt einen weiteren Änderungserkennungslauf zwischen der aktuellen Ausführung und der Überprüfungsphase hinzu

Zuerst müssen Sie die Frage beantworten, ob Sie den even vom Lifecycle-Hook auslösen müssen. Wenn Sie alle Informationen, die Sie für die gerade in der Komponente Konstruktor benötigen, ich glaube nicht, dass die schlechte Option ist. Siehe Der wesentliche Unterschied zwischen Konstruktor und ngOnInit in Angular für weitere Details.

In Ihrem Fall würde ich wahrscheinlich mit einer asynchronen Ereignisauslösung anstelle einer manuellen Änderungserkennung gehen, um redundante Änderungserkennungszyklen zu vermeiden:

%Vor%

oder mit asynchronen Ereignisverarbeitung in der AppComponent:

%Vor%

Der oben gezeigte Ansatz wird von ngModel verwendet in der Implementierung .

Aber ich frage mich auch, wie ist this._authService.checkUserStatus() synchron?

    
AngularInDepth.com 04.11.2017 19:16
quelle
0

Ich habe kürzlich nach der Migration auf Angular 4.x das gleiche Problem festgestellt. Eine schnelle Lösung besteht darin, jeden Teil des Codes, der ChangeDetection in setTimeout(() => {}, 0) // notice the 0 it's intentional verursacht, zu umbrechen.

Auf diese Weise wird emit NACH% life-cycle hook gedrückt und verursacht daher keinen Änderungsfehler. Obwohl mir bewusst ist, dass dies eine ziemlich schmutzige Lösung ist, ist es ein praktikabler Quickfix .

    
Ante Jablan Adamović 06.11.2017 13:06
quelle