Angular v22 Is Here: Signal Forms Go Stable, Async Signals Land, Aria Ships [2026]

Link copied
Angular v22 Is Here: Signal Forms Go Stable, Async Signals Land, Aria Ships [2026]

Angular v22 Is Here: Signal Forms Go Stable, Async Signals Land, Aria Ships [2026]

Angular v22 shipped officially — the biggest release for the signal era of the framework. After two cycles of experimental APIs, three of the headline features the Angular team has been previewing all year — Signal Forms, Asynchronous Signals, and Angular Aria — are now stable and production-ready. Add to that a swath of template ergonomics, security hardening, AI tooling, and a series of small DX wins, and v22 is the most polished single Angular release in years.

This post is the deep summary. We will walk every meaningful change in v22, what it replaces, how to migrate, and which features matter most for your codebase. If you maintain an Angular app, this is your two-page brief on what the release means for you.

TL;DR — three headline stabilizations #

The headline of v22 is one word: stable. Three APIs that have been experimental graduate to production-ready:

API What it does Replaces
Signal Forms A reactive form API built on signals with automatic two-way binding, type-safe field access, and schema-based validation Reactive Forms (the FormGroup/FormControl API) and template-driven (ngModel) for most new code
Asynchronous Signals First-class signal primitives for async work — injectAsync, debounced(), linkedSignal, sync values in Resource Hand-rolled effect() + Promise patterns; some RxJS pipelines
Angular Aria A new accessibility module with primitives for focus management, ARIA attributes, and roving tabindex A patchwork of @angular/cdk/a11y features and rolled-from-scratch components

All three were the marquee features of v21's experimental tier. v22 promotes them. Application code that uses them no longer requires opt-in flags.

Signal Forms — finally stable #

The v21 announcement called Signal Forms "experimental but production-ready in practice." v22 makes it official. The API is now exported from @angular/forms/signals and stable.

The shape (covered in detail in lesson 4.1 of the Angular tutorial):

import { form, field, required, email, minLength } from '@angular/forms/signals';

this.loginForm = form({
  email: field('', [required(), email()]),
  password: field('', [required(), minLength(8)]),
});

// Read state
this.loginForm.email().value();    // current value (signal)
this.loginForm.email().errors();   // { required?: true, email?: true }
this.loginForm.valid();             // boolean signal

What changed between v21 experimental and v22 stable:

  • No flag needed. v21 required setting an experimental flag in tsconfig.app.json. v22 ships it on by default.
  • Schema-based validators are first-class. The full Validator shape — sync, async, cross-field — is part of the public API.
  • Type-safe field access. form.email().value() is strongly typed; you cannot read a non-existent field.
  • Automatic two-way binding with model() integration. Field inputs and outputs match the [(prop)] template syntax natively.

For brand-new forms in 2026, this is the recommended API. Reactive Forms continue to work and are not deprecated, but the Angular team's recommended migration path for new code is signal forms.

If you're starting a new form today: reach for form() from @angular/forms/signals. If you're maintaining a reactive-forms codebase: migrate opportunistically when you touch each form.

Asynchronous Signals — stable primitives for async #

The second big stabilization is async signals. v22 ships several previously-experimental APIs as stable:

debounced() #

Given any signal, return one that mirrors it after N milliseconds of stillness:

import { debounced } from '@angular/core/signals';

const query = signal('');
const debouncedQuery = debounced(query, 300);

query.set('a'); query.set('an'); query.set('ang');
// debouncedQuery() updates to 'ang' 300ms after the last set

This was experimental in v21. In v22 it's stable, and the most common use — wiring debounced() to httpResource() for search-as-you-type — is the recommended pattern. See lesson 3.4 of the Angular tutorial for the deep dive.

linkedSignal() (also stabilized) #

Writable derived state with auto-reset when its source changes — for active-tab indices, dependent form defaults, and other "derive-but-also-let-the-user-override" patterns. Covered in lesson 3.8.

injectAsync() #

A new helper for injecting async-resolved values directly in the injection context:

const data = await injectAsync(DATA_TOKEN);

Resolves at construction time. Useful for app initializers that need data before the component renders, and for resolvers (lesson 5.5) that want to inject async dependencies.

Sync values in Resource #

The resource() API (lesson 6.3) now supports synchronous initial values — so a cache hit can return immediately without a Promise wrapper. This is the cleanest pattern for SSR (lesson 9.6) where you want the server-fetched data to appear synchronously on hydration.

Angular Aria — accessibility primitives shipped #

The third stabilization: Angular Aria — a new @angular/aria package that provides foundational accessibility primitives:

  • Focus managementFocusTrap, focus monitor utilities
  • ARIA attributes — declarative directives for setting aria-* attributes from signals
  • Roving tabindex — for menubars, tab lists, listboxes
  • Live regions — announce dynamic changes to assistive tech
  • Selection — single/multi-select primitives for custom widgets

Most of these existed in @angular/cdk/a11y. The Angular team consolidated them, modernized the APIs (signal-based where applicable), and shipped them as a separate @angular/aria package. The CDK versions continue to work but are deprecated in favor of the new package.

