A expressão ___ foi alterada depois de ter sido verificada

Por que o componente neste simples plunk

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

jogando:

EXCEÇÃO: A expressão ‘Eu sou {{message}} no aplicativo @ 0: 5’ foi alterada após a verificação. Valor anterior: ‘Estou carregando :(‘. Valor atual: ‘Estou pronto para carregar :)’ em [I’m {{message}} in App @ 0: 5]

quando tudo o que estou fazendo é atualizar uma binding simples quando minha visão é iniciada?

Como afirmado por drewmoore, a solução adequada nesse caso é ativar manualmente a detecção de alterações para o componente atual. Isso é feito usando o método detectChanges() do object ChangeDetectorRef (importado de angular2/core ) ou seu método markForCheck() , que também faz com que qualquer componente pai seja atualizado. Exemplo relevante:

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

Aqui também estão os Plunkers demonstrando as abordagens ngOnInit , setTimeout e enableProdMode apenas no caso.

Primeiro, observe que essa exceção só será lançada quando você estiver executando o aplicativo no modo dev (que é o caso, por padrão, da versão beta-0): Se você chamar enableProdMode() ao inicializar o aplicativo, ele não será jogado ( veja plunk atualizado ).

Segundo, não faça isso porque esta exceção está sendo lançada por um bom motivo: Em resumo, quando no modo dev, cada rodada de detecção de mudança é seguida imediatamente por um segundo turno que verifica que nenhuma binding foi alterada desde o final do primeiro. como isso indicaria que as mudanças estão sendo causadas pela própria detecção de mudança.

Em seu plunk, a binding {{message}} é alterada pela sua chamada para setMessage() , o que acontece no hook ngAfterViewInit , que ocorre como parte do turno inicial de detecção de mudança. Isso em si não é problemático – o problema é que setMessage() altera a binding, mas não aciona uma nova rodada de detecção de alterações, o que significa que essa alteração não será detectada até que alguma rodada futura de detecção de alterações seja acionada em outro lugar .

O takeaway: Qualquer coisa que altere uma binding precisa acionar uma rodada de detecção de alterações quando isso acontecer.

Atualize em resposta a todos os pedidos de um exemplo de como fazer isso : a solução do @Thok funciona, assim como os três methods na resposta @MarkRajcok apontou. Mas, francamente, todos eles se sentem feios e errados comigo, como o tipo de hacks que nos acostumamos a nos apoiar em ng1.

Para ter certeza, há circunstâncias ocasionais em que esses hacks são apropriados, mas se você os usa em algo mais do que ocasionalmente, é um sinal de que você está lutando contra a estrutura em vez de abraçar completamente sua natureza reativa.

IMHO, um mais idiomático, “Angular2 maneira” de abordar isso é algo nos moldes de: ( 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 :)') } }

Eu consertei isso adicionando ChangeDetectionStrategy do núcleo angular.

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

Você não pode usar o ngOnInit porque está apenas alterando a message variável de membro?

Se você quiser acessar uma referência a um componente filho @ViewChild(ChildComponent) , você realmente precisa esperar por ele com ngAfterViewInit .

Uma solução updateMessage() é chamar o updateMessage() no próximo loop de events, por exemplo, setTimeout.

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

O artigo Tudo o que você precisa saber sobre o erro ExpressionChangedAfterItHasBeenCheckedError explica o comportamento em grandes detalhes.

O problema com a sua configuração é que o gancho do ciclo de vida ngAfterViewInit é executado após a detecção de alterações processar as atualizações do DOM. E você está efetivamente alterando a propriedade que é usada no modelo neste hook, o que significa que o DOM precisa ser re-renderizado:

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

e isso exigirá outro ciclo de detecção de alterações e o Angular por design executará apenas um ciclo de digestão.

Você basicamente tem duas alternativas como consertar:

  • atualizar a propriedade de forma assíncrona usando setTimeout , Promise.then ou assíncrona observável referenciada no modelo

  • execute a atualização da propriedade em um gancho antes da atualização do DOM – ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked.

ngAfterViewChecked () funcionou para mim:

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

Você só precisa atualizar sua mensagem no gancho certo do ciclo de vida, neste caso, é ngAfterContentChecked vez de ngAfterViewInit , porque em ngAfterViewInit uma verificação para a mensagem variável foi iniciada, mas ainda não terminou.

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

então o código será apenas:

 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 :)' } }

Veja a demonstração de trabalho no Plunker.

Você também pode colocar sua chamada para updateMessage () no método ngOnInt (), pelo menos funciona para mim

 ngOnInit() { this.updateMessage(); } 

No RC1 isso não aciona a exceção

Ele lança um erro porque seu código é atualizado quando ngAfterViewInit () é chamado. Significa que seu valor inicial foi alterado quando ngAfterViewInit ocorrer. Se você chamar isso em ngAfterContentInit () , ele não causará erro.

 ngAfterContentInit() { this.updateMessage(); } 

Você também pode criar um timer usando o rxjs Observable.timer e atualizar a mensagem em sua assinatura =>

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

Eu já tentei esta solução: (funcionou!)

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

Você pode ler mais detalhes aqui: https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterit hasbeencheckederror – error – e3fd9ce7dbb4

Eu não podia comentar no post @Biranchi desde que eu não tenho reputação suficiente, mas resolveu o problema para mim.

Uma coisa a notar! Se adicionar changeDetection: ChangeDetectionStrategy.OnPush no componente não funcionou, e é um componente filho (componente idiota) tente adicioná-lo ao pai também.

Isso corrigiu o bug, mas eu me pergunto quais são os efeitos colaterais disso.

Para isso eu tentei acima respostas muitos não funciona na última versão do Angular (6 ou posterior)

Eu estou usando controle de material que exigiu alterações após a primeira binding feita.

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

Adicionando minha resposta, isso ajuda a resolver alguns problemas específicos.