Publicado em

- 20 minutos de leitura

Angular 22: Novidades e Funcionalidades

img of Angular 22: Novidades e Funcionalidades

O lançamento do Angular 22 marca a consolidação definitiva da era “Signal-first”, transformando este poderoso framework de desenvolvimento web em uma ferramenta muito mais leve e voltada para a alta performance no frontend.

Esta versão foca em remover complexidades históricas, simplificando radicalmente a forma como escrevemos componentes usando Signal Forms, lidamos com chamadas de rede via Resource API e otimizamos a reatividade adotando o OnPush nativamente.

Abaixo, detalhamos cada uma das grandes novidades do Angular 22 que agora fazem parte da versão estável e como elas impactam o seu código.

Se preferir, assista o vídeo diretamente no YouTube:

1. Nova Função debounced() para Signals

O Angular 22 resolve um dos problemas mais comuns na web: aguardar o usuário parar de digitar ou realizar uma ação contínua antes de disparar um processo pesado (como uma requisição HTTP).

A nova função debounced() recebe um Signal como origem e adiciona um tempo de espera customizável, retornando um Resource completo.

Isso significa que você tem acesso a propriedades como .value(), .isLoading() e .status() de forma nativa, sem precisar recorrer a operadores do RxJS (como debounceTime) ou a funções setTimeout manuais.

Como funciona o Ciclo de Vida e o Status?

Como debounced() retorna um Resource, seu estado reflete exatamente o que está acontecendo:

  • loading / reloading: Indica que um novo valor foi emitido pelo Signal de origem, mas o tempo de espera (debounce) ainda está ativo (ou seja, o valor está pendente de consolidação).
  • resolved / local: O tempo de espera terminou e o valor debounced foi atualizado.
  • error: O Signal de origem lançou um erro durante a avaliação.

Customizando o tempo de espera (wait)

O segundo parâmetro de debounced() é extremamente flexível. Você pode passar:

  1. Um número (milissegundos): Representa um atraso de tempo fixo tradicional.
  2. Uma função assíncrona: Uma função com a assinatura (value: T, lastValue: ResourceSnapshot<T>) => Promise<void> | void. O valor debounced só será consolidado quando a Promise retornada por essa função for resolvida. Se o Signal de origem mudar antes da Promise resolver, a execução anterior é cancelada automaticamente.

Opções de Configuração (DebouncedOptions)

Opcionalmente, você pode passar um terceiro parâmetro com as seguintes configurações:

  • equal: Uma função de comparação personalizada (ValueEqualityFn<T>) para determinar se o valor mudou. Se a função retornar true, a atualização e o disparo do temporizador de debounce são ignorados.
  • injector: Um Injector personalizado para uso fora de um contexto de injeção ativo.

Veja como criar uma busca inteligente de forma simples:

   import { Component, signal, debounced } from '@angular/core'

@Component({
	selector: 'app-busca',
	template: `
		<input [value]="termo()" (input)="atualizar($event)" placeholder="Digite sua busca..." />

		@if (termoAtrasado.isLoading()) {
			<p>Aguardando o usuário parar de digitar...</p>
		} @else {
			<p>Buscando por: {{ termoAtrasado.value() }}</p>
		}
	`,
	changeDetection: ChangeDetectionStrategy.OnPush
})
export class BuscaComponent {
	termo = signal('')

	// Debounce simples de 500ms
	termoAtrasado = debounced(() => this.termo(), 500)

	atualizar(event: Event) {
		const input = event.target as HTMLInputElement
		this.termo.set(input.value)
	}
}

E aqui está um exemplo avançado utilizando uma Promise personalizada para controlar o tempo de debounce, além de passar a opção equal para evitar renderizações redundantes:

   import { Component, signal, debounced } from '@angular/core'

