Blog
35 artigos · atualizado semanalmente Veja nossas Ferramentas
Todos os artigos
All

Angular 22: OnPush as Default, Stable Signal Forms, and What to Check Before Running ng update

Angular 22 ships with OnPush as the new default, stable Signal Forms, and a stable Resource API. Here's what changes, what ng update handles automatically, and what you need to review manually.

COVER · All

I ran ng update @angular/core@22 @angular/cli@22 on a mid-sized project and the first thing I noticed afterward was a component that stopped updating the screen. No build errors. No console errors. It just stopped working — the most annoying kind of bug.

The culprit was OnPush. And the irony is that the change was exactly what the Angular team promised it would be: smooth, automatic, invisible. If you understand what's happening underneath, that's great. If you don't, you're in for a debug session with no obvious starting point.

Angular 22, released on June 3, 2026, isn't a feature release — it's the consolidation of three years of bets on signals. Zoneless, Signal Forms, Resource API: everything that was in developer preview finally reached stable. If you've been following this cycle closely, you'll recognize everything. If you haven't, you're about to feel the impact.


Why Angular 22 Is Different From Recent Releases

Since Angular 16, the framework has been doubling down on one direction: signals as the central reactivity mechanism, gradually replacing Zone.js. Each version delivered a piece — signals in 16, signals in components in 17, Signal Forms in developer preview in 19, experimental zoneless in 18, and so on.

Angular 22 closes that loop. Nothing new was invented — the pieces just became stable at the same time. That changes what you can confidently ship to production.


OnPush Is Now the Default — What That Means in Practice

This is the most silent breaking change in the release.

In any previous version, a component without an explicit changeDetection used ChangeDetectionStrategy.Default, which checks the entire component tree on every event, timeout, and HTTP response. Inefficient, but it worked even if you didn't know what was happening underneath.

In Angular 22, the default changed to OnPush. A component without an explicit changeDetection now only updates the view when:

  • An @Input changes by reference
  • A DOM event fires inside it
  • A signal it reads changes value
  • You call markForCheck() manually

If your component relied on direct object mutation or on observables without the async pipe, the screen stops updating — silently.

What ng update handles automatically: it adds ChangeDetectionStrategy.Eager to all existing components that had no changeDetection declared, preserving previous behavior.

// Before (Angular 21)
@Component({
  selector: 'app-user-card',
  template: `...`
})
export class UserCardComponent { ... }

// After ng update (Angular 22)
@Component({
  selector: 'app-user-card',
  changeDetection: ChangeDetectionStrategy.Eager, // added automatically
  template: `...`
})
export class UserCardComponent { ... }

ChangeDetectionStrategy.Eager is the new name for the old default behavior. The important point: after the update, searching for Eager in your codebase is the fastest way to see how much of your app isn't yet OnPush-ready.

The problem I ran into was a component generated by an external scaffolding tool that fell outside the automatic schematic. If you use custom generators or third-party templates, review manually — the schematic only touches files it can track.


Signal Forms Are Finally Stable

Signal Forms moved out of developer preview and are production-ready in Angular 22. The core idea is different from Reactive Forms: instead of a FormGroup that controls values, you have a signal as the source of truth, and the form is derived from it.

The central piece is now the formRoot directive, which connects the signal form to the <form> element in the template:

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

@Component({
  selector: 'app-checkout',
  imports: [FormRoot, FormField],
  template: `
    <form [formRoot]="checkoutForm">
      <input [formField]="checkoutForm.fields.email" type="email" />
      <input [formField]="checkoutForm.fields.name" />
      <button type="submit">Submit</button>
    </form>
  `
})
export class CheckoutComponent {
  model = signal({ email: '', name: '' });

  checkoutForm = form(this.model, schema, {
    submission: {
      action: async (f) => {
        await api.checkout(f.value());
      }
    }
  });
}

formRoot handles the submit event automatically — no (ngSubmit) needed. The submission.action callback fires when the form is valid and submitted.

Should you migrate your Reactive Forms now? Not necessarily. Signal Forms and Reactive Forms coexist fine in Angular 22. The team's recommendation is to migrate incrementally, starting with new forms. Old, complex forms with custom validation can take significant time — assess the cost before starting.


Resource API and httpResource: No More Subscribe

The Resource API also moved out of developer preview. With it, httpResource — which will probably change day-to-day work the most for anyone doing HTTP requests in components.

