L’espressione ___ è cambiata dopo che è stata verificata

Perché è il componente in questo semplice plunk

@Component({ selector: 'my-app', template: `
I'm {{message}}
`, }) export class App { message:string = 'loading :('; ngAfterViewInit() { this.updateMessage(); } updateMessage(){ this.message = 'all done loading :)' } }

lancio:

ECCEZIONE: l’espressione ‘I’m {{message}} in App @ 0: 5’ è cambiata dopo che è stata selezionata. Valore precedente: ‘Sto caricando :(‘. Valore corrente: ‘Ho finito di caricare :)’ in [I’m {{message}} in App @ 0: 5]

quando tutto ciò che sto facendo è aggiornare una semplice associazione quando viene avviata la mia visualizzazione?

Come affermato da Drewmoore, la soluzione corretta in questo caso è di triggersre manualmente il rilevamento delle modifiche per il componente corrente. Questo viene fatto usando il metodo detectChanges() dell’object ChangeDetectorRef (importato da angular2/core ), o il suo metodo markForCheck() , che aggiorna anche i componenti principali. Esempio pertinente:

 import { Component, ChangeDetectorRef } from 'angular2/core' @Component({ selector: 'my-app', template: `
I'm {{message}}
`, }) export class App { message: string = 'loading :('; constructor(private cdr: ChangeDetectorRef) {} ngAfterViewInit() { this.message = 'all done loading :)' this.cdr.detectChanges(); } }

Qui ci sono anche Plunkers che dimostrano gli approcci ngOnInit , setTimeout e enableProdMode per ogni evenienza.

Innanzitutto, tieni presente che questa eccezione verrà generata solo quando enableProdMode() l’app in modalità dev (che è il caso per impostazione predefinita come beta-0): se chiami enableProdMode() quando enableProdMode() bootstrap dell’app, non verrà gettato ( vedi plunk aggiornato ).

In secondo luogo, non farlo perché questa eccezione viene lanciata per una buona ragione: in breve, quando in modalità dev, ogni round di rilevamento del cambiamento viene seguito immediatamente da un secondo round che verifica che nessun binding sia cambiato dalla fine del primo, in quanto ciò indicherebbe che i cambiamenti sono stati causati dal rilevamento del cambiamento stesso.

Nel tuo plunk, il binding {{message}} è cambiato dalla tua chiamata a setMessage() , che avviene nel hook ngAfterViewInit , che si verifica come parte del turno iniziale di rilevamento delle modifiche. Questo di per sé non è problematico – il problema è che setMessage() cambia l’associazione ma non innesca un nuovo round di rilevamento delle modifiche, il che significa che questa modifica non verrà rilevata fino a quando non verrà triggersto qualche round di rilevamento del cambiamento da qualche altra parte .

Il take-away: tutto ciò che cambia un legame deve triggersre un round di rilevamento dei cambiamenti quando lo fa.

Aggiornamento in risposta a tutte le richieste per un esempio su come farlo : @ La soluzione di Tycho funziona, così come i tre metodi nella risposta segnalata da @MarkRajcok. Ma francamente mi sembrano tutti brutti e sbagliati, come il tipo di hack che ci siamo abituati a appoggiare in ng1.

Certo, ci sono circostanze occasionali in cui questi hack sono appropriati, ma se li stai usando su qualcosa di più di una base occasionale, è un segno che stai combattendo il quadro piuttosto che abbracciare completamente la sua natura retriggers.

IMHO, un modo più idiomatico, “Angular2” di avvicinarsi a questo è qualcosa sulla falsariga di: ( plunk )

 @Component({ selector: 'my-app', template: `
I'm {{message | async}}
` }) export class App { message:Subject = new BehaviorSubject('loading :('); ngAfterViewInit() { this.message.next('all done loading :)') } }

Ho risolto questo problema aggiungendo ChangeDetectionStrategy dal core angular.

 import { Component, ChangeDetectionStrategy } from '@angular/core'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'page1', templateUrl: 'page1.html', }) 

