A expressão ___ mudou após ser verificada

286

Por que o componente deste simples puxão

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

jogando:

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

quando tudo o que estou fazendo é atualizar uma ligação simples quando minha visualização é iniciada?

chamou moore
fonte
7
O artigo Tudo o que você precisa saber sobre o ExpressionChangedAfterItHasBeenCheckedErrorerro explica o comportamento em grandes detalhes.
Max Koretskyi
Considere modificar o seu ChangeDetectionStrategyao usar o detectChanges() stackoverflow.com/questions/39787038/…
Luis Limas
Basta pensar que existe um controle de entrada e você está preenchendo dados em um método e no mesmo método está atribuindo algum valor a ele. O compilador ficará confuso com certeza com o valor novo / anterior. Portanto, a ligação e o preenchimento devem ocorrer em diferentes métodos.
MAC

Respostas:

292

Conforme declarado por drewmoore, a solução adequada nesse caso é acionar manualmente a detecção de alterações para o componente atual. Isso é feito usando o detectChanges()método do ChangeDetectorRefobjeto (importado de angular2/core) ou seu markForCheck()método, que também atualiza qualquer componente pai. Exemplo relevante :

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

}

Aqui também estão Plunkers demonstrando as abordagens ngOnInit , setTimeout e enableProdMode , apenas por precaução.

Kiara Grouwstra
fonte
7
No meu caso, eu estava abrindo um modal. Após abrir o modal, ele mostrava a mensagem "A expressão ___ mudou após a verificação", então minha solução foi adicionada this.cdr.detectChanges (); depois de abrir o meu modal. Obrigado!
Jorge Casariego
Onde está sua declaração para a propriedade cdr? Eu esperaria ver uma linha assim cdr : anyou algo assim sob a messagedeclaração. Só preocupado que eu perdi alguma coisa?
precisa saber é o seguinte
1
@CodeCabbie está nos argumentos do construtor.
slasky 31/03
1
Esta resolução corrige meu problema! Muito obrigado! Maneira muito clara e simples.
Lwozniak 31/10/19
3
Isso me ajudou - com algumas modificações. Eu tive que estilizar elementos li que foram gerados por meio do loop ngFor. Eu precisava alterar a cor dos spans nos itens da lista com base no innerText ao clicar em 'sort', que atualizava um bool sendo usado como parâmetro de um pipe de classificação (o resultado classificado era uma cópia dos dados, para que os estilos não estivessem recebendo atualizado apenas com ngStyle). - Em vez de usar 'AfterViewInit', usei 'AfterViewChecked' - Também certifiquei-me de importar e implementar o AfterViewChecked. nota: configurar o pipe como 'pure: false' não estava funcionando, tive que adicionar esta etapa extra (:
Chloe Corrigan
170

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

Segundo, não faça isso porque essa exceção está sendo lançada por uma boa razão: em resumo, quando no modo dev, todas as rodadas de detecção de alterações são seguidas imediatamente por uma segunda rodada que verifica se nenhuma ligação foi alterada desde o final da primeira, pois isso indica que as alterações estão sendo causadas pela própria detecção de alterações.

Na sua conexão, a ligação {{message}}é alterada pela sua chamada para setMessage(), o que ocorre no ngAfterViewInitgancho, que ocorre como parte da curva inicial de detecção de alterações. Porém, isso por si só não é problemático - o problema é que setMessage()altera a ligação, 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 futura rodada de detecção de alterações seja acionada em outro lugar.

O ponto principal: tudo o que altera uma ligação precisa acionar uma rodada de detecção de alterações quando isso ocorre.

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

Para ter certeza, existem ocasionais circunstâncias em que estes hacks são apropriados, mas se você estiver usando-os em qualquer coisa mais do que um muito ocasional, é um sinal de que você está lutando contra a estrutura, em vez de abraçar plenamente a sua natureza reativa.

IMHO, um "Angular2 maneira" mais idiomática de abordar isso é algo ao longo das linhas de: ( 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 :)')
  }
}
chamou moore
fonte
13
Por que setMessage () não aciona uma nova rodada de detecção de alterações? Pensei que o Angular 2 disparasse automaticamente a detecção de alterações quando você altera o valor de algo na interface do usuário.
Danny Libin
4
@drewmoore "Qualquer coisa que mude uma ligação precisa disparar uma rodada de detecção de alterações quando isso acontece". Quão? É uma boa prática? Tudo não deveria ser concluído em uma única execução?
Daniel Birowsky Popeski
2
@ Tycho, de fato. Desde que escrevi esse comentário, respondi a outra pergunta, descrevendo as três maneiras de executar a detecção de alterações , que inclui detectChanges().
Mark Rajcok
4
apenas notar que, no corpo questão atual, o método chamado é chamado updateMessage, nãosetMessage
superjos
3
@ Daynil, tive o mesmo sentimento, até ler o blog dado no comentário sob a pergunta: blog.angularindepth.com/… Explica por que isso precisa ser feito manualmente. Nesse caso, a detecção de alterações angulares tem um ciclo de vida. Caso algo esteja alterando um valor entre esses ciclos de vida, uma detecção forçada de mudanças precisa ser executada (ou um tempo limite definido - que é executado no próximo loop de evento, acionando a detecção de alterações novamente).
Mahesh
50

