Angular 19 Zoneless: Performance e Change Detection Sem Zone.js em 2026

Guia completo sobre Angular zoneless change detection: como funciona o provideZonelessChangeDetection, migracao de Zone.js para signals, armadilhas com setTimeout e formularios reativos, SSR sem Zone.js, benchmarks de performance e o roadmap do Angular 19 ao 21.

Diagrama de arquitetura Angular zoneless change detection sem Zone.js com signals e performance otimizada

A remocao do Zone.js representa a maior mudanca arquitetural no sistema de change detection do Angular desde a introducao do framework. Desde o Angular 2, o Zone.js atuava como uma camada invisivel que interceptava todas as operacoes assincronas do navegador — de cliques e timers a requisicoes HTTP — para notificar o framework sobre potenciais alteracoes de estado. Embora essa abordagem garantisse que a interface estivesse sempre atualizada, o custo em termos de performance e complexidade de depuracao era significativo. Com o Angular 19 introduzindo provideExperimentalZonelessChangeDetection e o Angular 20 estabilizando a API como provideZonelessChangeDetection, os desenvolvedores ganham controle total sobre quando e como o Angular detecta mudancas na interface.

O modo zoneless substitui o modelo de interceptacao global por um sistema baseado em notificacoes explicitas via Signals. Em vez de executar ciclos de change detection apos cada callback assincrono, o Angular passa a reagir somente quando um signal utilizado em um template e atualizado. O resultado e uma reducao drastica no numero de verificacoes desnecessarias, bundles menores e stack traces limpos sem frames adicionais do Zone.js.

Cronograma de estabilizacao

O Angular 19 disponibiliza o modo zoneless como experimental via provideExperimentalZonelessChangeDetection(). No Angular 20, a API e promovida a estavel como provideZonelessChangeDetection(). O Angular 21 tera a opcao de gerar novos projetos sem Zone.js por padrao via CLI.

Como funciona o change detection com Zone.js

Para compreender o impacto do modo zoneless, e fundamental entender o papel do Zone.js no modelo tradicional. O Zone.js aplica monkey patches em mais de 130 APIs do navegador — setTimeout, setInterval, addEventListener, Promise.then, XMLHttpRequest, fetch e muitas outras. Cada vez que uma dessas funcoes e executada, o Zone.js intercepta o callback e notifica o Angular de que algo potencialmente mudou no estado da aplicacao.

Essa notificacao dispara um ciclo completo de change detection. O Angular percorre toda a arvore de componentes, comparando os valores atuais com os anteriores para identificar o que precisa ser re-renderizado. Em aplicacoes com centenas de componentes, esse processo pode envolver milhares de verificacoes por interacao do usuario, mesmo quando apenas um unico valor foi alterado.

app.config.ts - Traditional Zone.js setup (Angular 18-19)typescript
import { ApplicationConfig } from '@angular/core';
import { provideZoneChangeDetection } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    // Zone.js patches ~130+ browser APIs
    // Every async callback triggers change detection
  ]
};

A opcao eventCoalescing: true mitiga parcialmente o problema ao agrupar multiplos eventos em um unico ciclo de change detection. Porem, o overhead fundamental permanece: o Zone.js adiciona aproximadamente 33 KB ao bundle (10 KB gzipped), insere frames adicionais em todas as stack traces e executa verificacoes globais independentemente de onde a mudanca ocorreu.

Em cenarios de alta frequencia — como animacoes, scroll handlers ou inputs de formularios — o modelo Zone.js gera centenas de ciclos de change detection por segundo, muitos deles completamente desnecessarios. Esse e o problema central que o modo zoneless resolve.

Habilitando o modo zoneless no Angular 19 e 20

A transicao para o modo zoneless envolve duas etapas: substituir o provider de change detection e remover o pacote zone.js do projeto.

No Angular 19, a API e marcada como experimental:

app.config.ts - Angular 19 (experimental)typescript
import { ApplicationConfig } from '@angular/core';
import { provideExperimentalZonelessChangeDetection } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    provideExperimentalZonelessChangeDetection(),
    // No more Zone.js patching
  ]
};

A partir do Angular 20, a API e estabilizada e o prefixo experimental e removido:

app.config.ts - Angular 20+ (stable)typescript
import { ApplicationConfig } from '@angular/core';
import { provideZonelessChangeDetection } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    provideZonelessChangeDetection(),
  ]
};

Apos configurar o provider, o Zone.js deve ser removido do projeto. O polyfill precisa ser excluido tanto dos targets de build quanto de teste no angular.json, e o pacote desinstalado:

bash
# Remove zone.js polyfill from angular.json build and test targets
# Then uninstall the package
npm uninstall zone.js

