«Expression has changed after it was checked» — por que isso acontece e como conviver com isso?
\n\nSe você já escreveu um aplicativo em Angular, certamente já se deparou com esta mensagem sinistra no console:
\n\nERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'.\n\nEste erro é um verdadeiro «flagelo» dos desenvolvedores. Milhares de perguntas no Stack Overflow, centenas de horas de depuração, dezenas de colapsos nervosos. Mas a boa notícia: ele pode não apenas ser corrigido, mas também compreendido por que ocorre. Neste artigo, analisaremos as principais causas do erro Expression has changed after it was checked, escreveremos exemplos em TypeScript e daremos soluções concretas. Vamos lá!
\n\nO que esse erro significa afinal?
\n\nO Angular funciona em dois modos: desenvolvimento (dev mode) e produção. No modo dev, o framework realiza uma verificação adicional — após cada ciclo de detecção de mudanças, ele executa uma segunda passagem para garantir que os dados não mudaram «em tempo real». Se o valor mudou entre a primeira e a segunda passagem — o Angular falha com o erro ExpressionChangedAfterItHasBeenCheckedError.
\n\nEm palavras simples: você disse ao Angular «aqui estão os dados», ele os verificou, e depois você os alterou. O Angular fica ofendido e grita.
\n\nTop 3 causas do erro
\n\n1. Alteração de dados em hooks do ciclo de vida após ngAfterViewInit
\n\nA causa mais popular — tentar alterar uma propriedade do componente no hook ngAfterViewInit, ngAfterContentInit ou ngAfterViewChecked. Neste momento, o Angular já concluiu a verificação da view, e qualquer alteração causa o erro.
Exemplo em TypeScript:
\n\nimport { Component, AfterViewInit } from '@angular/core';\n\n@Component({\n selector: 'app-example',\n template: '<p>{{ message }}</p>'\n})\nexport class ExampleComponent implements AfterViewInit {\n message = 'Valor inicial';\n\n ngAfterViewInit(): void {\n // Erro! Alterando a propriedade após a verificação da view\n this.message = 'Valor alterado';\n }\n}\n\nAqui, o Angular primeiro verifica message (valor «Valor inicial»), depois chama ngAfterViewInit, onde você o altera para «Valor alterado». Na verificação repetida, o Angular vê a discrepância e lança o erro.
2. Operações assíncronas dentro do ciclo de vida
\n\nSe você executa código assíncrono (setTimeout, Promise, Observable) dentro de um hook, a atualização dos dados ocorre fora da zona de controle do Angular.
\n\nExemplo:
\n\nimport { Component, OnInit } from '@angular/core';\n\n@Component({\n selector: 'app-async',\n template: '<p>{{ data }}</p>'\n})\nexport class AsyncComponent implements OnInit {\n data = 'Início';\n\n ngOnInit(): void {\n setTimeout(() => {\n this.data = 'Novo valor';\n }, 0);\n }\n}\n\nEmbora o setTimeout com atraso de 0 ms seja executado quase imediatamente, o Angular já concluiu a verificação. No modo dev, isso levará ao erro.
\n\n3. Componentes aninhados e passagem de dados via Input
\n\nQuando o componente pai passa dados para o filho via @Input, e o componente filho tenta alterar esses dados em seu ciclo de vida, ocorre um conflito.
Exemplo:
\n\n// Componente pai\n@Component({\n selector: 'app-parent',\n template: '<app-child [value]="parentValue"></app-child>'\n})\nexport class ParentComponent {\n parentValue = 'Original';\n}\n\n// Componente filho\n@Component({\n selector: 'app-child',\n template: '<p>{{ value }}</p>'\n})\nexport class ChildComponent implements AfterViewInit {\n @Input() value: string = '';\n\n ngAfterViewInit(): void {\n this.value = 'Alterado no