DEV Community

vandrei de lima
vandrei de lima

Posted on

Entenda o Zoneless do Angular

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Performance: O zone.js nã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.
  2. Tamanho do Bundle: O zone.js adiciona um peso extra ao bundle inicial (~13kb gzipped) e aumenta o tempo de inicialização.
  3. Debug Complexo: As stack traces tornam-se longas e difíceis de ler devido às múltiplas camadas adicionadas pelo Zone.js.
  4. Incompatibilidade com APIs Modernas: O zone.js não consegue interceptar nativamente o async/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 AsyncPipe chama automaticamente o método markForCheck quando 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() ou await) 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);
  });
}
Enter fullscreen mode Exit fullscreen mode

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 :

  1. 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
  ],
};
Enter fullscreen mode Exit fullscreen mode
  1. Remova o Zone.js do Build:
    No arquivo angular.json, remova as entradas de zone.js da seção polyfills.

  2. Desinstale a Dependência:

   npm uninstall zone.js
Enter fullscreen mode Exit fullscreen mode

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)