For anyone shipping custom UI widgets (a custom dropdown, autocomplete, tree view), this is the toolkit. For consumers of Angular Material, no action needed — Material uses Aria internally and the upgrade is transparent.

Template enhancements #

v22 ships several template-layer improvements:

Safe navigation narrows nullables #

In earlier versions, {{ user?.name }} in a template kept TypeScript thinking user could be null even inside @if (user; as u) { ... } blocks. v22 fixes the type narrowing — the compiler now correctly understands that inside an @if block, the variable is non-null.

@if (user(); as u) {
  <!-- TypeScript and the template compiler both know u is User, not User | null -->
  <p>{{ u.address.city }}</p>          <!-- previously needed u.address?.city -->
}

Small fix; eliminates dozens of unnecessary ?. chains in real templates.

Optional chaining returns undefined (not null) #

A subtle but useful change: in Angular template expressions, obj?.x now matches TypeScript semantics — returns undefined if the left side is nullish, not null. Matches what your TypeScript code does outside templates. Reduces "why is it undefined here and null there?" debugging.

Comments in HTML elements #

You can now write standard HTML comments inside Angular template attribute lists:

<button
  type="submit"
  <!-- TODO: add aria-label once design lands -->
  (click)="submit()"
>

Minor quality-of-life win; matches what most other templating languages have always allowed.

Improved SVG namespace handling #

SVG elements with explicit namespaces (xlink:href, custom namespaces) now compile correctly without manual [attr.xlink:href] workarounds. If you've ever fought Angular over animated SVG, this release is a small gift.

Developer experience #

A grab-bag of API improvements that quietly make daily work better:

@Service decorator #

A new decorator for service classes, equivalent to @Injectable({ providedIn: 'root' }) but shorter:

import { Service } from '@angular/core';

@Service()
export class AuthService {
  user = signal<User | null>(null);
  // ...
}

The long form still works. For new services, @Service() saves a few characters and a glance at the metadata object.

TestBed.getFixture #

The new helper consolidates the most common pattern in component tests:

// Old
const fixture = TestBed.createComponent(MyComponent);
fixture.detectChanges();
const component = fixture.componentInstance;

// New
const { fixture, component } = TestBed.getFixture(MyComponent);
// detectChanges fires automatically

Small reduction in boilerplate; eliminates a class of "forgot to call detectChanges" bugs.

ApplicationRef.bootstrap with config #

The imperative bootstrap API (used for dynamically-rendered components, lesson 2.14) now accepts a configuration object — providers, environment injector, etc. — without the createComponent({ environmentInjector }) dance:

this.appRef.bootstrap(MyModalComponent, {
  providers: [{ provide: MODAL_DATA, useValue: { title: 'Confirm' } }],
});

Cleaner than the multi-step setup that was previously required.

Host directives de-duplication #

If two components in the same view attach the same hostDirective, v22 detects the duplicate and uses one instance instead of two. Pre-v22, you'd get two instances and potentially duplicate behavior. Fix is automatic; no code change needed.

Enhanced profiling with doc URLs #

The Angular DevTools profiler now includes documentation URLs in its output. Click a profiler entry → jump directly to the relevant page on angular.dev. Useful when diagnosing CD performance issues for the first time.

Performance and security #

Three security/perf improvements worth flagging:

Resource caching for SSR #

The resource() and httpResource() APIs (lesson 6.3) now integrate with Angular's SSR TransferState (lesson 9.6) — the server's fetched data is serialized into the HTML response, and the client's resource picks it up on hydration without re-fetching. Saves the equivalent network call on first-page load.

SSRF protection in platform-server #

Server-side requests originating from SSR (e.g., HttpClient calls during SSR rendering) now have built-in SSRF (Server-Side Request Forgery) protection — by default they refuse to call internal IPs (127.0.0.1, 10.x.x.x, etc.) unless explicitly allowlisted. Closes a class of vulnerabilities where user-controlled URLs could be used to probe internal infrastructure.

Sanitization of SVG href / xlink:href #

Dynamic SVG href values are now sanitized for javascript: URLs the same way other URL contexts are. Cuts another XSS vector.

<meta> selector sanitization #

The Meta service's addTag and similar methods now sanitize content to prevent meta-tag-injection attacks (a real but obscure XSS vector).

None of these require code changes — they harden the framework's defaults.

AI integration — provideWebMcpTools #

The Angular team continues to lean into AI-augmented development. v22 introduces:

  • DI graph visualization in Angular DevTools — see the injection chain for any service, hover for the file path. Useful for debugging "why is THIS instance being injected?" questions.
  • provideWebMcpTools — a new provider that exposes web-relevant tools (browser API access, DOM inspection) to MCP clients. Pair with ng mcp (covered in lesson 12.1) for richer AI capabilities inside Angular projects.
  • Updated MCP server — the official ng mcp server has new tools for v22 features (Signal Forms migration analyzer, Aria component generator).

If you're using Claude, Cursor, or another MCP-aware AI assistant alongside an Angular project, v22's tooling significantly upgrades what the AI can do for you.

Breaking changes #

The v22 release notes call out two breaking changes worth noting:

TypeScript 5.9 support dropped #