@Component({
	selector: 'app-busca-avancada',
	template: `
		<input [value]="termo()" (input)="atualizar($event)" placeholder="Digite..." />
		<p>Valor debounced: {{ termoAtrasado.value() }}</p>
	`,
	changeDetection: ChangeDetectionStrategy.OnPush
})
export class BuscaAvancadaComponent {
	termo = signal('')

	// Usando uma Promise para controlar o debounce de forma customizada ou dinâmica
	termoAtrasado = debounced(
		() => this.termo(),
		(novoValor, ultimoSnapshot) => {
			// Retorna uma Promise que resolve após 1 segundo (aqui poderíamos aguardar um evento como "scrollend", término de animação, etc.)
			return new Promise<void>((resolve) => {
				setTimeout(() => resolve(), 1000)
			})
		},
		{
			// Evita reprocessar se o termo normalizado for idêntico
			equal: (a, b) => a.trim().toLowerCase() === b.trim().toLowerCase()
		}
	)

	atualizar(event: Event) {
		const input = event.target as HTMLInputElement
		this.termo.set(input.value)
	}
}

2. Signal Forms está estável

O Signal Forms chega à sua versão estável trazendo uma mudança drástica de paradigma na construção de formulários no frontend.

Agora, a diretiva formRoot é aplicada diretamente na tag <form>, o que vincula a instância do seu Signal Form ao HTML e gerencia automaticamente eventos de submissão (como o clique no botão ou o Enter do teclado).

Além disso, a forma como definimos validações e submetemos os dados foi centralizada na declaração do formulário, deixando o código muito mais limpo e organizado.

Veja o exemplo de como usar o formRoot, as validações no schema e a nova API de submissão:

   import { Component, signal } from '@angular/core'
import { form, FormField, FormRoot, required, email } from '@angular/forms/signals'

@Component({
	selector: 'app-cadastro',
	imports: [FormField, FormRoot],
	template: `
		<form [formRoot]="cadastroForm">
			<input [formField]="cadastroForm.email" type="email" placeholder="Seu e-mail" />

			@if (cadastroForm.email().touched()) {
				@for (erro of cadastroForm.email().errors(); track erro) {
					<span class="erro">{{ erro.message }}</span>
				}
			}

			<button type="submit" [disabled]="cadastroForm().submitting() || cadastroForm().invalid()">
				@if (cadastroForm().submitting()) {
					Salvando...
				} @else {
					Salvar
				}
			</button>
		</form>
	`,
	changeDetection: ChangeDetectionStrategy.OnPush
})
export class CadastroComponent {
	dados = signal({ email: '' })

	cadastroForm = form(
		this.dados,
		(schema) => {
			required(schema.email, { message: 'O e-mail é obrigatório!' })
			email(schema.email, { message: 'Digite um e-mail válido.' })
		},
		{
			submission: {
				action: async (modelo) => {
					try {
						await new Promise((resolve) => setTimeout(resolve, 1000))
						console.log('Sucesso:', modelo().value())
						return null
					} catch (error) {
						return {
							kind: 'FormError',
							message: 'Erro ao salvar os dados.'
						}
					}
				}
			}
		}
	)
}

3. Resource API está estável também!

Por padrão, os Signals do Angular foram desenhados para serem puramente síncronos, o que sempre gerou desafios no desenvolvimento web ao lidar com APIs externas e processamentos demorados.

A grande revolução da Resource API é criar uma ponte nativa e elegante para operações assíncronas sem que você precise sair do ecossistema de Signals e entrar obrigatoriamente no RxJS.

Através de funções como resource() (para Promises) e rxResource() (para fluxos RxJS complexos), o Angular permite envelopar qualquer tarefa assíncrona dentro de um objeto reativo especial. Ele rastreia todo o ciclo de vida da operação, expondo o resultado final e o progresso em tempo real através de propriedades como .value(), .isLoading(), .error() e .status().

Além disso, ao usar um Signal dentro da propriedade request, a Resource API cancela requisições antigas e dispara uma nova execução de forma automática sempre que esse valor mudar, eliminando a necessidade de usar o operador switchMap do RxJS.

   import { Component, signal, resource } from '@angular/core'

