Angular v22 ships on June 3, 2026 with Signal Forms stable, Angular Aria accessibility primitives, declarative Signal-based data fetching, OnPush as default, and a Fetch-first HTTP client. Here's everything you need to know — with code examples, migration strategies, and practical adoption advice.
Angular v22 was released June 3, 2026. It's the most significant Angular release since v17's standalone components — shifting the entire framework toward a signal-first architecture. Signal Forms graduate to stable, OnPush becomes the default change detection, the HTTP client uses Fetch by default, and the new Angular Aria module provides first-class accessibility. Released alongside is v22.0.1 (June 10) with security fixes including TransferState hardening and SVG attribute sanitization.
Graduated from developer preview — production-ready reactive forms built on Angular Signals.
First-class WCAG-compliant accessibility primitives — ARIA directives, keyboard navigation, screen reader support.
Declarative Signal-based data fetching with automatic loading/error states and chain() for dependent resources.
Components without explicit changeDetection now default to OnPush. Automated migration via schematics.
HttpClient now uses FetchBackend by default. XHR mode opt-in via withXhr(). Deprecates withFetch().
Dropped support for TypeScript 5.x and Node.js 20. Requires TS 6.0+ and Node.js v22+.
New dedicated decorator for injectable services with factory function type safety.
ComponentFactoryResolver and ComponentFactory removed. Pass component classes directly.
Let's go through each feature in depth — what it does, why it matters, and how to use it today.
The biggest feature in Angular v22 is the graduation of Signal Forms from developer preview to stable. After months of refinement — including lazy field initialization, generic union support, debounced async validation, parse error signals, configurable submit behavior, and custom control reset propagation — Signal Forms are now ready for production workloads.
Signal Forms replace both template-driven and reactive forms with a unified, signal-based API. Instead of
FormControl, FormGroup, and FormArray classes, you get reactive field
primitives backed by Angular Signals — meaning automatic, granular change detection without zone.js polling.
export class LoginComponent {
form = new FormGroup({
email: new FormControl('', [Validators.required, Validators.email]),
password: new FormControl('', [Validators.required, Validators.minLength(8)]),
});
get email() { return this.form.get('email'); }
onSubmit() {
if (this.form.valid) {
this.authService.login(this.form.value);
}
}
}
import { signalForm, formField } from '@angular/forms';
export class LoginComponent {
form = signalForm({
email: formField('', { validators: [required, email] }),
password: formField('', { validators: [required, minLength(8)] }),
});
// Form state is reactive via Signals
emailError = computed(() => {
const field = this.form.controls.email;
if (field.touched() && field.invalid()) {
return field.getError('email') ?? 'Invalid email';
}
return null;
});
onSubmit() {
if (this.form.valid()) {
this.authService.login(this.form.value());
}
}
}
Key differences: Signal Forms track validity, touched, dirty, and errors through Signals rather than observable
streams. There's no .get() with string paths — you access fields as typed properties. The schema
definition is compact and composable, supporting nested groups, dynamic arrays, and union types natively.
| Feature | Reactive Forms | Signal Forms |
|---|---|---|
| Change Detection Trigger | Zone.js (polling) | Signal (push, granular) |
| Error Access | Subscription to statusChanges | Signal — field.invalid() |
| Async Validation | AsyncValidatorFn + debounceTime | validateAsync with debounce option |
| HTTP Validation | Manual service calls | validateHttp — built-in |
| Type Safety | Partial | Full — generic unions supported |
| Dynamic Arrays | FormArray | FieldArray — signal-based |
| Per-Field Errors | form.get('field').errors | field.getError() — type-safe |
| Lazy Init | All controls created upfront | Lazy field instantiation |
Angular v22 ships Angular Aria as a stable module — a comprehensive set of accessibility primitives that make WCAG compliance a built-in framework feature rather than a third-party concern.
Angular Aria includes directives for:
import { AriaModule } from '@angular/aria';
@Component({
selector: 'app-modal',
standalone: true,
imports: [AriaModule],
template: `
<div role="dialog" ariaModal ariaLabelledBy="modal-title">
<h2 id="modal-title">Confirm</h2>
<p>Are you sure you want to delete this item?</p>
<div class="actions" ariaKeyboardNav>
<button (click)="confirm()">Delete</button>
<button (click)="cancel()">Cancel</button>
</div>
</div>
`
})
export class ConfirmModalComponent {}
The ariaKeyboardNav directive handles arrow key navigation among focusable children, which is
essential for toolbars, menu bars, tab lists, and radio groups. Combined with ariaModal for
focus trapping and ariaLiveAnnouncer for dynamic updates, Angular Aria eliminates the need
for third-party accessibility wrappers like Reach UI or Radix.
httpResource and its sibling rxResource represent a paradigm shift in how Angular applications fetch and manage remote data. Instead of managing request lifecycle manually with subscriptions, loading flags, and error states, you declare the data dependency as a Signal — Angular handles the rest.
import { httpResource } from '@angular/common/http';
@Component({
selector: 'app-user-profile',
template: `
@if (user.isLoading()) {
<p>Loading...</p>
} @else if (user.error()) {
<p>Error: {{ user.error() }}</p>
} @else {
<h2>{{ user.value().name }}</h2>
<p>{{ user.value().email }}</p>
}
`
})
export class UserProfileComponent {
userId = input<number>();
// Declarative — httpResource reacts when userId changes
user = httpResource<User>(() => `/api/users/${this.userId()}`, {
defaultValue: { name: '', email: '' }
});
}
httpResource returns an object with three reactive properties: value (the data Signal),
isLoading (a boolean Signal), and error (an error Signal). When the request URL
changes (e.g., the userId input updates), the resource automatically re-fetches and transitions through
loading → success/error states.
Real applications rarely fetch a single resource. httpResource.chain() lets you express
dependent data fetching without nested subscriptions or manual coordination:
// Team details load automatically after selectedTeamId is known
team = httpResource<Team>(() => `/api/teams/${this.selectedTeamId()}`);
// Team members load when team data is available
teamMembers = this.team.chain(
(team) => `/api/teams/${team.id}/members`,
{ defaultValue: [] as Member[] }
);
// Team members' tasks load from members
teamTasks = this.teamMembers.chain(
(members) => `/api/tasks?assigneeIds=${members.map(m => m.id).join(',')}`,
{ defaultValue: [] as Task[] }
);
Each chain() call creates a derived resource that automatically invalidates and re-fetches
when its parent resource changes. The chain handles race conditions — if a parent re-fetches before a
child resolves, the stale child request is cancelled automatically.
For Observable-based data sources, rxResource provides the same declarative API over
arbitrary RxJS observables — useful for WebSocket streams, event emitters, or legacy service integrations.
Starting in Angular v22, components with no explicit changeDetection setting
default to ChangeDetectionStrategy.OnPush. This is a breaking change that
aligns Angular with modern reactive patterns.
To preserve the old behavior, explicitly set:
@Component({
selector: 'app-legacy',
changeDetection: ChangeDetectionStrategy.Eager, // was Default
template: `...`
})
export class LegacyComponent {}
The ng update schematic automatically identifies components that relied on the old
default and adds ChangeDetectionStrategy.Eager where needed. However, the long-term
direction is clear: pair OnPush with Signals for optimal performance. Signals naturally trigger
change detection only in the components that consume them, making zone.js increasingly optional.
If you've been using Signals, computed, and effect in your Angular 21
codebase, this change is transparent — your components already behave like OnPush because signal
reads in templates are tracked and re-rendered granularly. The difference is now enforced at
the component level, catching components that mutated state directly without going through Signals
or immutables.
Angular v22 flips the default HTTP backend from XMLHttpRequest (XHR) to the modern
Fetch API. The HttpClient now uses FetchBackend by default,
bringing streaming response support, better error handling, and improved performance characteristics.
withFetch() is now deprecated — it was previously required to opt into the Fetch backend,
but it's now the default and can be safely removed. If you need the XHR backend (e.g., for upload
progress reporting), opt in explicitly:
// Default in v22 — Fetch is used automatically
provideHttpClient()
// Opt into XHR if you need upload progress callbacks
provideHttpClient(withXhr())
The schematics migration adds withXhr() automatically where provideHttpClient
was previously called without arguments, preserving backward compatibility. New projects should
remove withFetch() from their provider configuration.
Other HTTP improvements in v22:
reportUploadProgress and reportDownloadProgress replace the old reportProgress option (now deprecated)HttpClient.jsonp and HttpClientJsonpModule are deprecated in favor of standard HTTPAngular v22 drops support for TypeScript 5.x (including TS 5.8 and 5.9) and Node.js 20. You must upgrade to:
This enables Angular to leverage TS 6.0's improved type inference, const type parameters, and
the new using declarations for resource management. The compiler-cli also adds
explicit support for Node.js 26's new APIs.
The ng update schematic adds "strictTemplates": true to your tsconfig
and disables the nullishCoalescingNotNullable and optionalChainNotNullable
diagnostics that the TS 6.0 upgrade may trigger on existing templates. You can disable these
manually if needed.
A new dedicated @Service decorator for injectable services provides better factory
function type safety and marks the service as injectable with a clearer intent than the generic
@Injectable({ providedIn: 'root' }) pattern. The decorator is marked as stable in v22.
import { Service } from '@angular/core';
@Service()
export class UserService {
constructor(private http: HttpClient) {}
getUsers(): Signal<ResourceState<User[]>> {
return httpResource(() => '/api/users');
}
}
A new injectAsync helper function enables asynchronous dependency injection —
useful for lazy-loading services, dynamic providers, or dependencies that require async initialization.
paramsInheritanceStrategy now defaults to 'always', meaning route parameters
are inherited from all parent routes. provideRoutes() has been removed — use
provideRouter() or the ROUTES multi token. Component input binding gains
options for unmatched input behavior.
Angular v22 enables incremental hydration by default for server-side rendered applications. Instead of hydrating the entire page at once, the framework hydrates components as they become visible or interactive — reducing Time to Interactive (TTI) significantly.
ComponentFactoryResolver and ComponentFactory — pass component classes directly to ViewContainerRef.createComponent()createNgModuleRef — use createNgModule insteadChangeDetectorRef.checkNoChanges — use fixture.detectChanges() in testsprovideRoutes() — use provideRouter() or ROUTES multi tokengetAngularLib/setAngularLib — use getAngularJSGlobal/setAngularJSGlobal@angular/platform-server — deprecated, use standard fetch APIs
While Angular Aria was developed over several releases, v22 finalises it as a stable, documented
module. The changelog also notes an important fix: ARIA property bindings are no longer
renamed to attributes (commit f008045ded), ensuring that dynamic ARIA
bindings in templates work correctly without unexpected attribute expansions.
Here's the step-by-step migration checklist for upgrading from Angular 21 to 22:
npm install typescript@~6.0ng update @angular/core@22 @angular/cli@22 — this handles:
ChangeDetectionStrategy.Eager where neededprovideRoutes() → provideRouter()provideHttpClient configurationstrictTemplates and disabling nullable diagnosticswithXhr() for upload progress usersViewContainerRef.createComponent()min and max validators no longer accept string values — use numbers or nullwithFetch() from provider config (it's now the default)ChangeDetectionStrategy.Eager or migrate to Signalsdata- prefixed attributes no longer bind inputs/outputs; in variables throw in template expressions; duplicate selectors trigger compile-time errorsYes — for most projects. The signal-first direction is clearly Angular's future, and v22 offers a stable foundation for that transition. Key considerations:
I've been building Angular applications since Angular 2 (2016) and have migrated codebases from v2 through v21. The v22 upgrade is one of the smoothest major transitions yet — the schematics are comprehensive, and the signal-first architecture genuinely improves developer experience and application performance. If you need help planning or executing the migration, reach out.
I build production Angular applications and can help with v22 migration, architecture, or full-stack development. Free initial consultation.