O Zoneless é um dos temas que mais gera confusão entre os novos desenvolvedores Angular hoje. Não por ser complexo, mas porque entender o que ele muda de verdade depende de ter uma boa base sobre o que veio antes. E esse ponto de partida é o zone.js.
O que é o Zone.js?
O zone.js é uma biblioteca poderosa que o Angular utiliza desde suas primeiras versões para orquestrar a detecção de mudanças (Change Detection). Ele funciona interceptando, através de uma técnica conhecida como monkey patching, as APIs assíncronas nativas do navegador. Isso inclui operações comuns do dia a dia do desenvolvedor, como o uso de setTimeout e setInterval, a adição de ouvintes de eventos com addEventListener, requisições HTTP via XMLHttpRequest ou fetch, e a resolução de Promessas.
Como ele funciona na prática?
Para ilustrar, imagine que você tem um componente com um botão que altera o valor de uma variável após um pequeno atraso.
import { Component } from "@angular/core";
@Component({
selector: "app-exemplo",
template: `
<p>Nome: {{ nome }}</p>
<button (click)="mudarNome()">Mudar Nome</button>
`,
})
export class ExemploComponent {
nome = "Angular";
mudarNome() {
setTimeout(() => {
this.nome = "Zone.js";
}, 1000);
}
}
Neste cenário, quando o setTimeout termina sua execução, o zone.js notifica o Angular de que uma operação assíncrona foi concluída. O Angular, por sua vez, assume que o estado da aplicação pode ter mudado e dispara um ciclo de detecção de mudanças em toda a árvore de componentes para atualizar a interface do usuário.
No modo Zoneless, esse mesmo exemplo precisaria de uma pequena adaptação para funcionar. Em vez de uma variável comum, usamos um Signal, que tem a capacidade de notificar o Angular diretamente, independente de onde o .set() é chamado:
import { Component, signal } from "@angular/core";
@Component({
selector: "app-exemplo",
template: `
<p>Nome: {{ nome() }}</p>
<button (click)="mudarNome()">Mudar Nome</button>
`,
})
export class ExemploComponent {
nome = signal("Angular");
mudarNome() {
setTimeout(() => {
this.nome.set("Zoneless"); // O Signal avisa o Angular diretamente
}, 1000);
}
}
A diferença é sutil, mas importante: no primeiro exemplo o Angular é avisado pelo zone.js (que monitorou o setTimeout). No segundo, quem avisa é o próprio Signal ao ter seu valor atualizado. Sem o Zone.js e sem o Signal, a interface simplesmente não seria atualizada.
O Problema com o Zone.js
Embora o zone.js facilite o desenvolvimento ao automatizar a detecção de mudanças, ele traz consigo alguns pontos negativos significativos:
-
Performance: O
zone.jsnão sabe exatamente o que mudou, apenas que algo pode ter mudado. Isso faz com que ele dispare a detecção de mudanças em toda a árvore de componentes com mais frequência do que o necessário. -
Tamanho do Bundle: O
zone.jsadiciona um peso extra ao bundle inicial (~13kb gzipped) e aumenta o tempo de inicialização. - Debug Complexo: As stack traces tornam-se longas e difíceis de ler devido às múltiplas camadas adicionadas pelo Zone.js.
-
Incompatibilidade com APIs Modernas: O
zone.jsnão consegue interceptar nativamente oasync/await, forçando o Angular CLI a converter esse código para Promessas.
O que é o Zoneless?
Diferente do que o nome pode sugerir, o Zoneless não se trata apenas de remover a biblioteca zone.js do projeto. Trata-se de uma mudança arquitetural profunda onde o Angular deixa de ser reativo a eventos globais do navegador e passa a ser reativo a notificações explícitas de mudança de estado.
No modo Zoneless, o Angular não fica mais "vigiando" todas as operações assíncronas. Em vez disso, ele atualiza a interface apenas quando recebe um sinal claro de que os dados vinculados ao template foram alterados.
Como funciona por "debaixo dos panos"?
O Zoneless introduz um novo agendador (Scheduler) que agrupa (coalesce) as notificações de mudança para evitar verificações redundantes. O Angular agora depende de gatilhos específicos e explícitos para saber quando rodar a detecção de mudanças:
- Signals: Quando um sinal (Signal) lido em um template é atualizado, ele notifica o Angular diretamente.
-
Eventos de Template: Callbacks de ouvintes de eventos vinculados no template (como um
(click)) sinalizam que uma interação ocorreu. -
AsyncPipe: O uso do
AsyncPipechama automaticamente o métodomarkForCheckquando novos dados são emitidos. -
Atualizações de Inputs: Quando um
@Input()de um componente recebe um novo valor.
Essa abordagem torna a detecção de mudanças extremamente seletiva e eficiente, devolvendo o controle da performance para as mãos do desenvolvedor.
Comparativo: Zone.js vs Zoneless
Para entender a mudança no dia a dia, vamos comparar como os dois modelos se comportam em diferentes situações e quais são suas características técnicas.
Características Gerais
| Característica | Zone.js (Tradicional) | Zoneless (Novo) |
|---|---|---|
| Gatilho de Mudança | Automático (qualquer evento assíncrono) | Explícito (Signals, Eventos, AsyncPipe) |
| Escopo da Verificação | Verifica a árvore inteira (por padrão) | Direcionado apenas aos componentes notificados |
| Bundle Size | Maior (exige a lib zone.js) |
Menor (dependência removida) |
| Async/Await | Requer conversão (downleveling) | Suporte nativo e otimizado |
Comportamento em Cenários Práticos
Abaixo, detalhamos onde o Zoneless assume o controle automaticamente e onde você precisará ser mais explícito.
Cenários onde o Zoneless DISPARA normalmente
Nestas situações, o Angular continua atualizando a interface como você já está acostumado:
- Signals: A forma nativa e recomendada; sempre disparam a atualização.
-
Eventos de DOM: Cliques, inputs e submissões vinculados no template (
(click),(input)). - Async Pipe: Dispara porque internamente chama o método de notificação necessário.
- Router & Forms: Eventos de navegação e atualizações de formulários reativos continuam funcionando.
-
@Inputs: Atualizações via bindings de template ou
ComponentRef.setInput(). -
Chamadas Explícitas: O uso de
ChangeDetectorRef.markForCheck()funciona como esperado.
Cenários onde o Zoneless NÃO dispara automaticamente
Nestes casos, a interface não será atualizada sem intervenção manual ou uso de APIs modernas:
-
Subscrições Manuais (Observables/HTTP): Se você alterar uma variável comum dentro de um
.subscribe(), o Angular não saberá que algo mudou. - Timers (setTimeout/setInterval): O fim de um timer não avisa mais o framework automaticamente.
-
Promises: A resolução de uma
Promise(via.then()ouawait) não dispara a atualização. -
Eventos Externos: WebSockets ou ouvintes de eventos globais (ex:
window.addEventListener) fora do template.
A correção para esses cenários segue sempre o mesmo raciocínio: ou você troca a variável por um Signal, ou chama markForCheck() do ChangeDetectorRef manualmente. A primeira opção é a mais recomendada:
// Não atualiza a interface no Zoneless
dados: string = '';
ngOnInit() {
this.meuServico.getData().subscribe(res => {
this.dados = res;
});
}
// Com Signal, funciona como esperado
dados = signal('');
ngOnInit() {
this.meuServico.getData().subscribe(res => {
this.dados.set(res);
});
}
Como Ativar o Zoneless
Antes de ativar, é importante entender o estágio de suporte do Zoneless de acordo com a versão do seu projeto:
- Angular v18: Introduziu o recurso em estágio experimental.
- Angular v19: O Angular Material e o CDK tornaram-se compatíveis nativamente com a arquitetura sem zonas.
- Angular v20: O modo Zoneless alcançou o estado estável, pronto para produção.
- Angular v21+: O time do Angular sinalizou que a tendência é tornar o Zoneless o padrão para novos projetos nas próximas versões.
Se você já está em uma versão compatível, o processo de migração envolve três passos simples :
-
Atualize o Bootstrap da Aplicação:
No seu arquivo
app.config.ts, adicione o provider correspondente.
import {
ApplicationConfig,
provideZonelessChangeDetection,
} from "@angular/core";
export const appConfig: ApplicationConfig = {
providers: [
provideZonelessChangeDetection(), // Ativa o modo Zoneless
],
};
Remova o Zone.js do Build:
No arquivoangular.json, remova as entradas dezone.jsda seçãopolyfills.Desinstale a Dependência:
npm uninstall zone.js
Boas Práticas
A migração mais comum é justamente mover variáveis de estado para Signals. É o passo que resolve a maior parte dos problemas e já melhora a performance mesmo antes de remover o Zone.js completamente.
Outra dica prática: se você usa muito ChangeDetectionStrategy.OnPush nos seus componentes, a transição para Zoneless vai ser tranquila o comportamento já é parecido. Se seus componentes ainda estão no modo Default, vale revisar isso antes de ativar o Zoneless.
Por fim, para eventos globais como resize ou scroll, prefira o @HostListener. Ele mantém o evento dentro do contexto do Angular, garantindo que o Zoneless seja notificado normalmente.
Conclusão
O Zoneless representa o futuro do Angular. Ele não apenas resolve problemas históricos de performance, mas também alinha o framework com as APIs modernas da web. Ao entender os cenários de disparo, você ganha total controle sobre a performance da sua aplicação, resultando em um código mais leve, rápido e previsível.
Referências
Angular v18 is now available! - Angular Blog
Angular without ZoneJS (Zoneless) - Angular Documentation
Top comments (0)