@Component({
	selector: 'app-perfil-usuario',
	template: `
		<button (click)="proximoUsuario()">Próximo Usuário</button>

		@if (usuario.isLoading()) {
			<p>Carregando dados do servidor de forma assíncrona...</p>
		} @else if (usuario.error()) {
			<p>Erro encontrado: {{ usuario.error() }}</p>
		} @else {
			<h3>Nome: {{ usuario.value()?.name }}</h3>
		}
	`,
	changeDetection: ChangeDetectionStrategy.OnPush
})
export class PerfilUsuarioComponent {
	usuarioId = signal(1)

	usuario = resource({
		params: () => ({ id: this.usuarioId() }),
		loader: async ({ params }) => {
			const res = await fetch(`https://api.exemplo.com/users/${params.id}`)
			if (!res.ok) throw new Error('Falha na requisição')
			return res.json()
		}
	})

	proximoUsuario() {
		this.usuarioId.update((id) => id + 1)
	}
}

4. OnPush Strategy como Padrão

Entre as grandes novidades do Angular 22, a mudança para a estratégia ChangeDetectionStrategy.OnPush como padrão em todos os novos componentes é um marco histórico.

Essa mudança acontece porque o uso do Zone.js não é mais necessário nas aplicações modernas para garantir a reatividade da tela.

Antes dos Signals, o Zone.js precisava interceptar todos os eventos assíncronos da página para redesenhar o frontend inteiro de forma pouco otimizada, o que causava sérios gargalos de performance em aplicações com muitos dados.

Agora, graças à reatividade nativa dos Signals, o próprio framework sabe exatamente qual dado mudou e qual nó do DOM atualizar, tornando o monitoramento global do Zone.js completamente obsoleto.

Para evitar confusão nos projetos legados, o antigo comportamento de detecção de mudanças foi renomeado de Default para Eager.

Ao atualizar para o Angular 22 via ng update, o assistente de migração do framework adiciona automaticamente changeDetection: ChangeDetectionStrategy.Eager em todos os componentes existentes que não possuíam uma estratégia definida de reatividade.

Isso garante que a sua aplicação legada continue funcionando sem qualquer quebra de comportamento, permitindo que você adote a nova reatividade no seu próprio ritmo.

   // Componentes novos já nascem com OnPush nativo e livre de Zone.js.
@Component({
	selector: 'app-antigo',
	template: `<p>Modo de renderização constante com Zone.js</p>`,
	// Se quiser usar o modo Eager antigo por compatibilidade, declare explicitamente:
	changeDetection: ChangeDetectionStrategy.Eager
})
export class AntigoComponent {}

Imagem com o texto: Curso Angular Moderno e um homem de pele parda usando óculos com a logo do Angular atrás. Logo abaixo existe um botão com o texto "Eu quero!"

5. O Decorator @Service

O Angular 22 introduz o decorator @Service(), uma alternativa mais limpa e moderna ao clássico @Injectable().

O @Injectable() existe desde as primeiras versões do framework e carrega uma série de configurações complexas (como providedIn, useClass, useValue, etc.).

Como a grande maioria das aplicações modernas apenas precisa definir um serviço Singleton global, o @Service() foi criado para simplificar e padronizar esse cenário.

Principais Diferenças e Características

  • Auto-provisionamento por Padrão: Por padrão, qualquer classe anotada com @Service() é registrada automaticamente como providedIn: 'root'. Caso queira registrar o serviço manualmente no escopo de um componente ou módulo específico, você pode desativar esse comportamento definindo a propriedade autoProvided: false:
       @Service({ autoProvided: false })
  • Fim da Injeção por Construtor: O @Service() não suporta injeção de dependência baseada em construtor. Ele exige o uso exclusivo da função inject(). Se você tentar declarar parâmetros no construtor de uma classe anotada com @Service(), o compilador do Angular gerará um erro de compilação (SERVICE_CONSTRUCTOR_DI).
  • Configurações Simplificadas: Para evitar a complexidade das opções herdadas do @Injectable(), o @Service() removeu o suporte a provedores complexos (como useClass, useValue, useExisting). Em vez disso, ele disponibiliza uma única propriedade factory opcional para configurar como a instância do serviço deve ser criada:
       @Service({
    	factory: () => new LogisticaService()
    })

