Angualr 22v what actually chnaged

ποΈ Angular v22 β The Experimental Era Is Over
Theme of this issue: Angular v22 doesn't ship one big thing. It ships ten things that all became ready at once. That's actually more significant.
π― Hook
Angular 22 dropped this week and the headline isn't a single killer feature.
It's a phase change β every reactive API the team has been building for two years just went stable on the same day.
Signal Forms. resource(). httpResource(). OnPush by default. And one genuinely new thing that nobody saw coming: WebMCP.
π₯ Hot Take
π¬ My take: The Angular team just made "high performance" the floor, not the ceiling. OnPush by default means you can't accidentally build a slow app anymore β you have to explicitly opt into the old behavior. That's the right call, and it should've happened two versions ago. But better late than right on time.
π° Top Articles
1. Announcing Angular v22
TL;DR: The official release post. Signal Forms, resource() API, and OnPush all hit stable. WebMCP is experimental. Breaking change: router params inheritance now defaults to 'always'.
My take: Read the migration guide before you upgrade. The Eager markers the automated migration adds are your to-do list β not a finished job.
2. Angular 22: Key Features and Changes
TL;DR: The most thorough breakdown of v22 available right now. Covers every feature with code examples, including the linkedSignal custom set option and WebMCP implicit form tools.
My take: Bookmark this. It's what the official docs should look like on day one.
3. Angular 22: The Most Important New Features at a Glance
TL;DR: ANGULARarchitects' summary with a focus on what enterprise teams need to watch.
My take: If you lead a team and need to sell "why upgrade now", this is your ammo.
π οΈ Feature Deep Dives with Code
1. OnPush Is Now the Default
New components get high-performance change detection for free. No more remembering to set it.
The automated migration adds ChangeDetectionStrategy.Eager to your existing components β that's the new name for the old "check always" default.
// Angular 22 β new component, no changeDetection property needed
@Component({
selector: 'app-counter',
template: `<p>{{ count() }}</p>`
})
export class CounterComponent {
count = signal(0); // OnPush + signals = just works
}
// What the migration adds to your EXISTING components:
@Component({
selector: 'app-legacy',
changeDetection: ChangeDetectionStrategy.Eager, // your cleanup todo list
template: `{{ value }}`
})
export class LegacyComponent {
value = 'I still use the old model';
}
Search your codebase for
Eagerafter migrating. Every hit is a candidate for signal refactoring.
2. Signal Forms β Now Stable
The form() API you tried in v21 is now production-ready. No breaking changes. No experimental label.
import { Component, signal } from '@angular/core';
import { form, FormField, required, email } from '@angular/forms/signals';
interface LoginData {
email: string;
password: string;
}
@Component({
selector: 'app-login',
imports: [FormField],
template: `
<form (submit)="onSubmit($event)">
<input type="email" [formField]="loginForm.email" />
@if (loginForm.email().touched() && loginForm.email().invalid()) {
@for (error of loginForm.email().errors(); track error) {
<p class="error">{{ error.message }}</p>
}
}
<input type="password" [formField]="loginForm.password" />
<button type="submit" [disabled]="loginForm().invalid()">Log In</button>
</form>
`
})
export class LoginComponent {
loginModel = signal<LoginData>({ email: '', password: '' });
loginForm = form(this.loginModel, (f) => {
required(f.email, { message: 'Email is required' });
email(f.email, { message: 'Please enter a valid email' });
required(f.password, { message: 'Password is required' });
});
onSubmit(event: Event) {
event.preventDefault();
if (this.loginForm().valid()) {
console.log(this.loginModel()); // typed, clean, no subscription needed
}
}
}
3. httpResource() β Stable
Reactive HTTP without leaving the signal graph. Auto-refetches when the input signal changes.
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()) {
<h1>{{ user.value().name }}</h1>
<p>{{ user.value().email }}</p>
} @else if (user.error()) {
<p>Failed to load user.</p>
}
<button (click)="nextUser()">Next User</button>
`
})
export class UserProfileComponent {
userId = signal(1);
// Re-fetches automatically when userId() changes β no subscribe, no pipe
user = httpResource<User>(() => `/api/users/${this.userId()}`);
nextUser() {
this.userId.update(id => id + 1);
}
}
4. @Service Decorator β Less Noise, Same Power
@Service() is @Injectable({ providedIn: 'root' }) with a name that says what the class is.
// Before (Angular 21 and earlier)
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class CartService {
items = signal<CartItem[]>([]);
}
// After (Angular 22)
import { Service } from '@angular/core';
@Service()
export class CartService {
items = signal<CartItem[]>([]);
// Root-provided singleton. Tree-shakeable. No config object needed.
}
5. injectAsync β Lazy-Load Services on Demand
Split a heavy service into its own chunk. Load it only when used.
import { Component, injectAsync } from '@angular/core';
import { onIdle } from '@angular/core';
@Component({
selector: 'app-report',
template: `<button (click)="export()">Export to PDF</button>`
})
export class ReportComponent {
// Loaded lazily β bundler puts ReportExporter in a separate chunk
private exporter = injectAsync(
() => import('./report-exporter').then(m => m.ReportExporter),
{ prefetch: () => onIdle({ timeout: 2_000 }) } // prefetch when browser is idle
);
async export() {
const exporter = await this.exporter();
exporter.exportToPdf();
}
}
6. WebMCP β Your App as an AI Tool (Experimental)
This is the one to watch. Register your Angular services as callable tools for in-browser AI agents.
// app.config.ts β application-wide MCP tool
import { Service, inject, provideExperimentalWebMcpTools } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
@Service()
class SearchService {
search(query: string): string[] {
return [`Result 1 for "${query}"`, `Result 2 for "${query}"`];
}
}
bootstrapApplication(AppRoot, {
providers: [
provideExperimentalWebMcpTools([
{
name: 'search_products',
description: 'Search the product catalog.',
inputSchema: {
type: 'object',
properties: { query: { type: 'string' } },
required: ['query']
},
execute: (args: { query: string }) => {
const svc = inject(SearchService);
const results = svc.search(args.query);
return { content: [{ type: 'text', text: results.join('\n') }] };
}
}
])
]
});
Signal Forms + WebMCP β the combo that makes it click:
// The form itself becomes a callable AI tool. No manual schema. No extra wiring.
readonly registrationForm = form(
this.model,
(f) => {
required(f.firstName, { message: 'First name is mandatory.' });
required(f.lastName, { message: 'Last name is mandatory.' });
},
{
experimentalWebMcpTool: {
name: 'registerUser',
description: 'Registers a new user in the system.'
},
submission: {
action: async (value) => {
await this.userService.register(value);
}
}
}
);
// Angular infers the JSON schema from your model's initial values.
// Validators map to required fields. Submission connects to the agent flow.
// An agent can now call this form the same way a human would fill it.
β οΈ Still experimental. Needs Chrome behind a flag + polyfill for now. Don't ship this to prod yet β but absolutely start playing with it.
7. Breaking Change: Router Params Inheritance
// Before v22 β you had to climb the parent chain manually:
const id = this.route.parent?.parent?.snapshot.params['id'];
// After v22 β params are inherited from all ancestor routes by default:
const id = this.route.snapshot.params['id']; // just works
// If you relied on the OLD behavior, restore it explicitly:
provideRouter(routes, withRouterConfig({ paramsInheritanceStrategy: 'emptyOnly' }));
π‘ Dev Tip of the Week
After running ng update @angular/core@22, grep your codebase for ChangeDetectionStrategy.Eager. Each result is a component still running the old "check everything on every event" model. Refactor them to signals one at a time β this is your Angular 22 modernization roadmap in a single command.
grep -r "ChangeDetectionStrategy.Eager" src/ --include="*.ts" -l
Why it matters: Every file in that list is a performance optimization waiting to happen. The migration did the safe thing β it preserved behavior. Now you do the right thing.
π€ Community Question
β WebMCP means your Angular form can be called directly by an AI agent running in the browser. Is that genuinely useful, or are we solving a problem that doesn't exist yet? Where would you actually use it first?
π What I'm Learning / Building
Angular 22 landed. The OnPush by default change is directly relevant β some of our legacy components are going to need the Eager audit. WebMCP is the thing I want to prototype on the weekend though. The idea of exposing a form as a typed tool for an agent is one of those features that sounds weird until suddenly it's the only way you'd want to build anything.
π Sources for This Issue
- [[Areas/Newsletter/Inbox/2026-06-06-angular-v22-release]]
- Source: https://angular.love/angular-22-key-features-and-changes
- Source: https://www.angulararchitects.io/en/blog/angular-22-the-most-important-new-features-at-a-glance/
- Source: https://www.cmarix.com/blog/latest-angular-version/