ngAfterViewChecked() trabalhou para mim:

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

constructor(private cdr: ChangeDetectorRef) { }
ngAfterViewChecked(){
   //your code to update the model
   this.cdr.detectChanges();
}
Jaswanth Kumar
fonte
Como mencionado anteriormente, o ciclo de detecção de alterações angulares, que é de duas fases, tem que detectar alterações após a visão da criança ser modificada, e eu sinto que a melhor maneira de fazer isso é usar o gancho do ciclo de vida fornecido pelo próprio angular e pedir que o angular manualmente detectar a alteração e vinculá-la. Minha opinião pessoal é que isso parece uma resposta apropriada.
precisa saber é o seguinte
1
Este trabalho para mim, para carregar o componente dinâmico da hierarquia dentro juntos.
Mehdi Daustany 22/03
47

Corrigi isso adicionando ChangeDetectionStrategy do núcleo angular.

import {  Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'page1',
  templateUrl: 'page1.html',
})
Biranchi
fonte
Isso funcionou para mim. Pergunto-me qual é a diferença entre este e usando ChangeDetectorRef
Ari
1
Hmm ... Então o modo do detector mudança será inicialmente definido para CheckOnce( documentação )
Alex Klaus
Sim, o erro / aviso se foi. Mas está demorando muito mais tempo do que antes, como uma diferença de 5 a 7 segundos, que é enorme.
Kapil Raghuwanshi
1
@KapilRaghuwanshi Corra this.cdr.detectChanges();atrás do que estiver tentando carregar. Porque pode ser porque a detecção de alterações não está sendo desencadeada
Harshal Carpenter
36

Você não pode usar ngOnInitporque acabou de alterar a variável de membro message?

Se você deseja acessar uma referência a um componente filho @ViewChild(ChildComponent), é necessário aguardá-lo ngAfterViewInit.

Uma correção suja é chamar updateMessage()o próximo loop de evento com, por exemplo, setTimeout.

ngAfterViewInit() {
  setTimeout(() => {
    this.updateMessage();
  }, 1);
}
Jo VdB
fonte
4
Alterar meu código para o método ngOnInit funcionou para mim.
Faliorn 27/01
29

Por isso, eu tentei as respostas acima, muitas não funcionam na versão mais recente do Angular (6 ou posterior)

Estou usando o controle de material que exigiu alterações após a primeira ligação.

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

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

MarmiK
fonte
Isso realmente funcionou para o meu caso, mas você tem um erro de digitação, depois de implementar o AfterContentChecked, deve chamar ngAfterContentChecked, não ngAfterViewInit.
Tomas Lukac
Atualmente, estou na versão 8.2.0 :)
Tomas Lukac
1
@ TomášLukáč Obrigado pela resposta, eu atualizei a minha resposta (y)
MarmiK
1
Eu recomendo esta resposta se nenhuma das opções acima não funcionar.
Amir Choubani 13/03
afterContentChecked é chamado toda vez que o DOM é recalculado ou verificado novamente. Intimating from Angular versão 9
Imran Faruqi
24

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

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

  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, executa apenas um ciclo de resumo.