Exemplo de Uso Prático

   import { Service, inject } from '@angular/core'
import { HttpClient } from '@angular/common/http'

@Service()
export class LogisticaService {
	private http = inject(HttpClient)

	rastrearEncomenda(codigo: string) {
		return this.http.get(`/api/rastreio/${codigo}`)
	}
}

Essa mudança torna o código muito mais enxuto para o dia a dia, eliminando ruídos visuais de configuração, auxiliando novos desenvolvedores a evitarem erros clássicos de escopo e consolidando a função inject() como o padrão moderno do framework.

6. TypeScript 6 Oficializado para o Desenvolvimento Web Angular

O suporte ao TypeScript 6 é mais uma das novidades do Angular 22, trazendo melhorias profundas de segurança e tipagem para o seu código.

Atenção: Com esta mudança, o suporte para versões do TypeScript anteriores à 6.0 foi completamente descontinuado. Ou seja, a migração para o TypeScript 6.x é um requisito obrigatório para rodar o Angular 22.

Versões Suportadas do TypeScript

Para que o Angular 22 funcione corretamente, a versão do TypeScript no seu projeto deve atender à seguinte restrição:

  • TypeScript: >=6.0.0 <6.1.0

Esta versão do compilador identifica erros potenciais mais cedo no seu editor de código, facilitando a vida do desenvolvedor frontend e evitando bugs em produção.

Além disso, ela prepara o terreno para otimizações futuras de compilação promovidas pela equipe do TypeScript.

7. Lazy-loading Otimizado com injectAsync()

A nova função injectAsync() no Angular 22 permite carregar arquivos de serviços de forma tardia, garantindo a alta performance do aplicativo.

Isso é perfeito para funções muito pesadas do frontend, como geradores de relatórios e exportadores, que não precisam travar o carregamento inicial da página do usuário.

   import { Component } from '@angular/core'
import { injectAsync } from '@angular/core'

@Component({
	selector: 'app-fatura',
	template: `<button (click)="exportar()">Baixar PDF</button>`
})
export class FaturaComponent {
	private pdfService = injectAsync(() => import('./pdf.service').then((m) => m.PdfService))

	async exportar() {
		const service = await this.pdfService()

		service.gerar()
	}
}

8. HttpClient com Fetch API por Padrão

A partir do Angular 22, o HttpClient passa a utilizar o FetchBackend por padrão para realizar requisições HTTP, substituindo a antiga implementação baseada em XMLHttpRequest (XHR).

Essa mudança torna desnecessário o uso da função helper withFetch(), que agora está oficialmente depreciada e pode ser removida do seu provideHttpClient().

   // Antes (Angular 17-21)
provideHttpClient(withFetch())

// Agora (Angular 22+)
provideHttpClient() // Já usa Fetch por padrão!

O que muda nos relatórios de progresso?

Como a Fetch API nativa ainda possui limitações para monitorar o progresso de envios (upload progress), se a sua aplicação depende desses relatórios em tempo real, você precisará configurar explicitamente o backend baseado em XHR:

   import { provideHttpClient, withXhr } from '@angular/common/http'

// Força o uso do XHR para manter suporte a upload progress
provideHttpClient(withXhr())

Migração Automática via Schematics

Para evitar quebras de compatibilidade (breaking changes) inesperadas ao migrar sistemas legados, a equipe do Angular incluiu um schematic de migração automática no processo de atualização.

Ao rodar ng update @angular/core, o script analisa a sua base de código e injeta automaticamente withXhr() em todas as declarações de provideHttpClient() que não possuíam withFetch() configurado.