Angular v22 requires TypeScript 6.0+. If you're on 5.9 or older, upgrade before adopting v22. The ng update schematic handles this automatically — ng update @angular/cli @angular/core bumps both.

VS Code Angular Extension bundles TypeScript 6.0 #

The official VS Code extension now ships with TypeScript 6.0 internally. If you have a project with "strict": false in tsconfig.json that relies on older TS 5.x behavior, you may see new errors from the IDE language service. The fix is to either upgrade your TS config to strict mode (recommended) or set the extension to use your project's local TypeScript via the workspace setting typescript.tsdk.

Both changes are mechanical fixes. No deep refactoring required.

Deprecations #

v22 doesn't deprecate any major user-facing APIs. The minor deprecations:

  • @angular/cdk/a11y — most utilities are deprecated in favor of @angular/aria. Both work; CDK ones will be removed in a future major release.
  • Older inject() decorator-only patterns@HostBinding / @HostListener decorators still work but the modern host: metadata block (lesson 2.12) is now consistently preferred in docs.
  • UntypedFormGroup / UntypedFormControl — for legacy reactive forms migration; not actively removed but increasingly secondary to typed/non-nullable form builders (lesson 4.6).

Nothing in v22 forces a rewrite. The deprecation runway is long; legacy code can stay legacy for several major releases.

Node.js 26 support #

The Angular CLI now supports Node.js 26.0.0 (the latest LTS as of mid-2026). If you're on Node 20 LTS, no change needed; v22 still supports Node 20+. If you've been waiting to upgrade Node, the runway is clear.

Migration: what to do today #

Three concrete steps for an existing Angular app:

1. Run ng update #

The official migration path:

ng update @angular/cli @angular/core

The CLI auto-handles dependency version bumps, TypeScript upgrade, and a handful of code transformations (including a Signal Forms migration if you opt in).

2. Audit the deprecated CDK a11y imports #

If you use @angular/cdk/a11y for focus management or aria helpers, plan a migration to @angular/aria. The migration is mostly mechanical — same API names, different import paths.

3. Consider opportunistic Signal Forms migration #

For new forms, use form() from @angular/forms/signals. For existing forms, leave them; migrate when you next touch each form file. The Angular CLI ships an experimental Signal Forms migration schematic; review the output carefully before committing.

What's next — v23 roadmap teasers #

The Angular team's official roadmap gives a few hints:

  • Selectorless components stable — currently experimental in v22, on track to stabilize in v23
  • Hybrid rendering improvements — better SSR-to-CSR transitions, smarter hydration
  • More signal-shaped APIssignalStore from the NgRx team is becoming the de-facto state-management primitive; Angular team is exploring official primitives in this space
  • Improved ng mcp tooling — better integration with Claude Desktop, Cursor, and Copilot for v23

For now, v22 is the version to be on.

What it means for your codebase #

If you're starting a new Angular project today: ship on v22. Use Signal Forms, signals everywhere, httpResource, the new template improvements. The framework's strongest endorsements are the right defaults.

If you're maintaining an existing app: ng update, smoke-test, ship the update. Most apps will see no behavior changes. Opportunistically adopt Signal Forms when you touch each form file. Plan the @angular/aria migration for a future quarter.

If you're on Angular ≤17: budget more time for the upgrade. The signal era (v17→v22) is a substantive change; running ng update between distant majors works but expect to address deprecations along the way.

Try it yourself #

The fastest way to feel v22 is to scaffold a fresh project:

npx -p @angular/cli@22 ng new hello-v22 --style=css --ssr=false
cd hello-v22
ng serve --open

Open app.ts. Notice: no NgModule, no BrowserModule, no zone.js reference. Signals are imported by default. The application config has provideZonelessChangeDetection() (lesson 3.5). The form file (if you generate one with ng g component my-form) uses signal forms.

If you wire up Claude Code with ng mcp enabled, you can ask:

YouI just upgraded to Angular v22. What’s the lowest-effort migration that gives me the biggest payoff?
Claude · used onpush_zoneless_migrationThree steps, in order: (1) Run ng update @angular/cli @angular/core — handles the version bumps and the easy code transformations. (2) Run the signal-input/signal-query migrations: ng generate @angular/core:signal-input-migration and :signal-queries-migration. This converts @Input() decorators to input() signal functions and @ViewChild to viewChild() — purely mechanical, low risk. (3) Audit your @angular/cdk/a11y imports and start the slow migration to @angular/aria as you touch components. After step 1 you’re on v22; after step 2 you’re using the modern signal APIs everywhere; after step 3 you’re on the long-term-supported a11y package.

Angular v22 is the most polished release in recent memory. The signal era is no longer the future of the framework — it's the present. Upgrade when you can; the migration is straightforward, the wins are real, and v23 builds on this foundation directly.

Further reading #

Go build something. v22 makes it easier than any prior release.

Up next in Angular

More from this topic

View all Angular articles →

Enjoyed this article?

Get new Angular tutorials delivered. No spam — just code-first articles when they ship.

Leave a Comment

Your email stays private. Required fields are marked *

Leave a Comment

Your email stays private. Required fields are marked *