Publicado em
- 20 minutos de leitura
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:
- Um número (milissegundos): Representa um atraso de tempo fixo tradicional.
- 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 retornartrue, a atualização e o disparo do temporizador de debounce são ignorados.injector: UmInjectorpersonalizado 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 {}
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 comoprovidedIn: '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 propriedadeautoProvided: 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çãoinject(). 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 (comouseClass,useValue,useExisting). Em vez disso, ele disponibiliza uma única propriedadefactoryopcional 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:
- Busca todas as
LViewraiz através do atributo[ng-version]. - Varre recursivamente todas as
LViewdescendentes. - Filtra esses elementos para encontrar apenas as diretivas.
- 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.js | Status de Compatibilidade no Angular 22 |
|---|---|
| Node.js 22.x | Suportado (a partir de ^22.22.0) |
| Node.js 24.x | Suportado (a partir de ^24.13.1) |
| Node.js 26.x | Novo: 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:
- No servidor (SSR): Assim que o fluxo do resource é resolvido, o valor é armazenado no
TransferStateassociado à chave fornecida. - 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
takeUntilDestroyedsem um injetor explícito ou chamadas atoSignal/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 umaRouteReuseStrategycustomizada, ela deve implementar o métodoshouldDestroyInjectorpara retornartruepara as rotas que devem ter seus injetores limpos. Além disso, certifique-se de implementarretrieveStoredRouteHandlespara 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:
- Validação de Min e Max: Em Signal Forms, as validações de
minemaxagora aceitam apenas números ounull. O suporte a strings foi removido. - Fim do ComponentFactory: As classes herdadas da era clássica
ComponentFactoryResolvereComponentFactoryforam completamente removidas. Agora você deve passar a classe do componente diretamente em APIs comoViewContainerRef.createComponent(). - 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). - Remoção de provideRoutes: A função utilitária
provideRoutes()foi removida da biblioteca. Use a declaração padrãoprovideRouter()ou o token multiROUTESse necessário. - Tipagem no bootstrap do
ApplicationRef: O segundo argumento deappRef.bootstrap()não aceita mais o tipoany. Para alinhar o método comcreateComponent(), agora ele aceita apenas um objeto de opções (contendohostElement,directivesebindings) ou um elemento direto (string | Element), exigindo que o valor passado não seja anulável (detalhes no commit do Angular). - 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 prefixodata-. Anteriormente, declarar algo como[data-prop]="valor"ou(data-evento)="callback()"vinculava o valor à entradapropou à saídaeventodo componente. Agora, atributos com o prefixodata-não são mais mapeados para inputs ou outputs de componentes. Para vincular atributos de dados customizados do HTML5 no DOM, use o prefixoattr.(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