Isso garante que sua aplicação continue rodando via XHR até que você decida validar e migrar para o Fetch manualmente.

9. Hidratação Incremental Ativa por Padrão

Com foco em levar o Server-Side Rendering (SSR) ao estado da arte no desenvolvimento web, o Angular 22 oficializa a Hidratação Incremental como comportamento padrão.

Anteriormente experimental, esta funcionalidade permite que a página renderizada no servidor seja hidratada no navegador do usuário de forma fragmentada e sob demanda (por exemplo, ao rolar até um componente ou interagir com ele), em vez de paralisar a thread principal tentando carregar e processar todo o Javascript de uma só vez.

Isso otimiza drasticamente métricas vitais como o Interaction to Next Paint (INP).

Como desativar a hidratação incremental?

Se por algum motivo de compatibilidade ou comportamento específico você precisar desativar essa funcionalidade e voltar para a hidratação total clássica, basta utilizar a nova função helper withNoIncrementalHydration() dentro da configuração de hidratação do cliente:

   import { bootstrapApplication } from '@angular/platform-browser'
import { provideClientHydration, withNoIncrementalHydration } from '@angular/platform-browser'

bootstrapApplication(AppComponent, {
	providers: [provideClientHydration(withNoIncrementalHydration())]
})

10. Integração com IA e Suporte ao Model Context Protocol (MCP)

Em um movimento visionário para alinhar o framework à era dos agentes autônomos de IA, o Angular 22 introduz suporte ao Model Context Protocol (MCP) através das APIs experimentais provideWebMcpTools e declareWebMcpTool.

Isso permite criar pontes de comunicação direta entre a aplicação em tempo de execução e ferramentas de inteligência artificial.

Com isso, agentes de IA conseguem inspecionar o gráfico de Injeção de Dependência (DI) do Angular e realizar diagnósticos e debugs diretamente no navegador, abrindo portas para uma nova geração de assistentes de desenvolvimento integrados.

   import { provideWebMcpTools } from '@angular/core'

// Habilita as ferramentas de MCP integradas ao gráfico DI para debug com IA
bootstrapApplication(AppComponent, {
	providers: [provideWebMcpTools()]
})

Por Trás dos Panos: A Ferramenta angular:di-graph

Para viabilizar essa integração, a equipe do Angular implementou a ferramenta in-page angular:di-graph (detalhada no commit 75f2cb8). Essa ferramenta extrai e retorna o gráfico completo de injeção de dependências da aplicação para a IA.

O mapeamento do grafo funciona a partir de um fluxo de varredura:

  1. Busca todas as LView raiz através do atributo [ng-version].
  2. Varre recursivamente todas as LView descendentes.
  3. Filtra esses elementos para encontrar apenas as diretivas.
  4. Recupera o injetor da diretiva e sobe na árvore de ancestrais para registrar os injetores de elementos (ElementInjector).

O mesmo processo é aplicado para descobrir os injetores de ambiente (EnvironmentInjector), seguindo a hierarquia correspondente.

Esta implementação inicial possui algumas limitações conhecidas, como a falta de correlação direta entre componentes e seus EnvironmentInjectors no resultado final, a ausência de suporte para múltiplos apps na mesma página (Micro-Frontends) e oportunidades de otimização de performance no algoritmo de busca.

11. Herança de Parâmetros de Rotas Simplificada

Outro ajuste muito bem-vindo no Roteador do Angular é a mudança do padrão da propriedade paramsInheritanceStrategy, que agora é configurada como 'always' (anteriormente era 'emptyOnly').

Com essa alteração, parâmetros capturados nas rotas superiores (como /usuarios/:id) são herdados por padrão por todas as rotas filhas aninhadas na árvore de navegação, eliminando a necessidade de ler dados de snapshots de rotas superiores manualmente ou reconfigurar o roteador em todo projeto.

12. Suporte ao Node.js 26

