Announcing Angular v22: Key Features and Code Examples
The Angular team has officially released Angular v22! This release marks a significant milestone in the framework's evolution, focusing heavily on performance-centric defaults, stabilizing modern reactive primitives, and refining dependency injection.
Whether you're starting a new application or preparing to migrate an existing codebase, understanding these new features will help you write faster, cleaner, and more maintainable code. Let's dive into the major additions with practical code examples.
1. OnPush Change Detection by Default
To improve application performance and guide developers toward best practices, ChangeDetectionStrategy.OnPush is now the default for all new components.
Instead of constantly checking the entire component tree for updates, OnPush components only re-evaluate when their inputs change, signals emit, or events are explicitly triggered.
The New Default Behavior
When you generate a component in Angular v22, it will behave as OnPush implicitly:
import { Component, input } from '@angular/core';
@Component({
selector: 'app-user-card',
template: `
<div>
<h3>Name: {{ name() }}</h3>
</div>
`
// changeDetection: ChangeDetectionStrategy.OnPush is implied by default!
})
export class UserCardComponent {
name = input.required<string>();
}
Opting Out: Using ChangeDetectionStrategy.Eager
If you need a component to use the old eager change detection behavior (previously known as Default), you must set it explicitly:
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-legacy-card',
template: `...`,
// Explicitly request eager check cycles
changeDetection: ChangeDetectionStrategy.Eager
})
export class LegacyCardComponent {}
2. The New @Service() Decorator
Angular v22 introduces the @Service() decorator as a cleaner, more ergonomic alternative to the traditional @Injectable({ providedIn: 'root' }) syntax for defining application-wide singletons.
- Implicit Root Scope: It automatically provisions the class as a tree-shakeable singleton at the root level.
- Mandatory
inject()function: To align with modern API guidelines, classes decorated with@Service()do not support constructor dependency injection. You must use the functionalinject()API to declare dependencies.
Before: Injectable Decorator
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class UserService {
constructor(private http: HttpClient) {}
}
After: Service Decorator
import { Service, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Service()
export class UserService {
// Constructor injection is bypassed entirely!
private http = inject(HttpClient);
}
3. Asynchronous Dependency Injection: injectAsync()
For applications with heavy dependencies (such as large PDF generation modules or graphing libraries), Angular v22 introduces injectAsync(). This API allows you to lazily load and resolve DI services asynchronously on-demand rather than bundling them in the initial payload.
Example: Lazy Loading a Report Exporter
import { Component, injectAsync } from '@angular/core';
@Component({
selector: 'app-report-panel',
template: `<button (click)="onExport()">Generate Report</button>`
})
export class ReportPanelComponent {
// Define the loader using a dynamic import
private loadExporter = injectAsync(
() => import('./report-exporter.service').then(m => m.ReportExporterService)
);
async onExport() {
// The dependency is fetched and resolved in the DI container on demand
const exporter = await this.loadExporter();
exporter.exportPdf();
}
}
4. Stable Signal-Based APIs (Forms & Resources)
Several reactive APIs that were experimental in previous versions have officially graduated to stable status in v22:
- Signal Forms: Provides fully type-safe form controls integrated directly with Angular Signals.
- Resource APIs: Utilities like
resource,rxResource, andhttpResourceare now production-ready, offering built-in state management for dynamic data requests.
Example: Reacting to Changes with httpResource
import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';
@Component({
selector: 'app-user-search',
template: `
<input (input)="onInput($event)" placeholder="Search users..." />
@if (userResource.isLoading()) {
<p>Loading...</p>
} @else {
<ul>
@for (user of userResource.value(); track user.id) {
<li>{{ user.name }}</li>
}
</ul>
}
`
})
export class UserSearchComponent {
searchQuery = signal('');
// Automatically triggers a refetch whenever searchQuery changes!
userResource = httpResource(() => `/api/users?q=${this.searchQuery()}`);
onInput(event: Event) {
const input = event.target as HTMLInputElement;
this.searchQuery.set(input.value);
}
}
5. Tooling and Runtime Updates
In addition to API enhancements, Angular v22 updates several core tools and dependencies:
- Vitest Integration: Vitest has replaced Karma and Jasmine as the default test runner, providing lightning-fast test execution.
- Native Fetch API: The framework's default HTTP client driver now uses the browser's native
FetchAPI under the hood instead ofXMLHttpRequest. - Environment Constraints: Angular v22 requires a minimum of Node.js 22 and TypeScript 6.
Conclusion
Angular v22 represents a significant step toward a fully reactive, signal-first ecosystem. By adopting defaults like OnPush, streamlining service creation with @Service(), and lazy loading with injectAsync(), you can build applications that load faster and operate with high runtime efficiency.