Você basicamente tem duas alternativas para corrigi-lo:

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

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

Max Koretskyi
fonte
Leia seus artigos: blog.angularindepth.com/… , lerá outro blog.angularindepth.com/… em breve. Ainda não foi possível descobrir a solução para este problema. você pode me dizer o que acontece se eu adicionar o gancho de ciclo de vida ngDoCheck ou ngAfterContentChecked e adicionar este this.cdr.markForCheck (); (cdr para ChangeDetectorRef) dentro dele. Não é o caminho certo para verificar alterações após o gancho do ciclo de vida e a verificação subsequente ser concluída.
Harsimer
9

Você só precisa atualizar sua mensagem no gancho do ciclo de vida correto, neste caso, é em ngAfterContentCheckedvez de ngAfterViewInit, porque em ngAfterViewInit, uma verificação da mensagem variável foi iniciada, mas ainda não foi encerrada.

consulte: 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: `<div>I'm {{message}} </div>`,
})
export class App {
  message: string = 'loading :(';

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

veja a demonstração de trabalho no Plunker.

RedPelle
fonte
Eu estava usando uma combinação de um @ViewChildren()contador de comprimento que foi preenchido por um Observable. Esta foi a única solução que funcionou para mim!
msanford
2
A combinação de ngAfterContentCheckede ChangeDetectorRefde cima funcionou para mim. Em ngAfterContentCheckedchamado -this.cdr.detectChanges();
Kunal Dethe
7

Este erro está ocorrendo porque o valor existente está sendo atualizado imediatamente após a inicialização. Portanto, se você atualizar um novo valor depois que o valor existente for renderizado no DOM, ele funcionará bem.Como mencionado neste artigo Depuração angular "A expressão foi alterada após a verificação"

por exemplo, você pode usar

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

}

ou

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

Como você pode ver, não mencionei o tempo no método setTimeout. Como é uma API fornecida pelo navegador, não uma API JavaScript, portanto, isso será executado separadamente na pilha do navegador e aguardará até que os itens da pilha de chamadas sejam concluídos.

Como o conceito de API do navegador é explicado por Philip Roberts em um dos vídeos do Youtube (o que o hack é o loop de eventos?).

sharad jain
fonte
4

Você também pode fazer 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

Tobias Gassmann
fonte
3

Você também pode criar um timer usando a Observable.timerfunção rxjs e atualizar a mensagem na sua assinatura:                    

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

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

ngAfterContentInit() {
    this.updateMessage();
}
Sandip - Desenvolvedor Full Stack
fonte
0

Não pude comentar no post de @Biranchi, já que não tenho reputação suficiente, mas isso resolveu o problema para mim.

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

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

TKDev
fonte
0

Eu recebi um erro semelhante ao trabalhar com a tabela de dados. O que acontece é quando você usa * ngFor dentro de outra * ngFor a tabela de dados lança esse erro à medida que intercepta o ciclo de alterações angulares. Portanto, em vez de usar a tabela de dados dentro da tabela de dados, use uma tabela regular ou substitua mf.data pelo nome da matriz. Isso funciona bem.

Priyanka Arora
fonte
0

Eu acho que uma solução mais simples seria a seguinte:

  1. Faça uma implementação de atribuir um valor a alguma variável, por exemplo, através da função ou setter.
  2. Crie uma variável de classe (static working: boolean)na classe em que essa função existe e toda vez que você chamar a função, simplesmente faça com que ela seja verdadeira como desejar. Dentro da função, se o valor do trabalho for verdadeiro, basta retornar imediatamente sem fazer nada. caso contrário, execute a tarefa desejada. Altere essa variável para false assim que a tarefa for concluída, ou seja, no final da linha de códigos ou dentro do método de inscrição, quando terminar de atribuir valores!
Imran Faruqi
fonte