Der Ausdruck ___ hat sich geändert, nachdem er überprüft wurde

286

Warum ist die Komponente in diesem einfachen Plunk

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App {
  message:string = 'loading :(';

  ngAfterViewInit() {
    this.updateMessage();
  }

  updateMessage(){
    this.message = 'all done loading :)'
  }
}

Werfen:

AUSNAHME: Der Ausdruck 'Ich bin {{message}} in App @ 0: 5' hat sich geändert, nachdem er überprüft wurde. Vorheriger Wert: 'Ich lade :('. Aktueller Wert: 'Ich bin fertig mit Laden :)' in [Ich bin {{message}} in App @ 0: 5]

Wenn ich nur eine einfache Bindung aktualisiere, wenn meine Ansicht initiiert wird?

zog mehr
quelle
7
Der Artikel Alles, was Sie über den ExpressionChangedAfterItHasBeenCheckedErrorFehler wissen müssen, erklärt das Verhalten ausführlich.
Max Koretskyi
Erwägen Sie, Ihre zu ändern,ChangeDetectionStrategy wenn Sie detectChanges() stackoverflow.com/questions/39787038/…
Luis Limas
Stellen Sie sich vor, es gibt ein Eingabesteuerelement, und Sie füllen ihm Daten in einer Methode auf, und in derselben Methode weisen Sie ihm einen Wert zu. Der Compiler wird mit Sicherheit mit dem neuen / vorherigen Wert verwechselt. Das Binden und Auffüllen sollte also auf unterschiedliche Weise erfolgen.
MAC

Antworten:

292

Wie von drawmoore angegeben, besteht die richtige Lösung in diesem Fall darin, die Änderungserkennung für die aktuelle Komponente manuell auszulösen. Dies erfolgt mithilfe der detectChanges()Methode des ChangeDetectorRefObjekts (importiert von angular2/core) oder seiner markForCheck()Methode, mit der auch alle übergeordneten Komponenten aktualisiert werden. Relevantes Beispiel :

import { Component, ChangeDetectorRef, AfterViewInit } from 'angular2/core'

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App implements AfterViewInit {
  message: string = 'loading :(';

  constructor(private cdr: ChangeDetectorRef) {}

  ngAfterViewInit() {
    this.message = 'all done loading :)'
    this.cdr.detectChanges();
  }

}

Hier finden Sie auch Plunker, die die Ansätze ngOnInit , setTimeout und enableProdMode für alle Fälle demonstrieren .

Kiara Grouwstra
quelle
7
In meinem Fall habe ich ein Modal eröffnet. Nach dem Öffnen des Modals wurde die Meldung "Ausdruck ___ hat sich nach dem Überprüfen geändert" angezeigt. Daher wurde meine Lösung hinzugefügt. This.cdr.detectChanges (); nach dem öffnen mein modal. Vielen Dank!
Jorge Casariego
Wo ist Ihre Erklärung für die cdr-Eigenschaft? Ich würde erwarten, eine Linie wie cdr : anyoder so etwas unter der messageErklärung zu sehen. Nur besorgt, dass ich etwas verpasst habe?
CodeCabbie
1
@CodeCabbie ist es in den Konstruktorargumenten.
Slasky
1
Diese Auflösung behebt mein Problem! Vielen Dank! Sehr klarer und einfacher Weg.
Lwozniak
3
Das hat mir geholfen - mit ein paar Modifikationen. Ich musste li-Elemente formatieren, die über die ngFor-Schleife generiert wurden. Ich musste die Farbe der Bereiche innerhalb der Listenelemente basierend auf innerText ändern, indem ich auf "Sortieren" klickte, wodurch ein Bool aktualisiert wurde, der als Parameter einer Sortierpipe verwendet wurde (das sortierte Ergebnis war eine Kopie der Daten, sodass Stile nicht abgerufen wurden nur mit ngStyle aktualisiert). - Anstatt 'AfterViewInit' zu verwenden, habe ich 'AfterViewChecked' verwendet. - Ich habe auch sichergestellt, dass AfterViewChecked importiert und implementiert wird. Hinweis: Das Setzen der Pipe auf 'pure: false' funktionierte nicht. Ich musste diesen zusätzlichen Schritt hinzufügen (:
Chloe Corrigan
170

Beachten Sie zunächst, dass diese Ausnahme nur ausgelöst wird, wenn Sie Ihre App im Entwicklungsmodus ausführen (was ab Beta-0 standardmäßig der Fall ist): Wenn Sie enableProdMode()beim Booten der App aufrufen , wird sie nicht ausgelöst ( siehe) aktualisiert plunk ).

