Explique-me por que continuo recebendo este erro: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.
Obviamente, eu só o obtenho no modo dev, isso não acontece na minha produção, mas é muito chato e simplesmente não entendo os benefícios de ter um erro no meu ambiente dev que não aparece no produto - Provavelmente por causa da minha falta de entendimento.
Normalmente, a correção é fácil, apenas envolvo o erro que causa o código em um setTimeout como este:
setTimeout(()=> {
this.isLoading = true;
}, 0);
Ou force a detecção de alterações com um construtor como este constructor(private cd: ChangeDetectorRef) {}
:
this.isLoading = true;
this.cd.detectChanges();
Mas por que constantemente encontro esse erro? Quero entender para evitar essas correções hacky no futuro.
angular
angular2-changedetection
angular2-databinding
Kevin LeStarge
fonte
fonte
Respostas:
Eu tive uma questão semelhante. Observando a documentação dos ganchos do ciclo de vida , mudei
ngAfterViewInit
parangAfterContentInit
e funcionou.fonte
setTimeout
como você mostrou acima, fez o truque. Obrigado!ngAfterContentChecked
funciona aqui enquantongAfterContentInit
ainda gera erro.Este erro indica um problema real no seu aplicativo, portanto, faz sentido lançar uma exceção.
Na
devMode
detecção de alterações, é adicionada uma volta adicional após cada execução regular de detecção de alterações para verificar se o modelo foi alterado.Se o modelo mudou entre o turno regular e o turno adicional de detecção de alterações, isso indica que
que são ruins, porque não está claro como proceder, pois o modelo pode nunca se estabilizar.
Se as execuções angulares alterarem a detecção até o modelo estabilizar, ele poderá ser executado para sempre. Se o Angular não executar a detecção de alterações, a visualização poderá não refletir o estado atual do modelo.
Consulte também Qual é a diferença entre o modo de produção e desenvolvimento no Angular2?
fonte
ngOnInit
oungOnChanges
para modificar o modelo (alguns retornos de chamada do ciclo de vida permitem modificar o modelo que outros não, não me lembro exatamente de qual deles faz ou não). Não vincule a métodos ou funções na exibição; em vez disso, vincule a campos e atualize os campos nos manipuladores de eventos. Se você deve ligar-se aos métodos, verifique se eles sempre retornam a mesma instância de valor, desde que não haja realmente uma alteração. A detecção de alterações chamará muito esses métodos.changeRef.detectChanges()
é uma solução / suprime o erro, é uma prova disso. É como modificando dentro estado$scope.$watch()
em angular 1.cdRef.detectChanges()
é necessário apenas em alguns casos estranhos e você deve olhar atentamente quando precisar, para entender corretamente o porquê.Muita compreensão veio depois que eu entendi os Ganchos do Ciclo de Vida Angular e sua relação com a detecção de alterações.
Eu estava tentando fazer com que o Angular atualizasse um sinalizador global vinculado ao
*ngIf
de um elemento e estava tentando alterar esse sinalizador dentro dongOnInit()
gancho do ciclo de vida de outro componente.De acordo com a documentação, esse método é chamado depois que o Angular já detectou alterações:
Portanto, atualizar o sinalizador dentro de
ngOnChanges()
não iniciará a detecção de alterações. Então, quando a detecção de alterações for acionada novamente, o valor do sinalizador será alterado e o erro será gerado.No meu caso, mudei isso:
Para isso:
e resolveu o problema :)
fonte
router.navigate
) após o carregamento para um fragmento, se presente no URL. Esse código foi inicialmente colocado noAfterViewInit
local em que eu estava recebendo o erro, depois mudei como você diz para o construtor, mas não respeitava o fragmento. Movendo-se parangOnInit
resolvido :) obrigado!setInterval()
também funciona se precisar disparar após outro código de evento vitalício.Atualizar
Eu recomendo começar primeiro com a auto-resposta do OP : pense corretamente no que pode ser feito no
constructor
vs, no que deve ser feitongOnChanges()
.Original
Esta é mais uma observação do que uma resposta, mas pode ajudar alguém. Eu me deparei com esse problema ao tentar fazer a presença de um botão depender do estado do formulário:
Até onde eu sei, essa sintaxe leva o botão a ser adicionado e removido do DOM com base na condição. O que por sua vez leva ao
ExpressionChangedAfterItHasBeenCheckedError
.A correção no meu caso (embora eu não pretenda entender todas as implicações da diferença), era usar
display: none
:fonte
[hidden]
vez da parte muito detalhada[style.display]
. :)[hidden]
não vai ter sempre o mesmo comportamento comodisplay: none
Havia respostas interessantes, mas não achei uma que atendesse às minhas necessidades, a mais próxima de @ chittrang-mishra, que se refere apenas a uma função específica e não a várias alternâncias, como no meu aplicativo.
Eu não queria usar
[hidden]
a vantagem de*ngIf
nem fazer parte do DOM, então encontrei a solução a seguir, que pode não ser a melhor para todos, pois suprime o erro em vez de corrigi-lo, mas no meu caso, onde eu conheço o resultado final está correto, parece ok para o meu aplicativo.O que eu fiz foi implementar
AfterViewChecked
, adicionarconstructor(private changeDetector : ChangeDetectorRef ) {}
e depoisEspero que isso ajude outros, como muitos outros me ajudaram.
fonte
O Angular executa a detecção de alterações e, quando descobrir que alguns valores que foram passados para o componente filho foram alterados, o Angular lança o erro:
ExpressionChangedAfterItHasBeenCheckedError
clique para maisPara corrigir isso, podemos usar o
AfterContentChecked
gancho do ciclo de vida efonte
No meu caso, tive esse problema no meu arquivo de especificação, enquanto executava meus testes.
Eu tive que mudar
ngIf
para[hidden]
para
fonte
[hidden]
: talkingdotnet.com/dont-use-hidden-attribute-angularjs-2*ngIf
altera o DOM, adicionando e removendo o elemento da página, enquanto[hidden]
altera a visibilidade do item, sem removê-lo do DOM.Siga os passos abaixo:
1. Use 'ChangeDetectorRef' importando-o de @ angular / core da seguinte maneira:
2. Implemente-o no construtor () da seguinte maneira:
3. Adicione à sua função o seguinte método que você está chamando em um evento, como o clique do botão. Então fica assim:
fonte
Eu estava usando ng2-carouselamos (Angular 8 e Bootstrap 4)
Abaixo corrigi meu problema:
O que eu fiz:
fonte
Eu estava enfrentando o mesmo problema que o valor estava mudando em uma das matrizes no meu componente. Mas, em vez de detectar as alterações na alteração de valor, mudei a estratégia de detecção de alteração de componente para
onPush
(que detectará alterações na alteração de objeto e não na alteração de valor).fonte
Referindo-se ao artigo https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4
Portanto, a mecânica por trás da detecção de alterações realmente funciona de maneira que os resumos de detecção e verificação de alterações sejam executados de forma síncrona. Isso significa que, se atualizarmos as propriedades de forma assíncrona, os valores não serão atualizados quando o loop de verificação estiver em execução e não obteremos
ExpressionChanged...
erros. A razão pela qual obtemos esse erro é que, durante o processo de verificação, o Angular vê valores diferentes dos registrados na fase de detecção de alterações. Então, para evitar isso ....1) Use changeDetectorRef
2) use setTimeOut. Isso executará seu código em outra VM como uma tarefa macro. A Angular não verá essas alterações durante o processo de verificação e você não receberá esse erro.
3) Se você realmente deseja executar seu código na mesma VM, use como
Isso criará uma micro-tarefa. A fila de micro-tarefas é processada após a conclusão do código síncrono atual, portanto, a atualização da propriedade ocorrerá após a etapa de verificação.
fonte
@HostBinding
pode ser uma fonte confusa desse erro.Por exemplo, digamos que você tenha a seguinte ligação de host em um componente
Para simplificar, digamos que essa propriedade seja atualizada através da seguinte propriedade de entrada:
No componente pai, você o está definindo programaticamente
ngAfterViewInit
Aqui está o que acontece:
carousel
(via ViewChild)carousel
aténgAfterViewInit()
(será nulo)style_groupBG = 'red'
background: red
o componente ImageCarousel do hostcarousel.style.background
e não é inteligente o suficiente para saber que isso não é um problema, e lança a exceção.Uma solução é introduzir outro ImageCarousel, um div de wrapper, e definir a cor do plano de fundo, mas você não obtém alguns dos benefícios de usar
HostBinding
(como permitir que o pai controle os limites completos do objeto).A melhor solução, no componente pai, é adicionar detectChanges () depois de definir a configuração.
Isso pode parecer bastante óbvio assim, e muito semelhante a outras respostas, mas há uma diferença sutil.
Considere o caso em que você não adiciona
@HostBinding
até mais tarde durante o desenvolvimento. De repente, você recebe esse erro e parece não fazer sentido.fonte
Aqui estão os meus pensamentos sobre o que está acontecendo. Não li a documentação, mas tenho certeza de que isso faz parte do motivo pelo qual o erro é mostrado.
Ao usar * ngIf, ele altera fisicamente o DOM adicionando ou removendo o elemento toda vez que a condição é alterada. Portanto, se a condição mudar antes de ser renderizada na visualização (o que é altamente possível no mundo do Angular), o erro será gerado. Veja a explicação aqui entre os modos de desenvolvimento e produção.
Ao usá-
[hidden]
lo, não altera fisicamente o,DOM
mas apenas o ocultaelement
da vista, provavelmente usandoCSS
na parte de trás. O elemento ainda está lá no DOM, mas não é visível, dependendo do valor da condição. É por isso que o erro não ocorrerá ao usar[hidden]
.fonte
isProcessing()
está fazendo a coisa certa, você deve usar!isProcessing()
para o[hidden]
hidden
não "usa CSS nas costas", é uma propriedade HTML comum. developer.mozilla.org/pt-BR/docs/Web/HTML/Global_attributes/…Para o meu problema, eu estava lendo o github - "ExpressionChangedAfterItHasBeenCheckedError ao alterar o valor 'não modelo' de um componente no afterViewInit" e decidi adicionar o ngModel
Corrigiu o meu problema, espero que ajude alguém.
fonte
ngModel
. E você poderia explicar por que isso deve ser útil?Dicas de depuração
Este erro pode ser bastante confuso e é fácil fazer uma suposição errada sobre exatamente quando está ocorrendo. Acho útil adicionar muitas instruções de depuração como essa nos componentes afetados nos locais apropriados. Isso ajuda a entender o fluxo.
No pai, coloque instruções como esta (a string exata 'EXPRESSIONCHANGED' é importante), mas, além disso, são apenas exemplos:
Nos retornos de chamada filho / serviços / timer:
Se você executar
detectChanges
manualmente, adicione o log para isso também:Em seguida, no depurador do Chrome, basta filtrar por 'EXPRESSIONCHANGES'. Isso mostrará exatamente o fluxo e a ordem de tudo o que é definido, e também exatamente em que ponto o Angular lança o erro.
Você também pode clicar nos links cinza para inserir pontos de interrupção.
Outra coisa a se observar se você tiver nomeado propriedades semelhantes em todo o aplicativo (como
style.background
), verifique se está depurando o que você pensa - configurando-o para um valor de cor obscuro.fonte
No meu caso, eu tinha uma propriedade assíncrona em
LoadingService
com um BehavioralSubjectisLoading
O uso do modelo [oculto] funciona, mas * ngIf falha
fonte
Uma solução que funcionou para mim usando rxjs
fonte
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.
aconteceu quando as alterações de valor são acionadas quando o DOM mudadelay
fazem o erro desaparecer. Funciona de maneira semelhante asetTimeout
.Eu tive esse tipo de erro no Ionic3 (que usa o Angular 4 como parte de sua pilha de tecnologia).
Para mim, estava fazendo isso:
<ion-icon [name]="getFavIconName()"></ion-icon>
Então, eu estava tentando alterar condicionalmente o tipo de ícone de íon de a
pin
para aremove-circle
, de acordo com o modo em que a tela estava operando.Acho que vou ter que adicionar um
*ngIf
.fonte
Meu problema foi manifesto quando adicionei,
*ngIf
mas essa não foi a causa. O erro foi causado pela alteração do modelo nas{{}}
tags e, em seguida, pela tentativa de exibir o modelo alterado na*ngIf
instrução. Aqui está um exemplo:Para corrigir o problema, mudei de local
changeMyModelValue()
para onde fazia mais sentido.Na minha situação, eu queria
changeMyModelValue()
ligar sempre que um componente filho alterava os dados. Isso exigiu que eu criasse e emitisse um evento no componente filho para que o pai pudesse lidar com ele (chamandochangeMyModelValue()
. Consulte https://angular.io/guide/component-interaction#parent-listens-for-child-eventfonte
Espero que isso ajude alguém vindo aqui: Fazemos chamadas de serviço
ngOnInit
da seguinte maneira e usamos uma variáveldisplayMain
para controlar a montagem dos elementos no DOM.component.ts
e component.html
fonte
Eu recebi esse erro porque estava usando uma variável em component.html que não foi declarada em component.ts. Depois que eu removi a peça em HTML, esse erro se foi.
fonte
Eu recebi esse erro porque estava despachando ações de redux no modal e o modal não foi aberto naquele momento. Eu estava despachando ações no momento em que o componente modal recebe entrada. Então, coloquei setTimeout lá para garantir que o modal seja aberto e as ações sejam corrigidas.
fonte
Para quem está lutando com isso. Aqui está uma maneira de depurar corretamente esse erro: https://blog.angular-university.io/angular-debugging/
No meu caso, na verdade, me livrei desse erro usando esse hack [oculto] em vez de * ngIf ...
Mas o link que eu forneci me permitiu encontrar THE GUILTY * ngIf :)
Aproveitar.
fonte
hidden
vez dengIf
não é um hack, nem aborda o núcleo do problema. Você está apenas escondendo o problema.A solução ... services e rxjs ... emissores de eventos e associação de propriedades usam o rxjs .. é melhor implementá-lo por conta própria, mais controle e mais fácil de depurar. Lembre-se de que os emissores de eventos estão usando rxjs. Simplesmente, crie um serviço e, dentro de um observável, faça com que cada componente assine o observador e passe novo valor ou valor de consumo, conforme necessário
fonte