Com essas alteracoes, o Angular deixa de interceptar operacoes assincronas e passa a depender exclusivamente de notificacoes explicitas para disparar atualizacoes na interface.

O que dispara o change detection no modo zoneless

Sem o Zone.js interceptando callbacks assincronos, o Angular precisa de mecanismos explicitos para saber quando atualizar a interface. No modo zoneless, os seguintes eventos disparam change detection:

  • Atualizacoes de signals utilizados em templates
  • Eventos do template (click, input, submit) via event bindings do Angular
  • markForCheck() chamado explicitamente via ChangeDetectorRef
  • Operacoes assincronas marcadas com AsyncPipe que emitem novos valores
  • ChangeDetectorRef.detectChanges() para forcamento local

O mecanismo principal e o sistema de Signals. Quando um signal referenciado em um template e atualizado, o Angular identifica exatamente quais componentes precisam ser re-renderizados, sem percorrer a arvore inteira:

counter.component.ts - Signal-driven change detectiontypescript
import { Component, signal, computed } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: `
    <div class="counter">
      <button (click)="decrement()">-</button>
      <span>{{ count() }}</span>
      <button (click)="increment()">+</button>
      <p>Double: {{ doubled() }}</p>
    </div>
  `
})
export class CounterComponent {
  // Signal updates automatically notify the template
  count = signal(0);
  doubled = computed(() => this.count() * 2);

  increment() {
    this.count.update(v => v + 1);
    // No markForCheck() needed - signal handles notification
  }

  decrement() {
    this.count.update(v => v - 1);
  }
}

Nesse exemplo, ao clicar no botao, o evento de click dispara a funcao increment(), que atualiza o signal count. O Angular detecta automaticamente que o template referencia count() e doubled(), re-renderizando apenas as partes afetadas. Nao ha varredura global na arvore de componentes — a atualizacao e precisa e cirurgica.

OnPush e modo zoneless

Componentes com changeDetection: ChangeDetectionStrategy.OnPush ja operam de forma semelhante ao modelo zoneless, reagindo apenas a mudancas em inputs e chamadas explicitas de markForCheck(). No modo zoneless, o OnPush continua funcional, mas os Signals oferecem uma abordagem mais ergonomica e performatica por eliminarem a necessidade de markForCheck() manual.

Armadilhas da migracao: setTimeout, formularios reativos e padroes legados

A principal fonte de problemas na migracao para o modo zoneless sao componentes que dependem implicitamente do Zone.js para disparar atualizacoes na interface. O padrao mais comum envolve o uso de setTimeout ou setInterval com atribuicoes diretas a propriedades do componente.

No modelo com Zone.js, o seguinte codigo funciona corretamente porque o Zone.js intercepta o callback do setTimeout e dispara change detection:

user-status.component.ts - Before: relies on Zone.jstypescript
@Component({
  selector: 'app-user-status',
  template: `<span>{{ statusMessage }}</span>`
})
export class UserStatusComponent {
  statusMessage = 'Loading...';

  ngOnInit() {
    setTimeout(() => {
      // Zone.js would trigger CD here - zoneless does NOT
      this.statusMessage = 'Ready';
    }, 2000);
  }
}

No modo zoneless, esse componente permanece exibindo "Loading..." indefinidamente. O setTimeout executa normalmente, a propriedade statusMessage e atualizada em memoria, mas o Angular nao recebe nenhuma notificacao sobre a mudanca. A solucao e migrar para signals:

user-status.component.ts - After: signal-based approachtypescript
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-user-status',
  template: `<span>{{ statusMessage() }}</span>`
})
export class UserStatusComponent {
  statusMessage = signal('Loading...');

  ngOnInit() {
    setTimeout(() => {
      // Signal update notifies Angular automatically
      this.statusMessage.set('Ready');
    }, 2000);
  }
}

A chamada this.statusMessage.set('Ready') notifica o Angular de que o valor mudou, disparando a atualizacao do template mesmo dentro de um callback de setTimeout.

Outro cenario frequente envolve formularios reativos. O FormControl do Angular emite valores via observables, que no modelo Zone.js disparam change detection automaticamente. No modo zoneless, a atualizacao da interface depende da conversao do observable para signal utilizando toSignal():

search.component.ts - Reactive forms with zonelesstypescript
import { Component, inject, signal } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { toSignal } from '@angular/core/rxjs-interop';
import { debounceTime, distinctUntilChanged } from 'rxjs';

@Component({
  selector: 'app-search',
  imports: [ReactiveFormsModule],
  template: `
    <input [formControl]="searchControl" placeholder="Search..." />
    <p>Results for: {{ searchTerm() }}</p>
  `
})
export class SearchComponent {
  searchControl = new FormControl('');