Zweitens, tu das nicht da diese Ausnahme aus gutem Grund ausgelöst wird: Kurz gesagt, im Dev-Modus folgt auf jede Änderungserkennungsrunde sofort eine zweite Runde, in der überprüft wird, ob sich seit dem Ende der ersten keine Bindungen geändert haben. Dies würde darauf hinweisen, dass Änderungen durch die Änderungserkennung selbst verursacht werden.

In Ihrem Plunk wird die Bindung {{message}}durch Ihren Aufruf an geändert setMessage(), was im ngAfterViewInitHook geschieht , der als Teil der anfänglichen Änderungserkennungsrunde auftritt. Das an sich ist jedoch nicht problematisch - das Problem ist dassetMessage() die Bindung geändert aber keine neue Runde der Änderungserkennung ausgelöst wird. Dies bedeutet, dass diese Änderung erst erkannt wird, wenn eine zukünftige Änderungsrunde an einer anderen Stelle ausgelöst wird.

Das Mitnehmen: Alles, was eine Bindung ändert, muss eine Runde der Änderungserkennung auslösen, wenn dies der Fall ist.

Aktualisieren Sie als Antwort auf alle Anfragen nach einem Beispiel dafür : Die Lösung von @ Tycho funktioniert ebenso wie die drei Methoden in der Antwort, auf die @MarkRajcok hingewiesen hat. Aber ehrlich gesagt fühlen sich alle hässlich und falsch für mich an, wie die Art von Hacks, an die wir uns in ng1 gewöhnt haben.

Zwar gibt es gelegentliche Umstände, unter denen diese Hacks angemessen sind, aber wenn Sie sie nur gelegentlich verwenden, ist dies ein Zeichen dafür, dass Sie gegen das Framework kämpfen, anstatt dessen reaktive Natur voll zu nutzen.