Non puoi usare ngOnInit perché stai semplicemente cambiando il message variabile membro?

Se si desidera accedere a un riferimento a un componente figlio @ViewChild(ChildComponent) , è effettivamente necessario attendere con ngAfterViewInit .

Una soluzione sporca consiste nel chiamare l’ updateMessage() nel prossimo ciclo degli eventi con ad esempio setTimeout.

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

L’articolo Tutto ciò che devi sapere sull’errore ExpressionChangedAfterItHasBeenCheckedError spiega il comportamento in grande dettaglio.

Il problema con la configurazione è che l’ ngAfterViewInit ciclo di vita ngAfterViewInit viene eseguito dopo gli aggiornamenti DOM elaborati dal rilevamento modifiche. E stai effettivamente cambiando la proprietà usata nel modello in questo hook, il che significa che il DOM deve essere nuovamente sottoposto a rendering:

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

e questo richiederà un altro ciclo di rilevamento delle modifiche e Angular by design esegue solo un ciclo di digestione.

In pratica hai due alternative su come risolverlo:

  • aggiornare la proprietà in modo asincrono utilizzando setTimeout , Promise.then o osservabile asincrono a cui si fa riferimento nel modello

  • eseguire l’aggiornamento delle proprietà in un hook prima dell’aggiornamento DOM – ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked.

ngAfterViewChecked () ha funzionato per me:

 import { Component, ChangeDetectorRef } from '@angular/core'; //import ChangeDetectorRef constructor(private cdr: ChangeDetectorRef) { } ngAfterViewChecked(){ //your code to update the model this.cdr.detectChanges(); } 

Devi semplicemente aggiornare il tuo messaggio nel hook del ciclo di vita di destra, in questo caso ngAfterContentChecked invece di ngAfterViewInit , perché in ngAfterViewInit è stato avviato un controllo per il messaggio variabile ma non ancora terminato.

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

quindi il codice sarà solo:

 import { Component } from 'angular2/core' @Component({ selector: 'my-app', template: `
I'm {{message}}
`, }) export class App { message: string = 'loading :('; ngAfterContentChecked() { this.message = 'all done loading :)' } }

guarda la demo di lavoro su Plunker.

Puoi anche mettere la tua chiamata a updateMessage () nel metodo ngOnInt (), almeno funziona per me

 ngOnInit() { this.updateMessage(); } 

In RC1 questo non fa scattare l’eccezione

Viene generato un errore perché il codice viene aggiornato quando viene chiamato ngAfterViewInit () . Significa che il tuo valore iniziale è cambiato quando ngAfterViewInit ha luogo, Se lo chiami in ngAfterContentInit (), allora non genererà un errore.

 ngAfterContentInit() { this.updateMessage(); } 

Puoi anche creare un timer usando rxjs Observable.timer e quindi aggiornare il messaggio nella tua iscrizione =>

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

Ho già provato questa soluzione: (ha funzionato!)

  export class BComponent { name = 'I am B component'; @Input() text; constructor(private parent: AppComponent) {} ngOnInit() { setTimeout(() => { this.parent.text = 'updated text'; }); } ngAfterViewInit() { setTimeout(() => { this.parent.name = 'updated name'; }); } } 

Puoi leggere maggiori dettagli qui: https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterhamschedcheckederror-error-e3fd9ce7dbb4

Non ho potuto commentare il post di @Biranchi dal momento che non ho abbastanza reputazione, ma ha risolto il problema per me.

Una cosa da notare! Se aggiungi changeDetection: ChangeDetectionStrategy.OnPush sul componente non ha funzionato, ed è un componente figlio (componente dumb) prova ad aggiungerlo anche al genitore.

Questo ha risolto il bug, ma mi chiedo quali sono gli effetti collaterali di questo.

Per questo ho provato sopra le risposte molte non funzionano nell’ultima versione di Angular (6 o successive)

Sto usando il controllo Materiale che richiedeva modifiche dopo il primo binding.

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

Aggiungendo la mia risposta, questo aiuta a risolvere un problema specifico.