  // Convert observable to signal for automatic template updates
  searchTerm = toSignal(
    this.searchControl.valueChanges.pipe(
      debounceTime(300),
      distinctUntilChanged()
    ),
    { initialValue: '' }
  );
}

A funcao toSignal() do pacote @angular/core/rxjs-interop converte qualquer observable em um signal, criando a ponte entre o mundo reativo do RxJS e o sistema de notificacoes do modo zoneless. Essa abordagem e especialmente relevante para formularios complexos que utilizam operadores como debounceTime e distinctUntilChanged.

Pronto para mandar bem nas entrevistas de Angular?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

SSR sem Zone.js: PendingTasks e estabilizacao

O Server-Side Rendering apresenta um desafio adicional no modo zoneless. Com o Zone.js, o Angular detectava automaticamente quando todas as operacoes assincronas haviam sido concluidas para serializar o HTML e envia-lo ao cliente. Sem o Zone.js, essa deteccao automatica deixa de existir.

O Angular resolve esse problema com a API PendingTasks. Essa classe permite registrar tarefas assincronas que devem ser concluidas antes da serializacao do SSR. O metodo run() aceita uma funcao assincrona e impede a serializacao ate que a Promise retornada seja resolvida:

data-loader.component.ts - SSR-compatible async loadingtypescript
import { Component, inject, signal } from '@angular/core';
import { PendingTasks } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';

@Component({
  selector: 'app-data-loader',
  template: `
    @if (data()) {
      <div>{{ data()!.title }}</div>
    } @else {
      <div>Loading...</div>
    }
  `
})
export class DataLoaderComponent {
  private http = inject(HttpClient);
  private pendingTasks = inject(PendingTasks);
  data = signal<{ title: string } | null>(null);

  ngOnInit() {
    // PendingTasks.run() prevents SSR serialization until complete
    this.pendingTasks.run(async () => {
      const result = await firstValueFrom(
        this.http.get<{ title: string }>('/api/data')
      );
      this.data.set(result);
    });
  }
}

Sem o PendingTasks.run(), o servidor serializaria o HTML antes do carregamento dos dados, resultando em um flash de "Loading..." que seria substituido pelo conteudo real apos a hidratacao no cliente. Com o PendingTasks, o servidor aguarda a conclusao da requisicao HTTP e serializa o HTML com os dados ja preenchidos, proporcionando uma experiencia de carregamento suave e otimizada para SEO.

Para aplicacoes que utilizam o HttpClient do Angular, as requisicoes feitas durante o SSR ja sao rastreadas automaticamente quando se utiliza provideClientHydration(). O PendingTasks e necessario para operacoes assincronas customizadas que nao passam pelo HttpClient.

Benchmarks de performance: Zone.js vs Zoneless

Os ganhos de performance do modo zoneless sao mensuráveis em múltiplas dimensoes. A tabela a seguir apresenta dados comparativos entre uma aplicacao Angular de medio porte operando com Zone.js e a mesma aplicacao migrada para o modo zoneless:

| Metrica | Zone.js | Zoneless | Melhoria | |---------|---------|----------|----------| | Tamanho do bundle inicial | +33KB bruto / +10KB gzip | 0KB de overhead | 100% de reducao | | Ciclos de change detection (app tipica) | 150-300 por interacao | 5-15 por interacao | 80-95% menos | | Profundidade da stack trace | 8-12 frames extras do Zone | Traces nativos limpos | Clareza imediata | | Time to Interactive (TTI) | Baseline | 15-25% mais rapido | Bootstrap do Zone eliminado |

A reducao de 80-95% nos ciclos de change detection e o ganho mais impactante para a experiencia do usuario. Em uma aplicacao com formularios complexos e atualizacoes frequentes, o modelo Zone.js dispara centenas de verificacoes por segundo, enquanto o modo zoneless reage apenas as mudancas efetivas nos signals referenciados pelos templates.

A eliminacao dos frames do Zone.js nas stack traces e um beneficio significativo para a produtividade dos desenvolvedores. No modelo tradicional, cada erro apresenta 8 a 12 frames adicionais do Zone.js que obscurecem a origem real do problema. No modo zoneless, as stack traces sao limpas e apontam diretamente para o codigo da aplicacao.

Compatibilidade com bibliotecas de terceiros

Algumas bibliotecas de terceiros dependem do Zone.js para disparar atualizacoes na interface. Antes de migrar para o modo zoneless, e recomendavel verificar a compatibilidade das dependencias do projeto. Bibliotecas que utilizam NgZone.run() ou dependem implicitamente da interceptacao do Zone.js podem exigir adaptacoes. O Angular Material e o CDK ja sao totalmente compativeis com o modo zoneless desde a versao 19.