O Angular 22 atualiza o suporte ao ambiente de execução local com a compatibilidade oficial para o Node.js 26.0.0 no compiler-cli.

Isso garante que equipes de desenvolvimento possam utilizar as versões mais modernas do runtime Javascript com total estabilidade, aproveitando as mais recentes otimizações de performance e recursos de segurança da engine V8 diretamente nos seus fluxos de build.

Abaixo está a tabela de compatibilidade oficial de versões do Node.js no Angular 22:

Versão do Node.jsStatus de Compatibilidade no Angular 22
Node.js 22.xSuportado (a partir de ^22.22.0)
Node.js 24.xSuportado (a partir de ^24.13.1)
Node.js 26.xNovo: Suportado (a partir de >=26.0.0)

13. Alerta de Compilação para Triggers de @defer Confusos

O Angular 22 também traz melhorias no compilador (compiler-cli) para ajudar a evitar erros comuns de lógica ao trabalhar com o carregamento tardio de componentes.

Agora, o compilador emitirá um aviso (warning) quando encontrar um bloco @defer contendo uma instrução de pré-carregamento (prefetch) sem que uma regra principal de renderização seja definida explicitamente.

Por que esse aviso foi criado?

Se você declara apenas a condição de pré-carregamento (ex: prefetch on interaction), mas não define uma condição principal para renderizar o bloco, o Angular assume por padrão o comportamento on idle para a renderização da tela.

Isso faz com que o componente seja carregado e exibido assim que a thread estiver livre, tornando o trigger de prefetch completamente inútil e redundante.

   <!-- Incorreto (Gera aviso no Angular 22) -->
@defer (prefetch on interaction) {
	<app-grafico-pesado />
}

<!-- Correto (Comportamento explícito e sem avisos) -->
@defer (on interaction; prefetch on idle) {
	<app-grafico-pesado />
}

14. Cache com Resource API para SSR com a propriedade id

O Angular 22 aprimorou a integração do Server-Side Rendering (SSR) e Hydration ao adicionar a capacidade de cachear o resultado de operações assíncronas feitas através da Resource API (resource e rxResource).

Anteriormente, ao carregar uma página renderizada no servidor (SSR) que utilizava resource para buscar dados, a requisição ocorria tanto no servidor quanto novamente no navegador assim que o cliente era inicializado (hydration), gerando chamadas HTTP redundantes.

Com a nova opção id nas configurações do resource, o Angular passa a utilizar o TransferState de forma transparente:

  1. No servidor (SSR): Assim que o fluxo do resource é resolvido, o valor é armazenado no TransferState associado à chave fornecida.
  2. No navegador (Hydration): O resource lê instantaneamente o valor pré-carregado a partir do cache e transiciona seu status para 'resolved' de forma síncrona, evitando a execução de uma nova chamada de HTTP.

Exemplo de uso prático:

   import { Component, resource, makeStateKey } from '@angular/core'

interface Produto {
	nome: string
}

@Component({
	selector: 'app-detalhe-produto',
	template: `
		@if (produto.isLoading()) {
			<p>Carregando produto...</p>
		} @else {
			<h2>{{ produto.value()?.nome }}</h2>
		}
	`,
	changeDetection: ChangeDetectionStrategy.OnPush
})
export class DetalheProdutoComponent {
	produto = resource({
		// Define a chave de cache para o Transfer State
		id: makeStateKey<Produto>('cache-detalhe-produto'),
		loader: async () => {
			const res = await fetch('https://api.exemplo.com/produto/1')
			return res.json()
		}
	})
}

Essa melhoria simples reduz o tráfego de dados, otimiza o tempo de carregamento da página no navegador e consolida ainda mais a Resource API como a ferramenta definitiva para lidar com estados assíncronos no framework.

15. Novo Método TestBed.getFixture para Testes Unitários

O Angular 22 introduz um novo método utilitário para facilitar a escrita de testes unitários: o TestBed.getFixture().

