---
title: "NG0100 ExpressionChangedAfterItHasBeenCheckedError: por que ele aparece no Angular zoneless"
description: "Por que o NG0100 ExpressionChangedAfterItHasBeenCheckedError aparece depois de ir pra zoneless ou Angular 22, o que ele significa agora (um binding atualizado sem notificar o Angular), a armadilha do fixture.detectChanges() no teste, e os fixes conforme de onde veio a mudança."
deck: "`NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked` é o erro de dev-mode que todo dev Angular já viu. Depois de ir pra [zoneless](https://angular.dev/guide/zoneless) ou pro OnPush default do Angular 22, ele aparece em código e em teste que nunca tinham estourado. A causa mudou: em zoneless ele costuma dizer que um binding mudou sem notificar o Angular, que é o contrato do zoneless, e não um problema de timing de lifecycle que você empurra pro `ngOnInit`."
author: "André Ramos"
url: "https://andreramos.dev/pt/angular/expressionchanged-error-zoneless-angular/"
lang: "pt-BR"
type: "article"
---

# NG0100 ExpressionChangedAfterItHasBeenCheckedError: por que ele aparece no Angular zoneless

> `NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked` é o erro de dev-mode que todo dev Angular já viu. Depois de ir pra [zoneless](https://angular.dev/guide/zoneless) ou pro OnPush default do Angular 22, ele aparece em código e em teste que nunca tinham estourado. A causa mudou: em zoneless ele costuma dizer que um binding mudou sem notificar o Angular, que é o contrato do zoneless, e não um problema de timing de lifecycle que você empurra pro `ngOnInit`.

## O erro, na íntegra

A mensagem é `NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'X'. Current value: 'Y'.` Ele só estoura em desenvolvimento. Depois de cada rodada de change detection, o Angular faz uma segunda passada pra conferir que nada mudou, e se um binding agora lê diferente do que ele acabou de setar, ele lança. A checagem existe pra pegar uma view num estado inconsistente.

As causas clássicas são conversa velha: um getter ou método que devolve um valor novo a cada chamada, um componente filho escrevendo num binding do pai, um valor setado no `ngAfterViewInit` depois que o pai já tinha sido checado. Se fosse esse o seu caso, o fix seria o de manual, mover a escrita pro `ngOnInit` ou pro `ngAfterContentInit`, e você não estaria lendo isto. O que mudou foi onde o erro aparece agora.

## Por que ele está aparecendo agora

Os times relatam uma onda de NG0100 logo depois de subir pro Angular 21, inclusive em teste unitário que estava verde no dia anterior, e o Angular 22 aumenta o raio ao [tornar o OnPush o default](/pt/angular/onpush-default-angular-22) de change detection. Se você não mexeu no componente mas o erro surgiu, o regime de change detection embaixo dele mudou.

Zoneless e OnPush são mais rígidos que o default com Zone.js. Com o Zone, uma passada ampla de change detection atualizava os bindings mesmo sem ninguém pedir explicitamente. Sem o Zone, um binding só atualiza quando algo notifica o Angular: uma escrita de signal, uma mudança de input, o async pipe ou o `markForCheck`. A passada de verificação do dev-mode agora também pega os bindings que o scheduler não teria atualizado, que é justamente o código que dependia do Zone sem ninguém perceber.

## O que o NG0100 quer dizer em zoneless

Num app com Zone.js, o NG0100 costumava ser um problema de timing: duas passadas, valores diferentes, conserta o lifecycle hook. Em zoneless ele carrega mais informação. O Angular lança quando um binding foi atualizado sem notificação, sem escrita de signal, sem `markForCheck`, sem async pipe. É a mesma classe de bug que aparece quando [um app vai pra zoneless](/pt/angular/code-that-breaks-when-angular-goes-zoneless): o valor mudou, mas nada avisou o Angular pra agendar a atualização.

Então leia o NG0100 em zoneless como um aviso adiantado, não como amolação. Em desenvolvimento ele estoura; em produção, sem a checagem de dev-mode, o mesmo binding simplesmente renderizaria desatualizado e você ia caçar fantasma. O erro está te fazendo um favor ao nomear o binding antes de ir pro ar.

| De onde vem a mudança | Por que o zoneless lança NG0100 | Fix |
|---|---|---|
| Um timer ou callback muta um campo | O campo mudou, nada agendou a atualização | Guarde num signal, ou chame markForCheck |
| Um getter devolve valor novo a cada chamada | As duas passadas leem valores diferentes | Memoize, ou mova pra um computed signal |
| Um filho escreve num input do pai num hook | O pai já tinha sido checado nesta passada | Escreva antes, ou modele como signal que o pai lê |
| O status de um reactive form controla a view | statusChanges emitiu, a leitura no template não foi agendada | Faça a ponte do statusChanges pra um signal |

## A armadilha do teste: fixture.detectChanges()

O lugar novo mais comum de bater no NG0100 é um teste. Em zoneless, chamar `fixture.detectChanges()` depois de uma mutação estoura ele, porque o `TestBed` exige que o componente seja compatível com OnPush e acusa um binding atualizado sem notificação. O hábito de chamar `detectChanges()` depois de toda mudança virou o gatilho, não o conserto, e ele vinha escondendo a notificação que faltava esse tempo todo.

Pare de forçar change detection. Conduza o componente como a produção conduz, escreva por um canal de verdade e espere a estabilidade, pra o teste exercitar o mesmo caminho de notificação que o app usa. Um teste que passa só porque você chamou `detectChanges()` é um teste que deixaria passar um bug de view desatualizada.

*Teste zoneless: conduza pela notificação, não force a detecção*

```ts
// Antes: força CD, estoura NG0100 em zoneless, esconde a notificação que falta
component.label = 'atualizado';
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('atualizado');

// Depois: escreva pelo canal de verdade (um signal), aí espere a estabilidade
component.label.set('atualizado');
await fixture.whenStable();
expect(fixture.nativeElement.textContent).toContain('atualizado');
```

## O fix quase nunca é silenciar

O movimento errado é espalhar `ChangeDetectorRef.detectChanges()` ou embrulhar a atribuição em `setTimeout` até o erro calar. Isso esconde a inconsistência que a checagem achou, e em zoneless esconde uma view que vai renderizar desatualizada em produção. Silenciar o NG0100 troca um erro barulhento de dev-mode por um silencioso em produção.

O fix certo nomeia onde o valor mudou e roteia por algo que o Angular observa. Um campo que um timer muta vira um signal. Um getter que recalcula vira um `computed`. Um callback de terceiro que atualiza a UI ganha um `markForCheck` na borda, ou um adapter que escreve um signal. Os fixes clássicos de lifecycle hook ainda valem quando a causa é mesmo timing; os casos de zoneless precisam de uma notificação, não de um segundo `detectChanges`.

*Fix de produção: uma notificação, não um erro silenciado*

```ts
// Estoura NG0100 em zoneless: o campo muda, nada notifica o Angular
ngOnInit() {
  setInterval(() => { this.elapsed = this.elapsed + 1; }, 1000);
}

// Resolvido: um signal é uma notificação; a atualização do template é agendada
readonly elapsed = signal(0);
ngOnInit() {
  setInterval(() => this.elapsed.update((n) => n + 1), 1000);
}
```

## Artefato reaproveitável: Depurando o NG0100 em zoneless

- Leia os valores Previous e Current na mensagem; eles nomeiam o binding que mudou.
- Pergunte o que atualizou esse binding, e se algo notificou o Angular (signal, markForCheck, async pipe).
- Mutação de campo por timer, callback ou terceiro: mova pra um signal, ou markForCheck na borda.
- Um getter que devolve valor novo a cada chamada: memoize ou use um computed signal.
- No teste, pare de chamar fixture.detectChanges() depois de mutação; escreva por canais de verdade e espere a estabilidade.
- Não silencie com detectChanges ou setTimeout; em zoneless isso esconde uma view que fica desatualizada em produção.

## Perguntas frequentes

### O que é o NG0100 ExpressionChangedAfterItHasBeenCheckedError?

Um erro só de desenvolvimento que o Angular lança quando um binding de template muda de valor depois que o change detection já rodou. Em dev mode o Angular faz uma segunda passada de verificação; se um binding agora lê diferente do que acabou de setar, ele lança pra acusar uma view inconsistente.

### Por que o NG0100 começou depois que subi pro Angular 21 ou 22?

Zoneless (o default em projeto novo desde a v21) e o [OnPush como default na v22](/pt/angular/onpush-default-angular-22) são mais rígidos que o default com Zone.js. Um binding que o Zone atualizava de qualquer jeito agora só atualiza quando algo notifica o Angular, e a checagem de dev-mode pega os que dependiam do Zone sem ninguém perceber.

### Por que o fixture.detectChanges() estoura NG0100 em teste zoneless?

Em zoneless, o `TestBed` exige que o componente seja compatível com OnPush. Chamar `fixture.detectChanges()` depois de uma mutação acusa um binding atualizado sem notificação. Escreva por canais de verdade (um signal, um evento) e espere a estabilidade, em vez de forçar a detecção.

### O NG0100 é um problema de produção?

O erro em si é só de desenvolvimento. Mas em zoneless ele costuma apontar um binding que mudou sem notificar o Angular, o que em produção renderizaria desatualizado sem erro nenhum. Consertar tira um bug de verdade, não só um aviso.

## Fontes consultadas

- https://angular.dev/errors/NG0100
- https://angular.dev/guide/zoneless
- https://angular.dev/guide/testing
- https://angular.dev/guide/components/lifecycle
- https://github.com/angular/angular/issues/59082
- https://github.com/angular/angular-cli/issues/32047
- https://blog.angular.dev/announcing-angular-v21-57946c34f14b

## Leia também

- [O código que quebra quando Angular fica zoneless](https://andreramos.dev/pt/angular/code-that-breaks-when-angular-goes-zoneless/)
- [OnPush é o default no Angular 22: o que quebra e o que fazer](https://andreramos.dev/pt/angular/onpush-default-angular-22/)
- [Um checklist curto antes de testar zoneless](https://andreramos.dev/pt/angular/zoneless-checklist/)
