Angular 22 is here this release is all about signals reaching full maturity.Let's explore the new features, improvements, deprecations, and breaking changes in this release.
Angular 22 was released on June 3, 2026, and this release is all about signals reaching full maturity. From signal-based forms graduating to a public API, to OnPush becoming the default change detection strategy, Angular 22 solidifies signals as the foundation of the framework.
Letβs dive into whatβs new, whatβs changed, and what you need to know before upgrading.
Want to see it in action? Iβve built a live demo app showcasing signal forms, linkedSignal, debounced signals, injectAsync, and more.
Whatβs New in Angular 22
OnPush Change Detection by Default
Components with an undefined changeDetection property are now OnPush by default. This is a significant shift. If you need the previous behavior, specify changeDetection: ChangeDetectionStrategy.Eager explicitly.
The Default strategy has been renamed to Eager:
declareenumChangeDetectionStrategy {
/**
* Use the `CheckOnce` strategy, meaning that automatic change detection is deactivated
* until reactivated by setting the strategy to `Default` (`CheckAlways`).
* Change detection can still be explicitly invoked.
* This strategy applies to all child directives and cannot be overridden.
*/
OnPush=0,
/**
* Use the `Eager` strategy, meaning that the component is checked eagerly when the change
* detection traversal reaches it, rather than only checking under certain circumstances (e.g.
* `markForCheck`, a signal in the template changed, etc).
*/
Eager=1,
/**
* Use the default `CheckAlways` strategy, in which change detection is automatic until
* explicitly deactivated.
* @deprecated Use `Eager` instead.
*/
Default=1
}
A migration schematic is provided to add ChangeDetectionStrategy.Eager where applicable during ng update.
If you use Angular Material, all Material components now use ChangeDetectionStrategy.Eager.
My recommendation: Use OnPush for all new components. For existing ones, migrate incrementally and make sure you have enough unit and integration tests to catch any change detection issues during the migration.
The @Service Decorator
Angular 22 introduces @Service() as a simpler alternative to @Injectable({ providedIn: 'root' }). Itβs auto-provided by default and enables lazy loading via injectAsync(). Try the injectAsync demo.
When you use [formField] on an input thatβs also inside a [formGroup], Angularβs ControlValueAccessor (CVA) and signal forms both try to control the same input. Adding ngNoCva tells Angular to skip the CVA:
You only need ngNoCva when mixing [formField] with reactive forms ([formGroup], formControlName, or ngModel). If youβre using [formField] standalone or on a custom FormValueControl component, thereβs no conflict.
SignalFormControl for Reactive Forms Compatibility
SignalFormControl lets you use a signal form control inside a traditional reactive form, making incremental migration much easier:
You can now debounce signals directly, great for search inputs, form validation, and other performance-sensitive reactive patterns. Try the debounced signals demo.
import { debounced } from'@angular/core';
// debounced() returns a Resource<T> - value updates after 300ms idle
Incremental hydration is now the default behavior for server-side rendered apps, no more opting in. This significantly improves Time to Interactive (TTI) for SSR apps. Try the hydration demo.
exportconstappConfig:ApplicationConfig= {
providers: [
provideClientHydration(withEventReplay()),
// Incremental hydration is now enabled by default in Angular 22!
],
};
Resource API Enhancements
The Resource API gets several powerful additions in Angular 22. Try the resource demo.
SSR Transfer Cache
The new id option caches the resource value in TransferState during SSR, so the client skips the re-fetch after hydration:
Without the id, the resource would still work but would re-fetch on the client after hydration - unnecessary if the data is already available from SSR.
Resource Params Status
You can now control a resourceβs state by throwing IDLE or LOADING from the params function:
Previously ?. returned null, which was inconsistent with JavaScript. Now it returns undefined as expected:
// Angular 22: ?. returns undefined (not null)
{{ config().features?.darkMode }}
// result: undefined (not null) when features is missing
@if (config().features?.darkMode !==undefined) {
Dark mode is {{ config().features?.darkMode }}
}
HTML Comments in Templates
HTML comments are now preserved in the rendered DOM - useful for debugging and documentation:
<!-- This HTML comment is preserved in the rendered DOM! -->
<divclass="result-card comment-demo">
<p>Inspect this element in DevTools - you'll see HTML comments in the DOM.</p>
<!-- Section: user-visible content -->
<pclass="highlight">This paragraph has comments above and below it.</p>
<!-- End section -->
</div>
data- Attributes No Longer Bind Inputs
data- prefixed attributes are now treated as plain HTML data attributes - useful for testing IDs, analytics hooks, and CSS selectors. If you were relying on data- attributes to bind component inputs, youβll need to use a different attribute name:
<app-tag-badge
[label]="tag.label"
[color]="tag.color"
[attr.data-tag-id]="tag.id"
data-testid="tag-badge"
/>
Router Updates and deprecations
The router gets some quality-of-life improvements in Angular 22. Try the router demo.
Params Inheritance by Default
paramsInheritanceStrategy now defaults to 'always' - all child routes automatically inherit parent route parameters:
// Route config:
{
path: 'teams/:teamId',
children: [{
path: 'members/:memberId',
component: RouteChild,
// RouteChild gets BOTH teamId AND memberId
}]
}
// In RouteChild component:
readonly teamId =input<string>(); // β inherited from parent!
readonly memberId =input<string>(); // β own route param
Navigate to a page with no ?tab β tab = undefined
Every time thereβs no tab in the URL, it gets wiped to undefined. Always.
undefinedIfStale:
Navigate to ?tab=settings β tab = "settings"
Navigate to a page with no ?tab β tab = undefined (because the router set it before, so it cleans up after itself)
But if tab was set by your own code (like a parent component passing [tab]="something"), and the URL never had ?tab - the router leaves it alone. It doesnβt touch what it didnβt set.
browserUrl on Router Links
browserUrl lets you display a different URL in the browser than the actual route - useful for vanity/pretty URLs:
<!-- Normal navigation -->
<arouterLink="/teams/1/members/2">Member 2</a>
<!-- Navigate to real route but show a vanity URL -->
<arouterLink="/teams/1/members/2"
[browserUrl]="'/team-alpha/member-2'">
Member 2 (vanity URL)
</a>
<!-- Browser shows: /team-alpha/member-2 -->
CanMatchFn Requires currentSnapshot
The currentSnapshot parameter is now required, giving you access to the current route state for conditional matching:
// Before (Angular β€21) - currentSnapshot was optional
No more opting into strict mode - itβs the default in TS 6.0:
// tsconfig.json in TS 5.x (opt-in)
{
"compilerOptions": {
"strict": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"noImplicitAny": true
}
}
// tsconfig.json in TS 6.0 (all on by default!)
{
"compilerOptions": {
// "strict": true is now the DEFAULT
// No need to set it explicitly
}
}
Temporal API Types
TypeScript 6.0 ships built-in type definitions for the Temporal API - the modern replacement for Date:
constnow= Temporal.Now.instant();
console.log(now.toString());
constdate= Temporal.PlainDate.from('2026-01-15');
consttoday= Temporal.Now.plainDateISO();
constdiff= today.until(date);
console.log(diff.days); // days between
Map.getOrInsert() / getOrInsertComputed()
New Map methods that atomically get an existing value or insert a default. getOrInsert(key, default) uses a static default value, while getOrInsertComputed(key, fn) lazily computes it. This eliminates the common βcheck-then-setβ pattern:
constfreq=newMap<string, number>();
for (constwordof words) {
constcount= freq.getOrInsert(word, 0);
freq.set(word, count +1);
}
// Lazily compute and cache the transformed value
consttransformCache=newMap<string, string>();
transformCache.getOrInsertComputed(key, (k) =>
k.split('').reverse().join('').toUpperCase()
);
RegExp.escape()
Safely escapes special regex characters in a string - essential when building patterns from user input:
constuserInput='price is $9.99 (USD)';
constescaped= RegExp.escape(userInput);
// Result: "price\ is\ \$9\.99\ \(USD\)"
constregex=newRegExp(escaped, 'g');
consttext='The price is $9.99 (USD). Done.';
constmatches= text.match(regex);
New Defaults for TypeScript
TypeScript 6.0 is the bridge release preparing the ecosystem for TypeScript 7.0, which will be rewritten in Go for dramatically faster compilation. TS 6.0 modernizes the default compiler options so new projects start with sensible, modern settings.
Setting
Old Default (TS 5.x)
New Default (TS 6.0)
strict
false
true
module
"commonjs"
"nodenext"
target
"es3"
"esnext"
types
(all @types)
(explicit only)
rootDir
(inferred)
"./src"
esModuleInterop
false
true
These new defaults only affect projects that donβt explicitly set these options. Existing projects with explicit settings in tsconfig.json are unaffected.
Tip: If you get type errors when using new TS 6.0 features like Temporal, Map.getOrInsert(), or RegExp.escape(), add the following to your tsconfig.json:
Angular 22 introduces experimental WebMCP support with provideWebMcpTools and declareWebMcpTool, enabling AI tool integration at the framework level. Try the WebMCP demo.
The Testability service no longer relies on Zone.js for stability - it now uses PendingTasks, which works in both zoned and zoneless apps:
constpendingTasks=inject(PendingTasks);
constcleanup= pendingTasks.add();
awaitdoAsyncWork();
cleanup(); // app becomes "stable" again
Hammer.js Integration Removed
Hammer.js integration has been removed from @angular/platform-browser. Use your own implementation for touch gesture support.
Style Cleanup on Component Destroy
Styles are now removed from the DOM when their associated host component is destroyed. If you relied on styles persisting after a component is destroyed, move those styles to a global stylesheet.
Compiler: Duplicate Selectors Throw at Compile Time
Elements with multiple matching selectors will now throw at compile time.
Compiler: in Variables Throw in Template Expressions
The in keyword in template expressions now refers to the operator, and variables named in will throw.
Upgrade Package
Deprecated getAngularLib/setAngularLib have been removed - use getAngularJSGlobal/setAngularJSGlobal instead.
Both MatDialog and MatBottomSheet now support passing bindings directly, making it easier to configure dialog/bottom-sheet content dynamically without relying on data injection.
// Open a dialog with reactive input bindings (no data bag!)
ComponentPortal now supports directives, expanding the flexibility of CDK portals.
Previously, ComponentPortal only accepted component types. In v22, you can attach a directive to a portal outlet, useful for injecting behavior without rendering a host element.
// Previously: ComponentPortal only accepted components
// Now: you can attach directives too
constportal=newComponentPortal(MyDirective);
portalOutlet.attach(portal);
Typography Mixins for Material Design
New granular SCSS mixins let you apply Material typography to individual components instead of the entire app. Pair with define-theme() to customise type scales.
MatTabs now supports configuring separate animation durations for entering and leaving animations.
[animationDuration] now accepts an object with separate body and header durations, giving finer control over tab transitions.
// Uniform duration (string)
<mat-tab-group animationDuration="500ms">
// Separate body/header durations (new in Material 22)
// Type: MatTabGroupAnimationDuration = string | number | {
// body: string | number;
// header: string | number;
// }
ARIA Components Stabilized
The @angular/cdk ARIA package components are now out of developer preview. New test harnesses have been added for combobox, grid, listbox, menu, tabs, toolbar, and tree, making them easier to test.
Combobox Promoted to Stable
SimpleCombobox has been promoted to Combobox. The legacy combobox and autocomplete implementations have been removed.
Google Maps: gmp-click Event Support
The Google Maps component now supports the gmp-click event for advanced marker interactions, providing richer event data including the exact LatLng of the click.
// Google Maps: gmp-click event support (Angular 22)
<google-map[center]="center"[zoom]="14">
<map-advanced-marker
[position]="markerPos"
(gmpClick)="onMarkerClick($event)"
/>
</google-map>
// The gmp-click event provides richer data than the legacy
// click event, including the LatLng of the click point.
Components Breaking Changes
ARIA: Legacy Combobox Removed
The legacy combobox and autocomplete implementations have been removed. Use the new standalone Combobox (formerly SimpleCombobox). All simple-combobox prefixed symbols and selectors have been renamed to combobox.
CDK Breaking Changes
CDK_DESCRIBEDBY_HOST_ATTRIBUTE and CDK_DESCRIBEDBY_ID_PREFIX removed
MESSAGES_CONTAINER_ID removed
injector parameter of ConfigurableFocusTrap and FocusTrap constructors is now required
Boolean parameter of ConfigurableFocusTrapFactory.create replaced with a config object
DropListRef.dropevent parameter is now required
ContextMenuTracker renamed to MenuTracker
Material Breaking Changes
MatListOption.checkboxPosition removed, use togglePosition instead
MatListOptionCheckboxPosition renamed to MatListOptionTogglePosition
ArrowViewState and ArrowViewStateTransition removed from MatSort
values Renamed to value
The values input/model has been renamed to value in Combobox, Listbox, Tree, Menu, Toolbar, and Select. Update your templates accordingly.
Constructors with Rest Arguments Removed
Many constructors with rest arguments have been removed. If you were extending Material/CDK components, update your super calls.
Angular CLI Changes
New Features
Karma to Vitest Migration
The Karma to Vitest migration schematic (refactor-jasmine-vitest) is now stable. The CLI includes comprehensive migration tooling that handles:
Fake async to Vitest fake timers migration
Jasmine spy API transformations
TSConfig globals updates
Istanbul coverage provider support for Vitest
Detailed migration reports
Strict Templates by Default
New workspaces now rely on strict template checking by default, without needing to opt in.
Chunk Optimization Enabled by Default
Chunk optimization is now enabled by default with smart heuristics, improving bundle sizes out of the box.
Subresource Integrity Validation
The application builder now validates subresource integrity for dynamically loaded modules, improving security.
platform Option Stabilized
The experimentalPlatform option in the application builder has been renamed to platform, indicating itβs now stable.
Vitest Improvements
isolate option added to the unit-test builder
quiet option to suppress build noise during tests
Istanbul coverage support in the Vitest runner
Runtime Zone.js detection in the Vitest unit test runner
AI Config with Angular MCP Server
The ai-config schematic now includes Angular MCP server configuration.
Terminal window
# The Angular MCP server provides tools for AI agents:
$npx@angular/climcp
# What the MCP server exposes to AI agents:
# β Project structure discovery (list_projects)
# β Angular best practices (get_best_practices)
# β Official documentation search (search_documentation)
# β Code examples for modern features (find_examples)
# New projects (ng new) automatically include .vscode/mcp.json!
CLI Breaking Changes
@angular-devkit/architect-cli Removed
The @angular-devkit/architect-cli package is no longer available. The architect CLI tool has been moved to @angular-devkit/architect.
Experimental Jest and Web Test Runner Builders Removed
The experimental @angular-devkit/build-angular:jest and @angular-devkit/build-angular:web-test-runner builders have been removed. Use Vitest instead.
Dev Server PORT Environment Variable Priority
ng serve now assigns the highest priority to the PORT environment variable, overriding angular.json and --port flag configurations.
istanbul-lib-instrument Now Optional
istanbul-lib-instrument is now an optional peer dependency. Projects using Karma with code coverage need to ensure itβs installed manually. ng update will add it automatically.
SSR Host Validation Stricter
The server no longer falls back to Client-Side Rendering when a request fails host validation. Requests with unrecognized Host headers now return 400 Bad Request.
CLI Deprecations
Webpack builders in @angular-devkit/build-angular, use @angular/build builders instead
@angular-devkit/build-webpack, use @angular/build builders instead
CommonEngine APIs in @angular/ssr, use AngularNodeAppEngine or AngularAppEngine instead
@ngtools/webpack loader and plugin, use @angular/build instead
Run ng update. Automatic schematics will handle most changes, including adding ChangeDetectionStrategy.Eager where needed
Remove withFetch() from provideHttpClient() calls
Replace reportProgress with reportUploadProgress / reportDownloadProgress
Replace ComponentFactoryResolver usage with direct component class references
Update min/max validators that pass string values to use numbers instead
Conclusion
Angular 22 is a landmark release. With OnPush as the default change detection strategy, signal forms graduating to stable, and the Fetch backend becoming the default HTTP implementation, this version cements signals as the core reactive primitive of Angular.
The framework continues to get faster, smaller, and more developer-friendly with every release.
For me the best feature of this release is Signal Forms which are stable now and set method on linkedSignal which is a game changer for derived state.
What are your favorite Angular 22 features? Let me know on Twitter @santoshyadavdev
Explore further:
Live Demo - Try all the Angular 22 features in action
If you enjoyed this post and want to stay up to date with the latest in web development, subscribe to my weekly newsletter WeeklyFive where I share five curated links every week on Angular, web dev, and developer tools.
Shout out to my GitHub Sponsors and Subscribers for supporting my work on Open Source.