Esse método permite recuperar a instância de ComponentFixture criada mais recentemente.

Isso é extremamente útil em cenários onde a fixture é inicializada dentro de um bloco beforeEach, mas precisa ser acessada diretamente nos testes individuais (it).

Com isso, elimina-se a necessidade de declarar e gerenciar variáveis locais no escopo do bloco describe apenas para compartilhar a referência da fixture.

Como Funciona:

  • Recuperação automática: Retorna a última fixture criada no ambiente de teste atual.
  • Prevenção de erros: Se mais de uma fixture tiver sido criada no mesmo teste, o método lança um erro explicativo para evitar que o teste dependa implicitamente da ordem de criação das fixtures.
  • Limpeza automática: A referência interna é limpa automaticamente quando o ambiente de teste é resetado (como após chamadas a TestBed.resetTestingModule()).

Exemplo de Uso:

   import { TestBed } from '@angular/core/testing'
import { MeuComponente } from './meu.componente'

describe('MeuComponente', () => {
	beforeEach(async () => {
		await TestBed.configureTestingModule({
			imports: [MeuComponente]
		}).compileComponents()

		// Cria a fixture no beforeEach sem precisar guardá-la em uma variável do describe
		TestBed.createComponent(MeuComponente)
	})

	it('deve renderizar o componente', () => {
		// Obtém a fixture de forma limpa e direta
		const fixture = TestBed.getFixture<MeuComponente>()
		expect(fixture.componentInstance).toBeTruthy()
	})
})

Se você tentar chamar TestBed.getFixture() sem ter criado nenhuma fixture anteriormente, ou se houver mais de uma fixture ativa, o Angular lançará uma exceção recomendando as melhores práticas para o caso.

16. Limpeza Automática de Injectors de Rotas (withExperimentalAutoCleanupInjectors)

Embora tenha sido introduzido de forma experimental no Angular 21.0, o recurso withExperimentalAutoCleanupInjectors ainda é pouco conhecido pela comunidade, mas resolve um dos maiores problemas de gerenciamento de memória em aplicações Angular de médio e grande porte.

O Problema: Acúmulo de Injetores em Rotas

Historicamente, quando você define providers diretamente no escopo de uma rota (comum em lazy-loading), o Angular cria um EnvironmentInjector específico para aquela rota.

O problema é que, por padrão, mesmo que o usuário navegue para outra parte da aplicação e a rota seja totalmente destruída da tela, esse injetor e os serviços instanciados nele permanecem ativos na memória para sempre (durante toda a vida útil da aplicação).

Isso gera:

  • Vazamentos de memória (Memory Leaks): Serviços que deveriam ser temporários continuam consumindo memória.
  • Ciclos de vida quebrados: O hook ngOnDestroy() desses serviços nunca é disparado.
  • Problemas com reatividade: Operadores reativos vinculados ao ciclo de vida do componente/injetor (como takeUntilDestroyed sem um injetor explícito ou chamadas a toSignal/toObservable) não são limpos adequadamente.
  • Acúmulo de estado residual: A falta de destruição do injetor faz com que as instâncias dos serviços persistam. Ao navegar de volta para a rota, o estado anterior (como filtros, paginação ou cache temporário) permanece ativo, exigindo uma limpeza manual e complexa para evitar a exibição de dados antigos de outras navegações.

A Solução: Limpeza Automática com withExperimentalAutoCleanupInjectors

Ao ativar esse recurso na sua aplicação, o Angular passa a monitorar a árvore de rotas ativa.

Quando uma rota deixa de fazer parte da árvore ativa (e não está salva para reutilização), o framework destrói automaticamente o seu EnvironmentInjector associado.

Com isso, o ngOnDestroy de todos os serviços instanciados no nível daquela rota é disparado perfeitamente, liberando a memória ocupada.

Como Habilitar:

Basta passar a função withExperimentalAutoCleanupInjectors() na configuração do seu provideRouter:

   import { bootstrapApplication } from '@angular/platform-browser'
