Angular 22: O Fim do Boilerplate e a Consolidação da Era Reativa
DEV Community Grade 8 4d ago

Angular 22: O Fim do Boilerplate e a Consolidação da Era Reativa

Se você acompanha a evolução do framework do Google nos últimos anos, sabe que ele vem passando por uma reconstrução silenciosa — peça por peça. Com o lançamento do Angular 22 em 3 de junho de 2026 , essa reconstrução deixou de ser uma promessa e virou padrão. Não estamos olhando para mais uma leva de features experimentais: estamos olhando para a consolidação de um ecossistema inteiramente repensado. Para quem vive de aplicações enterprise , Clean Architecture e ecossistemas de Microfrontends , essa é a versão que finalmente entrega o que vinha sendo prometido desde o Angular 16: um framework reativo de ponta a ponta, zone-less por natureza, e com muito menos cerimônia no caminho. Os experimentos acabaram. A seguir, o que mudou de verdade — e o que você precisa fazer antes de rodar o ng update . 📖 Se você está começando: vários termos técnicos deste artigo ( change detection , Signals, SSR, injeção de dependência, microfrontends…) estão explicados num glossário no final . Leia o artigo de ponta a ponta e use o glossário como consulta sempre que bater uma dúvida. O que chegou estável no Angular 22 OnPush é o novo padrão de change detection (o antigo Default virou Eager e está depreciado). Resource API estável : resource , rxResource e httpResource prontos para produção. Signal Forms estável , com Submission API, schemas dinâmicos (Zod/Valibot) e interop com Reactive Forms. Novo decorator @Service() , encurtando o @Injectable({ providedIn: 'root' }) . injectAsync para injeção de dependência lazy , com prefetch via onIdle . debounced para debounce nativo em Signals/Resources. Incremental Hydration ligada por padrão . HttpClient usa FetchBackend por padrão ( withFetch() depreciado). Melhorias importantes de Router e bootstrap pensadas para Microfrontends . 1. OnPush como novo padrão de Change Detection Chegou o momento que a comunidade pedia desde sempre: o ChangeDetectionStrategy.OnPush agora é o comportamento padrão de qualquer componente novo. A decisão faz todo sentido num mundo signals-first — quem usa Signals já recebe notificações cirúrgicas sobre o que mudou, e o OnPush aproveita isso ao máximo, verificando apenas os componentes realmente afetados em vez de varrer a árvore inteira. O antigo Default (que checava a árvore toda) foi renomeado para Eager e está depreciado . Se você ainda precisa do comportamento antigo em algum componente, declare explicitamente: import { ChangeDetectionStrategy , Component } from ' @angular/core ' ; @ Component ({ selector : ' app-legacy ' , changeDetection : ChangeDetectionStrategy . Eager , // substitui o antigo 'Default' template : `...` , }) export class LegacyComponent {} Detalhe crítico para a migração: durante o ng update , se o Angular não encontrar uma estratégia explícita, ele aplica Eager automaticamente para não quebrar nada. Ou seja, você não ganha performance "de graça" — precisa migrar componente a componente para colher o benefício do OnPush . 2. Resource API e httpResource estáveis A Resource API era a peça que faltava no quebra-cabeça dos Signals: derivar dados assíncronos de forma reativa, normalmente disparando requisições HTTP quando um Signal muda. Agora resource , rxResource e httpResource estão estáveis e liberados para produção . O ponto de entrada mais confortável é o httpResource . Ele recebe uma lambda reativa que retorna a requisição: se um Signal usado lá dentro muda, a requisição é refeita automaticamente. import { httpResource } from ' @angular/common/http ' ; import { ChangeDetectionStrategy , Component , signal } from ' @angular/core ' ; import { Flight } from ' ./flight ' ; @ Component ({ selector : ' app-flight-search ' , changeDetection : ChangeDetectionStrategy . OnPush , template : ` @if (flightsResource.isLoading()) { <div>Carregando…</div> } @else if (flightsResource.error()) { <div>Erro: {{ flightsResource.error() }}</div> } @else { @for (flight of flightsResource.value(); track flight.id) { <app-flight-card [item]="flight" /> } } ` , }) export class FlightSearch { protected readonly filter = signal ({ from : ' São Paulo ' , to : ' Uberlândia ' }); protected readonly flightsResource = httpResource < Flight [] > ( () => ({ url : ' https://api.exemplo.io/flight ' , params : { from : this . filter (). from , to : this . filter (). to }, }), { defaultValue : [] }, // evita o componente lidar com `undefined` na inicialização ); protected reload (): void { this . flightsResource . reload (); } } O recurso gerencia o próprio estado via Signals: value , error , isLoading e um status mais detalhado ( idle , loading , reloading , error , resolved , local ). E o melhor: race conditions são tratadas automaticamente — se várias requisições chegam em sequência, só o resultado da mais recente é usado, exatamente como o switchMap faria no RxJS, mas sem você escrever uma linha de pipe. Quer pular a requisição sob certas condições? Basta retornar undefined na lambda . 3. Signal Forms prontos para produção Acabou a eterna divisão entre Reactive Form

Se você acompanha a evolução do framework do Google nos últimos anos, sabe que ele vem passando por uma reconstrução silenciosa — peça por peça. Com o lançamento do Angular 22 em 3 de junho de 2026, essa reconstrução deixou de ser uma promessa e virou padrão. Não estamos olhando para mais uma leva de features experimentais: estamos olhando para a consolidação de um ecossistema inteiramente repensado. Para quem vive de aplicações enterprise, Clean Architecture e ecossistemas de Microfrontends, essa é a versão que finalmente entrega o que vinha sendo prometido desde o Angular 16: um framework reativo de ponta a ponta, zone-less por natureza, e com muito menos cerimônia no caminho. Os experimentos acabaram. A seguir, o que mudou de verdade — e o que você precisa fazer antes de rodar o ng update . 📖 Se você está começando: vários termos técnicos deste artigo (change detection, Signals, SSR, injeção de dependência, microfrontends…) estão explicados num glossário no final. Leia o artigo de ponta a ponta e use o glossário como consulta sempre que bater uma dúvida. O que chegou estável no Angular 22 - OnPush é o novo padrão de change detection (o antigoDefault virouEager e está depreciado). - Resource API estável: resource ,rxResource ehttpResource prontos para produção. - Signal Forms estável, com Submission API, schemas dinâmicos (Zod/Valibot) e interop com Reactive Forms. - Novo decorator @Service() , encurtando o@Injectable({ providedIn: 'root' }) . - injectAsync para injeção de dependência lazy, com prefetch viaonIdle . - debounced para debounce nativo em Signals/Resources. - Incremental Hydration ligada por padrão. - HttpClient usaFetchBackend por padrão (withFetch() depreciado). - Melhorias importantes de Router e bootstrap pensadas para Microfrontends. 1. OnPush como novo padrão de Change Detection Chegou o momento que a comunidade pedia desde sempre: o ChangeDetectionStrategy.OnPush agora é o comportamento padrão de qualquer componente novo. A decisão faz todo sentido num mundo signals-first — quem usa Signals já recebe notificações cirúrgicas sobre o que mudou, e o OnPush aproveita isso ao máximo, verificando apenas os componentes realmente afetados em vez de varrer a árvore inteira. O antigo Default (que checava a árvore toda) foi renomeado para Eager e está depreciado. Se você ainda precisa do comportamento antigo em algum componente, declare explicitamente: import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-legacy', changeDetection: ChangeDetectionStrategy.Eager, // substitui o antigo 'Default' template: `...`, }) export class LegacyComponent {} Detalhe crítico para a migração: durante o ng update , se o Angular não encontrar uma estratégia explícita, ele aplica Eager automaticamente para não quebrar nada. Ou seja, você não ganha performance "de graça" — precisa migrar componente a componente para colher o benefício do OnPush . 2. Resource API e httpResource estáveis A Resource API era a peça que faltava no quebra-cabeça dos Signals: derivar dados assíncronos de forma reativa, normalmente disparando requisições HTTP quando um Signal muda. Agora resource , rxResource e httpResource estão estáveis e liberados para produção. O ponto de entrada mais confortável é o httpResource . Ele recebe uma lambda reativa que retorna a requisição: se um Signal usado lá dentro muda, a requisição é refeita automaticamente. import { httpResource } from '@angular/common/http'; import { ChangeDetectionStrategy, Component, signal } from '@angular/core'; import { Flight } from './flight'; @Component({ selector: 'app-flight-search', changeDetection: ChangeDetectionStrategy.OnPush, template: ` @if (flightsResource.isLoading()) { Carregando… } @else if (flightsResource.error()) { Erro: {{ flightsResource.error() }} } @else { @for (flight of flightsResource.value(); track flight.id) { } } `, }) export class FlightSearch { protected readonly filter = signal({ from: 'São Paulo', to: 'Uberlândia' }); protected readonly flightsResource = httpResource ( () => ({ url: 'https://api.exemplo.io/flight', params: { from: this.filter().from, to: this.filter().to }, }), { defaultValue: [] }, // evita o componente lidar com `undefined` na inicialização ); protected reload(): void { this.flightsResource.reload(); } } O recurso gerencia o próprio estado via Signals: value , error , isLoading e um status mais detalhado (idle , loading , reloading , error , resolved , local ). E o melhor: race conditions são tratadas automaticamente — se várias requisições chegam em sequência, só o resultado da mais recente é usado, exatamente como o switchMap faria no RxJS, mas sem você escrever uma linha de pipe. Quer pular a requisição sob certas condições? Basta retornar undefined na lambda. 3. Signal Forms prontos para produção Acabou a eterna divisão entre Reactive Forms e Template-Driven. Os Signal Forms saíram do status experimental e são a abordagem recomendada para formulários: declarativos, fortemente tipados e reativos por Signals. O coração da API é a função form , que recebe um Signal com os dados e um schema de validação: import { form, minLength, required } from '@angular/forms/signals'; protected readonly flightForm = form(this.flight, (path) => { required(path.from); required(path.to); required(path.date); minLength(path.from, 3); }); O resultado é um FieldTree : uma estrutura aninhada de Signals em que cada campo expõe value , dirty , invalid e errors . No template, você usa a diretiva FormField : {{ flightForm.from().errors() | json }} E não para aí. O Angular 22 (somando a 21.1 e 21.2) trouxe um stack de formulários surpreendentemente completo: - Submission API ( FormRoot +submit ): toda a lógica de envio dentro do próprioform , inclusive recebendo erros de validação do servidor de volta para o estado do formulário. - Schemas dinâmicos com validateStandardSchema , compatível com Zod e Valibot — e que reavaliam quando um Signal muda. - Classes CSS condicionais ( ng-valid ,ng-invalid ,ng-dirty …) viaprovideSignalFormsConfig . - Interop com Reactive Forms via compatForm eSignalFormControl , então você migra de forma incremental sem reescrever o mundo. 4. O novo decorator @Service() Uma daquelas melhorias de ergonomia que você agradece todo dia. O @Service() encurta o caso mais comum de injeção — aquele @Injectable({ providedIn: 'root' }) repetido à exaustão: import { Service } from '@angular/core'; @Service() export class FlightClient { // Provido em root por padrão. A intenção fica explícita. } Se você não quer o provimento automático em root, desligue com autoProvided: false e proveja manualmente (em app.config.ts , no componente ou na rota): @Service({ autoProvided: false }) export class TabRegistry {} Importante: o @Service() não aposenta o @Injectable() . Ele é um atalho para o caso mais frequente. Onde você usa configurações de provider mais sofisticadas, o @Injectable() continua sendo a ferramenta certa. Numa Clean Architecture, o @Service() deixa a camada de adaptadores de infraestrutura bem mais enxuta — mas use com critério, não como substituto cego. 5. injectAsync : injeção de dependência lazy Esse é um presente para quem briga com tamanho de bundle e tempo de startup. Com injectAsync , você injeta uma dependência só quando ela é realmente necessária — ideal para serviços que carregam bibliotecas pesadas e só entram em cena após uma ação específica do usuário: import { injectAsync } from '@angular/core'; @Component({ /* ... */ }) export class CheckinPage { private readonly upgradeService = injectAsync(() => import('./upgrade-service').then((m) => m.UpgradeService), ); protected async upgrade(): Promise { const service = await this.upgradeService(); service.upgrade(/* ... */); } } O import — e, portanto, o carregamento do bundle — só acontece na primeira chamada. Para evitar o atraso dessa primeira vez, dá para pré-carregar com a opção prefetch combinada com onIdle (que carrega quando o navegador está ocioso): import { injectAsync, onIdle } from '@angular/core'; private readonly upgradeService = injectAsync( () => import('./upgrade-service').then((m) => m.UpgradeService), { prefetch: onIdle }, // carrega quando o browser estiver ocioso ); Só lembre: para o lazy load funcionar, o serviço injetado precisa ser auto-provido (via @Service() ou @Injectable({ providedIn: 'root' }) ). 6. debounced : debounce nativo para Signals Signals, por natureza, não sabem nada sobre tempo — não têm debounceTime nem throttle . O Angular 22 resolve isso com a função debounced , que cria um Resource cujo valor é atualizado com o atraso definido: import { debounced } from '@angular/core'; const filter = signal(''); const debouncedFilter = debounced(filter, 300); // 300ms effect(() => console.log(debouncedFilter.value())); Para formulários, o debounce já vem embutido nos Signal Forms — o debounced cobre todo o resto. 7. Incremental Hydration ligada por padrão Foco em performance: a hidratação incremental agora vem habilitada por padrão via provideClientHydration() . Para aplicações com SSR, isso significa um ganho real no carregamento inicial, hidratando componentes sob demanda em vez de tudo de uma vez. Se por algum motivo você não quiser, desligue explicitamente com withNoIncrementalHydration() — e há uma schematic de migração para ajudar. 8. HttpClient agora usa FetchBackend por padrão Mudança discreta, mas com impacto real na migração: o HttpClient passou a usar a Fetch API por padrão, então o withFetch() está depreciado e pode ser removido. A pegadinha está no progresso de requisições. O antigo reportProgress foi substituído por duas opções dedicadas — e upload progress exige XHR: // Download (funciona com Fetch) http.get('/arquivo-grande', { reportDownloadProgress: true, observe: 'events' }); // Upload (exige withXhr()) http.post('/upload', file, { reportUploadProgress: true, observe: 'events' }); Se você usa reportUploadProgress com o FetchBackend , o Angular lança uma exceção de propósito, sinalizando que você

Comments

No comments yet. Start the discussion.