it-swarm.com.de

Der Ausdruck ___ hat sich nach der Überprüfung geändert

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 {{Nachricht}} in App @ 0: 5" hat sich geändert, nachdem er geprüft wurde. Vorheriger Wert: "Ich lade :(". Aktueller Wert: "Ich bin fertig mit dem Laden :)" in [Ich {{Nachricht}} in App @ 0: 5]

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

183
drewmoore

Beachten Sie zunächst, dass diese Ausnahme nur ausgelöst wird, wenn Sie Ihre App im Entwicklungsmodus ausführen (dies ist ab Beta-0 standardmäßig der Fall): Wenn Sie enableProdMode() aufrufen = wenn die App gebootet wird, wird sie nicht geworfen ( siehe aktualisierten Plunk ).

Zweitens, tun Sie das nicht , da diese Ausnahme aus einem guten Grund ausgelöst wird: Kurz gesagt, im Dev-Modus wird jede Runde der Änderungserkennung befolgt sofort durch eine zweite Runde, in der überprüft wird, dass sich seit dem Ende der ersten keine Bindungen geändert haben, da dies darauf hinweist, dass Änderungen durch die Änderungserkennung selbst verursacht werden.

In Ihrem Plunk wird die Bindung {{message}} Durch Ihren Aufruf von setMessage() geändert. Dies geschieht im Hook ngAfterViewInit, der als Teil der ersten Änderungserkennungsrunde auftritt. Das ist an sich kein Problem - das Problem ist, dass setMessage() die Bindung ändert, aber keine neue Runde der Änderungserkennung auslöst, was bedeutet, dass diese Änderung erst in einer zukünftigen Runde der Änderungserkennung erkannt wird wird woanders ausgelöst.

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

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

Natürlich gibt es gelegentlich Umstände, unter denen diese Hacks angemessen sind, aber wenn Sie sie nicht nur für sehr gelegentlich verwenden, ist es eine Anzeichen dafür, dass Sie gegen das Framework kämpfen, anstatt dessen reaktiven Charakter voll zu nutzen.

IMHO, ein idiomatischerer "Angular2-Weg", 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 :)')
  }
}
141
awqueous

Wie von drewmoore festgestellt, besteht die richtige Lösung in diesem Fall darin, die Änderungserkennung für die aktuelle Komponente manuell auszulösen. Dies geschieht mit der detectChanges()-Methode des ChangeDetectorRef-Objekts (importiert aus angular2/core) oder seiner markForCheck()-Methode, wodurch auch alle übergeordneten Komponenten aktualisiert werden. Relevantes Beispiel :

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

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

  constructor(private cdr: ChangeDetectorRef) {}

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

}

Hier stellen auch Plunkers die Ansätze ngOnInit , setTimeout und enableProdMode vor, nur für den Fall.

195
Tycho

Ich habe das Problem 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',
})
35
Biranchi

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

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

Ein fehlerhafter Fix ist das Aufrufen von updateMessage() in der nächsten Ereignisschleife mit z. setTimeout.

ngAfterViewInit() {
  setTimeout(() => {
    this.updateMessage();
  }, 1);
}
25
Jo VdB

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();
}
21
Jaswanth Kumar

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

Das Problem bei Ihrem Setup besteht darin, dass ngAfterViewInit Lifecycle Hook ausgeführt wird, nachdem DOM-Aktualisierungen bei der Änderungserkennung durchgeführt wurden. Und Sie ändern effektiv die Eigenschaft, die in der Vorlage in diesem Hook verwendet wird. Dies bedeutet, dass DOM erneut gerendert werden muss:

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

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

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

  • aktualisieren Sie die Eigenschaft asynchron, indem Sie entweder setTimeout, Promise.then oder asynchrones Observable verwenden, auf das in der Vorlage verwiesen wird

  • führen Sie das Eigenschaftsupdate vor dem DOM-Update in einem Hook durch - ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked.

Sie müssen lediglich Ihre Nachricht im richtigen Lebenszyklus-Hook aktualisieren, in diesem Fall ist ngAfterContentChecked statt ngAfterViewInit, da in ngAfterViewInit eine Prüfung für die Variablennachricht gestartet wurde, aber noch nicht beendet ist.

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

also wird der code nur sein:

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 auf Plunker.

6
RedPelle

Sie können Ihren Aufruf zur Aktualisierung von Message () auch in die ngOnInit () - Methode stellen, zumindest funktioniert es für mich

ngOnInit() {
    this.updateMessage();
}

In RC1 löst dies keine Ausnahme aus

4
Tobias Gassmann

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

Ich verwende Materialkontrolle, die nach der ersten Bindung geändert werden musste.

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

Wenn ich meine Antwort so hinzufüge, hilft das einigen, ein bestimmtes Problem zu lösen.

4
MarmiK

Es wird ein Fehler ausgegeben, da der Code aktualisiert wird, wenn ngAfterViewInit () aufgerufen wird. Bedeutet, dass sich Ihr Anfangswert geändert hat, wenn ngAfterViewInit stattfindet. Wenn Sie das in ngAfterContentInit () aufrufen, wird kein Fehler ausgegeben.

ngAfterContentInit() {
    this.updateMessage();
}

Ich konnte den Beitrag von @Biranchi nicht kommentieren, da ich nicht genug Ruf habe, aber das Problem wurde für mich behoben.

Beachten Sie Folgendes:. Wenn Sie changeDetection hinzufügen: ChangeDetectionStrategy.OnPush für die Komponente hat nicht funktioniert, und es ist eine untergeordnete Komponente (dumme Komponente), die es auch dem übergeordneten Element hinzufügt. 

Dies hat den Fehler behoben, aber ich frage mich, welche Nebenwirkungen dies haben.

0
TKDev

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

Observable.timer(1).subscribe(()=> this.updateMessage());
0
rabinneslo