APIs do NgZone que sobrevivem ao modo zoneless

Mesmo com a remocao do Zone.js, a classe NgZone continua disponivel no Angular. No modo zoneless, o NgZone opera como um no-op — suas funcoes existem mas nao executam nenhuma logica de zona. Essa decisao de design garante retrocompatibilidade com codigo que referencia o NgZone diretamente.

As APIs relevantes incluem:

  • NgZone.run(): No modo zoneless, executa o callback diretamente sem nenhum wrapper de zona. Codigo existente que utiliza NgZone.run() para forcar atualizacoes deve migrar para ChangeDetectorRef.detectChanges() ou, preferencialmente, para signals.
  • NgZone.runOutsideAngular(): No modelo Zone.js, essa funcao era usada para executar operacoes de alta frequencia (animacoes, scroll handlers) sem disparar change detection. No modo zoneless, essa funcao e desnecessaria, pois operacoes assincronas ja nao disparam change detection automaticamente.
  • NgZone.isStable: No modo zoneless, retorna true constantemente, pois nao ha tarefas de zona para rastrear.

A presenca dessas APIs como no-ops permite que bibliotecas que referenciam o NgZone continuem funcionando sem alteracoes imediatas, facilitando a transicao gradual do ecossistema.

Roadmap do Angular 19 ao 21: cronograma de estabilizacao

A remocao do Zone.js e um processo gradual que se estende por tres versoes principais do Angular:

  • Angular 19 (atual): O provideExperimentalZonelessChangeDetection() esta disponivel para adocao antecipada. O Zone.js permanece como padrao para novos projetos. Todas as APIs de signals estao estaveis.
  • Angular 20: A API e promovida a estavel como provideZonelessChangeDetection(). O prefixo experimental e removido. Novos projetos gerados pelo CLI continuam com Zone.js por padrao, mas a migracao e oficialmente recomendada.
  • Angular 21: O CLI oferecera a opcao de gerar projetos sem Zone.js por padrao. A documentacao oficial priorizara exemplos sem Zone.js. O Zone.js sera depreciado mas continuara funcional para projetos legados.

Esse cronograma gradual permite que o ecossistema — bibliotecas de terceiros, ferramentas de teste e projetos corporativos — se adapte progressivamente. A equipe do Angular tem mantido retrocompatibilidade total em cada etapa, garantindo que projetos existentes continuem funcionando sem alteracoes durante a transicao.

Para desenvolvedores que se preparam para perguntas de entrevista Angular, o entendimento do modo zoneless e agora essencial. Questoes sobre change detection, signals e otimizacao de performance aparecem com frequencia crescente em processos seletivos. O artigo sobre as top 25 perguntas de entrevista Angular aborda vários desses topicos em profundidade, e o modulo Angular Signals oferece exercicios praticos sobre o sistema reativo que fundamenta o modo zoneless.

Conclusao

O modo zoneless representa uma evolucao fundamental na arquitetura do Angular, substituindo a interceptacao global do Zone.js por um sistema de notificacoes granulares baseado em Signals. Os principais pontos abordados neste guia incluem:

  • O Zone.js intercepta mais de 130 APIs do navegador e dispara ciclos globais de change detection apos cada operacao assincrona, gerando overhead significativo em aplicacoes de medio e grande porte
  • O provideExperimentalZonelessChangeDetection() no Angular 19 e o provideZonelessChangeDetection() no Angular 20+ habilitam o modo zoneless, eliminando completamente o Zone.js do bundle
  • Signals sao o mecanismo principal de notificacao no modo zoneless — atualizacoes em signals referenciados por templates disparam re-renderizacao precisa sem varredura global
  • Codigo que depende do Zone.js implicitamente (setTimeout com atribuicoes diretas, formularios reativos sem toSignal()) deve ser migrado para signals para funcionar corretamente
  • O PendingTasks substitui a deteccao automatica do Zone.js no SSR, permitindo controle explicito sobre a estabilizacao antes da serializacao
  • Benchmarks demonstram reducoes de 80-95% nos ciclos de change detection e eliminacao de 33 KB do bundle inicial
  • O NgZone permanece disponivel como no-op para retrocompatibilidade, facilitando a migracao gradual de bibliotecas e projetos existentes
  • O roadmap do Angular 19 ao 21 estabelece um caminho claro de estabilizacao, com o Zone.js sendo depreciado gradualmente ate a geracao de projetos zoneless por padrao

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Tags

#angular
#zoneless
#change-detection
#performance
#signals
#zone-js

Compartilhar

Artigos relacionados