The idea: instead of injecting HttpClient and managing subscribe/unsubscribe manually, you declare a resource that automatically reloads when the signals it depends on change.

import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';

interface User {
  id: number;
  name: string;
  email: string;
}

@Component({
  selector: 'app-user-profile',
  template: `
    @if (user.isLoading()) {
      <p>Loading...</p>
    } @else if (user.hasValue()) {
      <h2>{{ user.value().name }}</h2>
      <p>{{ user.value().email }}</p>
    } @else if (user.error()) {
      <p>Failed to load user.</p>
    }
  `
})
export class UserProfileComponent {
  userId = signal(1);
  user = httpResource<User>(() => `/api/users/${this.userId()}`);
}

When userId changes, the request fires again automatically. No switchMap, no takeUntilDestroyed, no manual lifecycle management.

httpResource returns an object with isLoading(), hasValue(), value(), and error() — all signals. You compose the UI directly from them in the template.

The traditional HttpClient wasn't removed. But for new components with simple requests that depend on reactive state, httpResource is objectively less code and less surface area for bugs.


HTTP Fetch by Default, Node 22, and TypeScript 6

Three environment changes to check before updating:

HTTP Fetch by default. HttpClient now uses the Fetch API internally instead of XMLHttpRequest. Transparent in most cases, but if you have interceptors that rely on XHR-specific behavior — like upload progress via reportProgress — test before going to production.

Node.js ≥ 22. If your CI pipeline still runs Node 20, the build will break. Update Node before bumping the Angular version.

TypeScript ≥ 6. TypeScript 6 changed module resolution and generic typing behavior. Most code compiles without changes, but if you have complex conditional types, you may see new compilation errors. Worth running tsc --noEmit locally before opening a PR.

A related point that's still outside Angular but will impact the ecosystem soon: Microsoft is porting the TypeScript compiler to Go (tsgo), promising 5x–10x speedup. It's not part of Angular 22, but it's a reason to update your build environment now — the performance gains are coming.


What ng update Does (and What It Doesn't)

ng update @angular/core@22 @angular/cli@22

The automatic schematic handles:

  • Adding ChangeDetectionStrategy.Eager to components without a declared strategy
  • Migrating ComponentFactoryResolver (removed in v22) to the current dynamic component creation API
  • Fixing child route parameter inheritance, which now always inherits by default

What it doesn't do:

  • Migrate Reactive Forms to Signal Forms
  • Remove Zone.js from the application
  • Update HTTP interceptors for Fetch
  • Touch components generated by external scaffolding tools

Duplicate bindings in templates — [value]="x" [value]="y" — now cause a compilation error. Angular 21 silently ignored them. If any template has accidental duplicate bindings, the build will stop.

data-* attributes are also no longer treated as property bindings. [data-id]="item.id" no longer works:

<!-- Angular 21: worked (incorrectly, but worked) -->
<div [data-id]="item.id">

<!-- Angular 22: correct syntax -->
<div [attr.data-id]="item.id">

Before running the update, a quick grep:

grep -rn "\[data-" src/

Fix anything that comes up before updating — the build will stop on those.


Should You Update to Angular 22 Now?

If the project is in production with thin component test coverage, wait a week — let the community surface edge cases the schematic doesn't cover. If you have a reasonable test suite and a staging environment, update. The schematic is solid, the changes are predictable, and staying on Angular 21 with Node 20 in 2026 is starting to have a real cost.

What to do before running the update:

  1. grep -rn "\[data-" src/ — fix these first
  2. Verify the Node version in CI (must be ≥ 22)
  3. Review components created by custom generators
  4. Run tsc --noEmit to surface TypeScript 6 errors early

Angular 22 isn't a revolutionary release — it's an honest one. It did what it promised, shipped stable what was in preview, and changed the defaults to match what the community already considered best practice. The migration cost is real but manageable. The cost of waiting keeps growing.

RD
Autor
Rafael Duarte
Desenvolvedor backend com passagem por fintech e SaaS B2B — trabalhou em times que escalaram APIs de zero a milhões de requisições. Carrega cicatrizes de produção suficientes para ter opiniões fortes sobre ferramentas, padrões e decisões de arquitetura. Não é acadêmico: leu a RFC do UUID quando precisou escolher entre v4 e v7 para uma tabela de alta escrita.
Ver perfil