
Angular 22 Is Here: Signal Forms, Stable Resources, and the Signal-First Era

D. Rout
June 12, 2026 10 min read
On this page
Angular 22 dropped on June 3, 2026, and it feels different from every release before it. This isn't a feature dump — it's a consolidation. The two-year experiment with Signals is over. Signal Forms and the Resource API (resource(), httpResource()) are now fully production-stable, OnPush is the new default change detection strategy, and a fresh set of DI ergonomics (@Service, injectAsync) make the framework cleaner than it's ever been.
If you've been holding off on adopting signal-based patterns in your Angular apps, this is the release that gives you the green light. This tutorial walks through every major change with real code, a migration reference table, and a working demo project you can clone today.
GitHub repo: angular22-whats-new — three runnable demos covering
httpResource(), Signal Forms, andinjectAsync().
Prerequisites {#prerequisites}
Before diving in, make sure your environment meets Angular 22's requirements:
- Node.js 22+ (Node 20 support is dropped in v22)
- TypeScript 6+ (TypeScript 5.x is no longer supported)
- **Angular CLI 22**: `npm install -g @angular/cli@22`
- Familiarity with Angular Signals basics — if you're new to signals, check out our Angular Signals deep-dive first.
1. Stable Signal Forms: No More Experimental Label {#signal-forms-stable}
Signal Forms were introduced as experimental in Angular 21. As of Angular 22, they are fully stable and production-ready. The API has not changed materially — but the guarantee has: no more breaking changes without a deprecation cycle.
What Signal Forms look like
import { Component, computed } from '@angular/core';
import { formGroup, formControl } from '@angular/forms';
@Component({
selector: 'app-product-search',
standalone: true,
template: `
<div [formGroup]="searchForm">
<input [formControl]="searchForm.controls.query" placeholder="Search…" />
<select [formControl]="searchForm.controls.category">
<option value="">All</option>
<option value="electronics">Electronics</option>
</select>
</div>
<p>Valid: {{ searchForm.valid() }}</p>
<p>Value: {{ searchForm.value() | json }}</p>
`,
})
export class ProductSearchComponent {
// No FormBuilder, no constructor injection — just a function call
readonly searchForm = formGroup({
query: formControl('', { required: true, minLength: 2 }),
category: formControl(''),
});
// Derived signal — automatically recomputes when the form value changes
readonly filteredResults = computed(() => {
const { query } = this.searchForm.value();
return query.length >= 2 ? PRODUCTS.filter(p => p.name.includes(query)) : [];
});
}
Notice what's gone: no FormBuilder, no constructor injection, no valueChanges subscription. searchForm.valid() and searchForm.value() are plain signals — they compose with computed(), effect(), and everything else in the signal graph.
What's new in v22 Signal Forms
Angular 22 also ships several additions to the Signal Forms API:
- Submission API — a first-class
submit()handler with built-in pending/error state signals validateStandardSchema— integrate Zod, Valibot, or any Standard Schema validator declaratively- Conditional CSS classes — bind class names directly to form state signals
- Reactive Forms interop — bridge legacy
AbstractControlinstances into the signal graph for gradual migration
2. Stable Resource API: resource(), rxResource(), httpResource() {#resource-api-stable}
The entire Resource API family graduates to stable in Angular 22. All three functions are production-ready:
| Function | Use case |
|---|---|
resource() |
Custom async loader with full control |
rxResource() |
RxJS-based loader (returns Observable) |
httpResource() |
Typed HTTP GET shorthand via HttpClient |
httpResource() in practice
httpResource() is the fastest path for reactive data fetching. It accepts a signal-returning URL factory and wires loading, error, and value state automatically:
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',
standalone: true,
template: `
<input type="number" [value]="userId()" min="1" max="10"
(input)="userId.set(+$any($event.target).value)" />
@if (userResource.isLoading()) {
<p>Loading…</p>
} @else if (userResource.error()) {
<p class="error">Failed to load user</p>
} @else {
<h2>{{ userResource.value()?.name }}</h2>
<p>{{ userResource.value()?.email }}</p>
}
`,
})
export class UserProfileComponent {
readonly userId = signal(1);
// Re-fetches automatically whenever userId() changes
readonly userResource = httpResource<User>(
() => `/api/users/${this.userId()}`
);
}
When userId changes, the previous request is automatically cancelled and a fresh one is issued. No switchMap, no takeUntilDestroyed, no manual loading flag.
Custom resource() with reload
For cases where httpResource() is too thin, resource() gives you full control:
import { resource, signal } from '@angular/core';
const searchQuery = signal('angular');
const searchResults = resource({
request: () => ({ q: searchQuery() }),
loader: async ({ request, abortSignal }) => {
const res = await fetch(`/api/search?q=${request.q}`, { signal: abortSignal });
if (!res.ok) throw new Error('Search failed');
return res.json();
},
});
// Trigger a manual reload (e.g. after a mutation)
searchResults.reload();
The abortSignal passed to the loader is automatically triggered when the request becomes stale — your fetch() calls are cancelled for free.
3. OnPush Is Now the Default {#onpush-default}
This is the change that will affect every Angular developer upgrading from v21. New components now use OnPush change detection by default. The old Default (now renamed Eager) strategy must be opted into explicitly.
import { Component, ChangeDetectionStrategy } from '@angular/core';
// Angular 22: OnPush is implicit — no need to specify it
@Component({ selector: 'app-my', template: `…` })
export class MyComponent {}
// If you need the old behavior, set Eager explicitly
@Component({
selector: 'app-legacy',
changeDetection: ChangeDetectionStrategy.Eager,
template: `…`,
})
export class LegacyComponent {}
What happens during ng update? Any existing component that relied on the old implicit default has ChangeDetectionStrategy.Eager added automatically. You won't get regressions — but you should review those components and migrate them to OnPush + signals over time.
4. @Service Decorator {#service-decorator}
Every Angular developer has typed @Injectable({ providedIn: 'root' }) thousands of times. Angular 22 ships @Service() as a cleaner, more intentional alternative:
// Before — Angular 21 and earlier
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class UserStore { … }
// After — Angular 22
import { Service } from '@angular/core';
@Service()
export class UserStore { … }
@Service() is a strict superset of @Injectable({ providedIn: 'root' }) for the common case. It creates a tree-shakeable root-level singleton and enforces the inject() function over constructor injection. @Injectable isn't going anywhere — use it when you need a non-root scope or constructor injection for compatibility.
The Angular CLI now generates @Service() by default with ng generate service. You can revert to @Injectable with ng generate service --injectable.
5. injectAsync(): Lazy-Loaded Services {#inject-async}
injectAsync() brings true service-level code splitting to Angular. Large dependencies — PDF exporters, analytics SDKs, chart libraries — can now stay out of the initial bundle until they're actually needed:
import { Component, signal, injectAsync } from '@angular/core';
@Component({
selector: 'app-checkout',
standalone: true,
template: `
<button (click)="placeOrder()">Complete Order</button>
@if (orderPlaced()) { <p>✅ Order placed!</p> }
`,
})
export class CheckoutComponent {
readonly orderPlaced = signal(false);
// AnalyticsService is lazy — not in the initial bundle
// { onIdle: true } prefetches when the browser is idle
private readonly analytics = injectAsync(
() => import('../services/analytics.service').then(m => m.AnalyticsService),
{ onIdle: true }
);
async placeOrder() {
const analytics = await this.analytics(); // loads on first call
analytics.track('checkout_completed', { timestamp: Date.now() });
this.orderPlaced.set(true);
}
}
The onIdle option kicks off prefetching via requestIdleCallback while the user is reading the page, so the first interaction is near-instant even though the code wasn't in the initial bundle.
6. Other Notable Changes {#other-changes}
Template syntax expansions
Angular 22 expands what you can write directly in templates:
<!-- Inline arrow functions -->
<button (click)="items.update(list => [...list, newItem()])">Add</button>
<!-- Spread syntax -->
<app-card [config]="{ ...baseConfig, title: item.title }" />
<!-- Template comments -->
<!-- @component: ProductCard | owner: @deepakrout -->
<div class="card">…</div>
<!-- Multiple @switch cases -->
@switch (status) {
@case ('pending', 'processing') { <span class="badge yellow">In Progress</span> }
@case ('complete') { <span class="badge green">Done</span> }
}
Route parameter inheritance
paramsInheritanceStrategy now defaults to 'always', meaning child routes automatically inherit all parent route params. This eliminates the route.parent?.parent?.snapshot.params pattern in deeply nested routes.
HTTP client uses Fetch by default
The HttpClient now uses the browser's native fetch() API as its default transport. Zone.js patching of XMLHttpRequest is no longer in the hot path for HTTP calls.
TypeScript 6 required, Node 20 dropped
Angular 22 requires TypeScript 6 and drops Node 20 support. Node 22 and 26 are both supported.
Angular 22 Feature Reference {#reference-table}
| Feature | Status | Notes |
|---|---|---|
| Signal Forms | ✅ Stable | Production-ready; Submission API + Zod interop included |
resource() / httpResource() |
✅ Stable | Full replacement for RxJS async patterns |
| Angular Aria | ✅ Stable | Reactive accessibility utilities |
| OnPush as default | ✅ Stable | ng update adds Eager to existing components |
@Service() decorator |
✅ Stable | Replaces @Injectable({ providedIn: 'root' }) |
injectAsync() |
✅ Stable | Service-level code splitting with onIdle prefetch |
| HTTP Client via Fetch | ✅ Stable | Native fetch() replaces XHR as default transport |
| Route param inheritance | ✅ Stable | paramsInheritanceStrategy: 'always' is now the default |
debounced() signal |
🧪 Experimental | Debounced signal utility |
| WebMCP | 🧪 Experimental | Expose Angular services as AI agent tools |
@boundary error boundaries |
👀 Preview | Template-level error boundaries (v22.1 expected) |
| TypeScript 6 | ⚠️ Required | TypeScript 5.x no longer supported |
| Node 20 | ❌ Dropped | Node 22+ required |
Upgrading from Angular 21 {#upgrade}
The upgrade is straightforward for most apps:
# Update the CLI globally
npm install -g @angular/cli@22
# Migrate your project (runs automatic codemods)
ng update @angular/core@22 @angular/cli@22
Key things the migration does automatically:
- Adds
ChangeDetectionStrategy.Eagerto components that relied on the previous implicit default, preserving behavior - Updates
HttpClientimports for the new Fetch backend
After migrating, you can incrementally adopt OnPush + signals component-by-component at your own pace.
What's Next {#whats-next}
Angular 22 is a launchpad, not a destination. A few directions worth watching:
- Migrate your Reactive Forms to Signal Forms — now that Signal Forms are stable, it's the right time to start. Our Signal Forms tutorial covers the migration path end-to-end.
- Adopt
httpResource()in your data services — replaceBehaviorSubject+switchMappatterns with reactivehttpResource()calls. Our resource() API tutorial is now fully up to date for v22 stable. - Explore WebMCP — the experimental WebMCP layer lets you expose Angular forms and services as typed tools that AI agents can call directly. It's early, but it's the most forward-looking addition in v22.
- Audit your bundle with
injectAsync()— identify large services that could be lazy-loaded to reduce initial bundle size and improve Time to Interactive.
Further Reading {#further-reading}
- Angular v22 Official Release Page
- What's New in Angular 22.0 — Ninja Squad
- Angular 22: Most Important Features — Angular Architects
- Angular 22: Key Features and Changes — Angular.love
- Angular 22.0 Upgrade Guide — VersionLog
- Angular Signals Official Documentation
Try the Demo {#demo}
The full working project for this post lives at angular22-whats-new. Clone it, run npm install && npm start, and you'll have three interactive demos running locally:
/user-profile—httpResource()reacting to a signal-driven user ID picker/product-search— Signal Forms with acomputed()derived result list/checkout—injectAsync()keepingAnalyticsServiceout of the initial bundle
Angular 22 is the release where the framework's signal-first vision becomes the default. The patterns you've been reading about for two years are now the safe, supported, production way to build Angular apps. There's no better time to upgrade.
Read next
Comments (0)
Join the conversation
Sign in to leave a comment on this post.
No comments yet. to be the first!