IMHO, eine idiomatischere "Angular2-Methode", um dies zu erreichen, ist etwas in der Art von: ( plunk )

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message | async}} </div>`
})
export class App {
  message:Subject<string> = new BehaviorSubject('loading :(');

  ngAfterViewInit() {
    this.message.next('all done loading :)')
  }
}
zog mehr
quelle
13
Warum löst setMessage () keine neue Runde der Änderungserkennung aus? Ich dachte, Angular 2 hat automatisch die Änderungserkennung ausgelöst, wenn Sie den Wert von etwas in der Benutzeroberfläche ändern.
Danny Libin
4
@drewmoore "Alles, was eine Bindung ändert, muss eine Änderungsrunde auslösen, wenn dies der Fall ist." Wie? Ist es eine gute Praxis? Sollte nicht alles in einem Durchgang erledigt werden?
Daniel Birowsky Popeski
2
@ Tycho, in der Tat. Seit ich diesen Kommentar geschrieben habe, habe ich eine andere Frage beantwortet, in der ich die drei Möglichkeiten zum Ausführen der Änderungserkennung beschreibe , einschließlich detectChanges().
Mark Rajcok
4
Nur um zu beachten, dass im aktuellen updateMessagesetMessage
Fragetext
3
@Daynil, ich hatte das gleiche Gefühl, bis ich den Blog gelesen habe, der im Kommentar unter der Frage angegeben wurde: blog.angularindepth.com/… Es erklärt, warum dies manuell durchgeführt werden muss. In diesem Fall hat die Änderungserkennung von Angular einen Lebenszyklus. Wenn sich zwischen diesen Lebenszyklen etwas ändert, muss eine erzwungene Änderungserkennung ausgeführt werden (oder ein Settimeout - das in der nächsten Ereignisschleife ausgeführt wird und die Änderungserkennung erneut auslöst).
Mahesh
50

ngAfterViewChecked() arbeitete für mich:

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

constructor(private cdr: ChangeDetectorRef) { }
ngAfterViewChecked(){
   //your code to update the model
   this.cdr.detectChanges();
}
Jaswanth Kumar
quelle
Wie bereits erwähnt, muss der zweiphasige Änderungserkennungszyklus des Winkels Änderungen erkennen, nachdem die Ansicht des Kindes geändert wurde. Ich denke, der beste Weg, dies zu tun, besteht darin, den von Angular selbst bereitgestellten Lebenszyklushaken zu verwenden und den Winkel manuell aufzufordern Erkennen Sie die Änderung und binden Sie sie. Meiner persönlichen Meinung nach scheint dies eine angemessene Antwort zu sein.
Joey587
1
Dies funktioniert für mich, um hierarchische dynamische Komponenten zusammen zu laden.
Mehdi Daustany
47

Ich habe dies behoben, indem ich ChangeDetectionStrategy aus dem Winkelkern hinzugefügt habe.

import {  Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'page1',
  templateUrl: 'page1.html',
})
Biranchi
quelle
Das hat bei mir funktioniert. Ich frage mich, was der Unterschied zwischen diesem und der Verwendung von ChangeDetectorRef
Ari
1
Hmm ... Dann wird der Modus des Änderungsdetektors zunächst auf CheckOnce( Dokumentation ) eingestellt
Alex Klaus
Ja, der Fehler / die Warnung ist weg. Aber es dauert viel länger als zuvor, wie ein Unterschied von 5-7 Sekunden, was enorm ist.
Kapil Raghuwanshi
1
@KapilRaghuwanshi Laufen Sie this.cdr.detectChanges();nach dem, was Sie laden möchten . Weil es sein könnte, dass die Änderungserkennung nicht ausgelöst wird
Harshal Carpenter
36

Können Sie nicht verwenden, ngOnInitweil Sie nur die Mitgliedsvariable ändern message?

Wenn Sie auf einen Verweis auf eine untergeordnete Komponente zugreifen möchten @ViewChild(ChildComponent), müssen Sie tatsächlich mit darauf warten ngAfterViewInit.

Eine schmutzige Lösung besteht darin, die updateMessage()in der nächsten Ereignisschleife mit z. B. setTimeout aufzurufen.

ngAfterViewInit() {
  setTimeout(() => {
    this.updateMessage();
  }, 1);
}
Jo VdB
quelle
4
Das Ändern meines Codes in die ngOnInit-Methode hat bei mir funktioniert.
Faliorn
29

Dafür habe ich die obigen Antworten ausprobiert. Viele funktionieren in der neuesten Version von Angular (6 oder höher) nicht.

Ich verwende die Materialkontrolle, für die nach der ersten Bindung Änderungen erforderlich waren.

    export class AbcClass implements OnInit, AfterContentChecked{
        constructor(private ref: ChangeDetectorRef) {}
        ngOnInit(){
            // your tasks
        }
        ngAfterContentChecked() {
            this.ref.detectChanges();
        }
    }

Wenn ich meine Antwort hinzufüge, hilft dies einigen, bestimmte Probleme zu lösen.

MarmiK
quelle
Dies hat in meinem Fall tatsächlich funktioniert, aber Sie haben einen Tippfehler. Nach der Implementierung von AfterContentChecked sollten Sie ngAfterContentChecked aufrufen, nicht ngAfterViewInit.
Tomas Lukac
Ich bin derzeit auf Version 8.2.0 :)
Tomas Lukac
1
@ TomášLukáč Danke für die Antwort, ich habe meine Antwort (y) aktualisiert
MarmiK
1
Ich empfehle diese Antwort, wenn keine der oben genannten nicht funktioniert.
Amir Choubani
afterContentChecked wird jedes Mal aufgerufen, wenn das DOM neu berechnet oder überprüft wird. Andeutung von Angular Version 9
Imran Faruqi
24

Der Artikel Alles, was Sie über den ExpressionChangedAfterItHasBeenCheckedErrorFehler wissen müssen, erklärt das Verhalten ausführlich.

Das Problem bei Ihrer Einrichtung besteht darin, dass der ngAfterViewInitLifecycle-Hook ausgeführt wird, nachdem die Änderungserkennung DOM-Aktualisierungen verarbeitet hat. Und Sie ändern effektiv die Eigenschaft, die in der Vorlage in diesem Hook verwendet wird, was bedeutet, dass DOM neu gerendert werden muss:

  ngAfterViewInit() {
    this.message = 'all done loading :)'; // needs to be rendered the DOM
  }

und dies erfordert einen weiteren Änderungserkennungszyklus, und Angular führt konstruktionsbedingt nur einen Digest-Zyklus aus.

Grundsätzlich haben Sie zwei Alternativen, um das Problem zu beheben:

  • Aktualisieren Sie die Eigenschaft asynchron entweder mit setTimeout, Promise.thenoder asynchron beobachtbar in der Vorlage referenziert

  • Führen Sie die Eigenschaftsaktualisierung in einem Hook vor der DOM-Aktualisierung durch - ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked.

Max Koretskyi
quelle
Lesen Sie Ihre Artikel: blog.angularindepth.com/… , Lesen Sie bald einen weiteren blog.angularindepth.com/… . Ich konnte immer noch keine Lösung für dieses Problem finden. Können Sie mir sagen, was passiert, wenn ich den Lifecycle-Hook ngDoCheck oder ngAfterContentChecked hinzufüge und diesen this.cdr.markForCheck () hinzufüge? (cdr für ChangeDetectorRef) drin. Ist es nicht eine richtige Möglichkeit, nach Abschluss des Lebenszyklus-Hooks und anschließender Prüfung auf Änderungen zu prüfen?
Harsimer
9

Sie müssen nur Ihre Nachricht in den rechten Lifecycle Haken aktualisieren, ist in diesem Fall ngAfterContentCheckedstatt ngAfterViewInit, weil in ngAfterViewInit eine Prüfung für die variable Nachricht gestartet wurde , aber noch nicht beendet.

Siehe: https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html#!#afterview

Der Code lautet also nur:

import { Component } from 'angular2/core'

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App {
  message: string = 'loading :(';

  ngAfterContentChecked() {
     this.message = 'all done loading :)'
  }      
}

Siehe die Arbeitsdemo zu Plunker.

RedPelle
quelle
Ich habe eine Kombination eines längenbasierten @ViewChildren()Zählers verwendet, der von einem Observable gefüllt wurde. Dies war die einzige Lösung, die für mich funktioniert hat!
Msanford
2
Die Kombination von ngAfterContentCheckedund ChangeDetectorRefvon oben hat bei mir funktioniert. Am ngAfterContentCheckedangerufenen -this.cdr.detectChanges();
Kunal Dethe
7

Dieser Fehler tritt auf, weil der vorhandene Wert unmittelbar nach der Initialisierung aktualisiert wird. Wenn Sie also einen neuen Wert aktualisieren, nachdem der vorhandene Wert in DOM gerendert wurde, funktioniert dies einwandfrei. Wie in diesem Artikel erwähnt. Angular Debugging "Ausdruck hat sich geändert, nachdem er überprüft wurde."

Zum Beispiel können Sie verwenden

ngOnInit() {
    setTimeout(() => {
      //code for your new value.
    });

}}

oder

ngAfterViewInit() {
  this.paginator.page
      .pipe(
          startWith(null),
          delay(0),
          tap(() => this.dataSource.loadLessons(...))
      ).subscribe();
}

Wie Sie sehen können, habe ich die Zeit in der setTimeout-Methode nicht erwähnt. Da es sich um eine vom Browser bereitgestellte API handelt, nicht um eine JavaScript-API, wird diese im Browser-Stack separat ausgeführt und wartet, bis die Elemente des Aufrufstapels abgeschlossen sind.

Wie die Browser-API das Konzept hervorruft, erklärt Philip Roberts in einem der Youtube-Videos (Was ist der Hack ist eine Ereignisschleife?).

Sharad Jain
quelle
4

Sie können updateMessage () auch in der ngOnInt () - Methode aufrufen, zumindest funktioniert es bei mir

ngOnInit() {
    this.updateMessage();
}

In RC1 löst dies keine Ausnahme aus

Tobias Gassmann
quelle
3

Sie können auch einen Timer mit der Observable.timerFunktion rxjs erstellen und dann die Nachricht in Ihrem Abonnement aktualisieren:                    

Observable.timer(1).subscribe(()=> this.updateMessage());
Rabinneslo
quelle
2

Es wird ein Fehler ausgegeben, da Ihr Code aktualisiert wird, wenn ngAfterViewInit () aufgerufen wird. Bedeutet, dass Ihr Anfangswert geändert wurde, als ngAfterViewInit stattfand. Wenn Sie dies in ngAfterContentInit () aufrufen, wird kein Fehler ausgegeben .

ngAfterContentInit() {
    this.updateMessage();
}
Sandip - Full Stack Entwickler
quelle
0

Ich konnte @Biranchis Beitrag nicht kommentieren, da ich nicht genug Ruf habe, aber es hat das Problem für mich behoben.

Eine Sache zu beachten! Wenn das Hinzufügen von changeDetection: ChangeDetectionStrategy.OnPush für die Komponente nicht funktioniert hat und es sich um eine untergeordnete Komponente (dumme Komponente) handelt, versuchen Sie, sie auch dem übergeordneten Element hinzuzufügen.

Dies hat den Fehler behoben, aber ich frage mich, was die Nebenwirkungen davon sind.

TKDev
quelle
0

Ich habe einen ähnlichen Fehler beim Arbeiten mit datatable erhalten. Wenn Sie * ngFor in einer anderen * ngFor-Datentabelle verwenden, wird dieser Fehler ausgelöst, wenn der Winkeländerungszyklus unterbrochen wird. Verwenden Sie also anstelle von datatable in datatable eine reguläre Tabelle oder ersetzen Sie mf.data durch den Array-Namen. Das funktioniert gut.

Priyanka Arora
quelle
0

Ich denke, eine einfachste Lösung wäre wie folgt:

  1. Führen Sie eine Implementierung durch, indem Sie einer Variablen einen Wert zuweisen, z. B. über eine Funktion oder einen Setter.
  2. Erstellen Sie eine Klassenvariable (static working: boolean)in der Klasse, in der diese Funktion vorhanden ist, und machen Sie sie bei jedem Aufruf der Funktion einfach wahr, je nachdem, was Sie möchten. Wenn der Wert der Arbeit innerhalb der Funktion wahr ist, kehren Sie einfach sofort zurück, ohne etwas zu tun. Andernfalls führen Sie die gewünschte Aufgabe aus. Stellen Sie sicher, dass Sie diese Variable nach Abschluss der Aufgabe in false ändern, dh am Ende der Codezeile oder innerhalb der Subscribe-Methode, wenn Sie mit dem Zuweisen von Werten fertig sind!
Imran Faruqi
quelle