---
title: "OnPush é o default no Angular 22: o que quebra e o que fazer"
description: "Por que o Angular 22 faz do OnPush o default, o que de fato quebra (estado mutado sem avisar o Angular), os dois jeitos de corrigir, e como os `Eager` que a migração adicionou servem de backlog de limpeza."
deck: "O [Angular 22](https://angular.dev/events/v22) virou o default de change detection para OnPush. Um componente sem estratégia `changeDetection` explícita agora é OnPush, não eager, e o `ng update` escreve `ChangeDetectionStrategy.Eager` nos seus componentes existentes pra mantê-los funcionando. Isso preserva o comportamento, mas também te entrega uma lista precisa dos componentes que vale arrumar."
author: "André Ramos"
url: "https://andreramos.dev/pt/angular/onpush-default-angular-22/"
lang: "pt-BR"
type: "article"
---

# OnPush é o default no Angular 22: o que quebra e o que fazer

> O [Angular 22](https://angular.dev/events/v22) virou o default de change detection para OnPush. Um componente sem estratégia `changeDetection` explícita agora é OnPush, não eager, e o `ng update` escreve `ChangeDetectionStrategy.Eager` nos seus componentes existentes pra mantê-los funcionando. Isso preserva o comportamento, mas também te entrega uma lista precisa dos componentes que vale arrumar.

## O que mudou, em uma linha

Antes do v22, um componente que nunca setou uma estratégia `changeDetection` rodava eager: o Angular o checava a cada tick. No Angular 22 esse mesmo componente é OnPush, que só re-renderiza quando uma referência de input muda, um evento dispara dentro dele, um async pipe emite, ou um signal que ele lê é escrito.

O `ng update` não quebra seu app. Ele adiciona `changeDetection: ChangeDetectionStrategy.Eager` nos seus componentes existentes pra nada mudar em runtime. Componentes novos, porém, começam OnPush, e esse é o default certo. O trabalho interessante é decidir quais dos componentes migrados precisavam mesmo da checagem eager, e quais só nunca tinham sido marcados como OnPush porque ninguém parou pra mexer.

## O que de fato quebra

Tire o `Eager` que a migração pôs de um componente e a falha tem sempre o mesmo formato: o dado mudou, mas a view não. O OnPush não checa a cada tick, então qualquer estado que você muta sem avisar o Angular fica invisível.

Os suspeitos de sempre são mutação no lugar (dar push num array ou setar uma propriedade num objeto que o template lê), um valor setado a partir de um callback que o Angular não conhece (um widget de terceiros, um `addEventListener` cru, um `setTimeout` que escreve um campo), e um pai que muta um objeto de `@Input` em vez de passar uma referência nova. Nenhum deles marca a view como suja, então o OnPush nunca re-renderiza.

*Por que isso para de renderizar no OnPush*

```ts
@Component({ selector: 'app-cart', template: '{{ items.length }} items' })
export class CartComponent {
  items: Item[] = [];

  // Mutar no lugar NÃO marca a view OnPush como suja: a contagem fica velha.
  add(item: Item) {
    this.items.push(item);
  }
}
```

## Os dois jeitos de fazer certo

Existe a saída de emergência e a correção. A saída é o `Eager` que a migração já escreveu: mantém o componente checando a cada tick, o que serve de paliativo mas é o comportamento que você está tentando deixar pra trás. A correção é dar ao componente um motivo de re-renderizar que o OnPush entenda.

A correção mais limpa é signals, porque escrever um signal marca a view OnPush como suja pra você. Onde signals ainda não encaixam, uma referência nova num `@Input` funciona, e em último caso você injeta `ChangeDetectorRef` e chama `markForCheck()` depois da mudança. Use o `markForCheck` sabendo que ele é sinal de que aquele estado provavelmente deveria ser um signal.

| A causa | A correção OnPush-limpa |
|---|---|
| Mutar um array ou objeto no lugar | Escreva um `signal` (ou atribua uma referência nova) |
| Um valor setado de um callback não-Angular | Mova o valor pra um `signal`, ou `markForCheck()` |
| Um pai mutando um objeto de `@Input` | Passe uma referência nova do pai |
| Você precisa mesmo de checagem a cada tick | Mantenha `ChangeDetectionStrategy.Eager`, de propósito |

*O mesmo componente, OnPush-limpo*

```ts
@Component({ selector: 'app-cart', template: '{{ items().length }} items' })
export class CartComponent {
  readonly items = signal<Item[]>([]);

  // Uma escrita de signal marca a view OnPush como suja. A contagem atualiza.
  add(item: Item) {
    this.items.update((list) => [...list, item]);
  }
}
```

## Como achar os componentes, inclusive em testes

Você não precisa caçar. Todo `ChangeDetectionStrategy.Eager` que a migração adicionou é um componente que rodava eager, e cada um é candidato a virar OnPush de verdade. Trabalhe essa lista por custo: os baratos viram signals, os que sustentam muita coisa ficam com `Eager` até você ter tempo, e você apaga a linha quando o componente está limpo.

Os testes mostram o mesmo formato. Um componente OnPush não renderiza só porque o tempo passou ou o estado mudou; você chama `fixture.detectChanges()` depois da mudança pra ver. É a mesma disciplina que os [testes de tempo no Vitest](/pt/angular/angular-vitest-async-validator-pending) pedem, e o mesmo tipo de dependência escondida com que um app tropeça quando vira [totalmente zoneless](/pt/angular/code-that-breaks-when-angular-goes-zoneless).

## O que eu faria

Deixe os componentes novos serem OnPush, porque o default do v22 é o certo. Trate os `Eager` que a migração adicionou como backlog, não como veredito: converta os fáceis pra signals conforme você toca no código, e deixe o resto com `Eager` até haver motivo pra voltar. Não há campanha aqui, só um default que finalmente aponta pro lado certo.

Isso é uma fatia do [upgrade pro Angular 22](/pt/angular/angular-22-what-changed). O default OnPush é a mudança que mexe com mais componentes, e ela recompensa o mesmo hábito que o resto do Angular moderno: deixar as mudanças de estado visíveis pro framework em vez de torcer pra um tick pegar.

## Artefato reaproveitável: Deixando um componente OnPush-limpo

- Ache as linhas `ChangeDetectionStrategy.Eager` que o `ng update` adicionou: esse é seu backlog.
- Troque mutação no lugar por uma escrita de `signal` ou uma referência nova.
- Pra valores setados de callbacks não-Angular, mova pra um `signal` ou chame `markForCheck()`.
- Garanta que os pais passem uma referência nova em vez de mutar um objeto de `@Input`.
- Em testes, chame `fixture.detectChanges()` depois de uma mudança pra a view OnPush renderizar.
- Apague a linha `Eager` quando o componente re-renderiza sem ela; mantenha só onde a checagem a cada tick é mesmo necessária.

## Perguntas frequentes

### Por que meu componente Angular parou de atualizar depois de subir pro v22?

Porque OnPush é o default de change detection no Angular 22. Um componente sem estratégia `changeDetection` explícita agora só re-renderiza numa mudança de referência de input, num evento, numa emissão de async pipe, ou numa escrita de signal. Se você muta estado no lugar ou o seta de um callback que o Angular não conhece, a view não atualiza. Mova o estado pra um `signal`, passe uma referência nova, ou chame `markForCheck()`.

### O que significa ChangeDetectionStrategy.Eager no Angular 22?

É o comportamento default antigo: o componente é checado a cada tick de change detection. Como o v22 faz do OnPush o default, o `ng update` adiciona `ChangeDetectionStrategy.Eager` nos seus componentes existentes pra o comportamento não mudar. É um paliativo seguro, e cada um é candidato a virar OnPush.

### Preciso migrar todos os componentes pra OnPush?

Não. A migração os mantém funcionando marcando como `Eager`. Você os converte no seu ritmo, geralmente movendo o estado mutado pra signals, e mantém `Eager` nos poucos componentes que precisam mesmo de checagem a cada tick.

### Por que meu componente OnPush não atualiza num teste?

Um componente OnPush re-renderiza só quando algo marca a view como suja. Num teste, chame `fixture.detectChanges()` depois da mudança pra a view refletir. Avançar o tempo ou setar estado sozinho não renderiza um componente OnPush.

## Fontes consultadas

- https://angular.dev/events/v22
- https://angular.dev/best-practices/runtime-performance
- https://angular.dev/guide/zoneless
- https://angular.dev/guide/signals
- https://blog.ninja-squad.com/2026/06/03/what-is-new-angular-22.0

## Leia também

- [Angular 22: o que mudou de verdade, e o que fazer num app em produção](https://andreramos.dev/pt/angular/angular-22-what-changed/)
- [O código que quebra quando Angular fica zoneless](https://andreramos.dev/pt/angular/code-that-breaks-when-angular-goes-zoneless/)
- [O que adotar, testar ou deixar para depois no Angular 21](https://andreramos.dev/pt/angular/adopt-spike-wait-angular-21/)