import { provideRouter, withExperimentalAutoCleanupInjectors } from '@angular/router'
import { routes } from './app.routes'
import { AppComponent } from './app.component'

bootstrapApplication(AppComponent, {
	providers: [
		provideRouter(
			routes,
			// Habilita a limpeza automática dos injetores de rotas
			withExperimentalAutoCleanupInjectors()
		)
	]
})

Detalhes Importantes:

  • Estratégia de Reutilização de Rotas (RouteReuseStrategy): Se a sua aplicação utiliza uma RouteReuseStrategy customizada, ela deve implementar o método shouldDestroyInjector para retornar true para as rotas que devem ter seus injetores limpos. Além disso, certifique-se de implementar retrieveStoredRouteHandles para evitar que injetores de rotas guardadas no cache sejam destruídos antes de serem reanexados.
  • Status Experimental: Como o nome indica, o recurso ainda é experimental. Contudo, é uma adição indispensável se o seu projeto sofre com alto consumo de memória decorrente de navegações constantes por rotas lazy-loaded com serviços próprios.

Atenção ao Migrar (Breaking Changes Importantes)

A transição para o Angular 22 traz algumas quebras de compatibilidade que merecem atenção especial no seu checklist de migração:

  1. Validação de Min e Max: Em Signal Forms, as validações de min e max agora aceitam apenas números ou null. O suporte a strings foi removido.
  2. Fim do ComponentFactory: As classes herdadas da era clássica ComponentFactoryResolver e ComponentFactory foram completamente removidas. Agora você deve passar a classe do componente diretamente em APIs como ViewContainerRef.createComponent().
  3. Seletores Duplicados: Elementos que correspondam a múltiplos seletores idênticos de componentes agora resultarão em erro de compilação em tempo de build (NG8023).
  4. Remoção de provideRoutes: A função utilitária provideRoutes() foi removida da biblioteca. Use a declaração padrão provideRouter() ou o token multi ROUTES se necessário.
  5. Tipagem no bootstrap do ApplicationRef: O segundo argumento de appRef.bootstrap() não aceita mais o tipo any. Para alinhar o método com createComponent(), agora ele aceita apenas um objeto de opções (contendo hostElement, directives e bindings) ou um elemento direto (string | Element), exigindo que o valor passado não seja anulável (detalhes no commit do Angular).
  6. Fim do suporte a bindings de inputs/outputs usando o prefixo data-: O compilador do Angular não realiza mais a normalização de atributos para remover o prefixo data-. Anteriormente, declarar algo como [data-prop]="valor" ou (data-evento)="callback()" vinculava o valor à entrada prop ou à saída evento do componente. Agora, atributos com o prefixo data- não são mais mapeados para inputs ou outputs de componentes. Para vincular atributos de dados customizados do HTML5 no DOM, use o prefixo attr. (por exemplo, [attr.data-prop]="valor"), que já é o padrão recomendado (detalhes no commit do Angular).

Conclusão

O Angular 22 consolida a visão de um framework moderno de desenvolvimento web, onde a performance frontend e a simplicidade de código finalmente andam juntas.

A chegada da função debounced(), a evolução incrível do Signal Forms com a diretiva formRoot e as soluções assíncronas providas pela Resource API eliminam a obrigatoriedade de usar o RxJS para as tarefas comuns do dia a dia.

Com essas novidades, desde o OnPush ativado por padrão até o novo decorator @Service, a curva de aprendizado fica incrivelmente mais amigável. Migrar para o Angular 22 é um passo essencial para quem deseja extrair o máximo de velocidade de suas aplicações modernas, dando um adeus definitivo às limitações do Zone.js.

Entre na nossa comunidade!

Receba novos posts, novidades do ecossistema Angular e muito mais.

Sobre o autor

Author's photo
Henrique Custódia Arquiteto Frontend, entusiasta Angular, cat lover, criador de conteúdo e fundador da Code Dimension!