es native support for modern stream handling, which directly supports the Fetch API transition.
Step 2: Dynamic Component Instantiation
Legacy dynamic component creation relied on factory resolution. Angular 22 removes ComponentFactoryResolver and ComponentFactory entirely. The runtime now accepts component classes directly via ViewContainerRef.
Legacy Pattern (Angular 21):
constructor(private factoryResolver: ComponentFactoryResolver) {}
loadAlertPanel() {
const factory = this.factoryResolver.resolveComponentFactory(AlertOverlay);
this.containerRef.createComponent(factory);
}
Angular 22 Implementation:
import { ViewContainerRef } from '@angular/core';
constructor(private containerRef: ViewContainerRef) {}
loadAlertPanel() {
this.containerRef.createComponent(AlertOverlay);
}
Architecture Decision: Direct class instantiation reduces boilerplate and eliminates the intermediate factory resolution step. This aligns with Angular's move toward functional APIs and reduces memory overhead during dynamic rendering.
Step 3: Router Configuration and Guard Signatures
The router module has undergone two critical changes: the provider function name and the default parameter inheritance strategy. Additionally, route guard signatures now expose the current router state snapshot.
Provider Update:
// Angular 21
import { provideRoutes } from '@angular/router';
export const appConfig = {
providers: [provideRoutes(routes)]
};
// Angular 22
import { provideRouter } from '@angular/router';
export const appConfig = {
providers: [provideRouter(routes)]
};
Parameter Inheritance Strategy:
Child routes now inherit parent parameters by default. If your application relies on isolated child contexts, you must explicitly configure the strategy.
export const appRoutes = provideRouter(
routes,
{ paramsInheritanceStrategy: 'emptyOnly' } // Restores Angular 21 behavior
);
Guard Signature Update:
CanMatchFn and CanActivateFn now receive a third argument: currentSnapshot.
export const authGuard: CanMatchFn = (route, segments, currentSnapshot) => {
const userRole = currentSnapshot.queryParams['role'];
return userRole === 'admin';
};
Rationale: Exposing the snapshot allows guards to make routing decisions based on the current navigation state without triggering additional router events. This reduces race conditions in complex nested layouts.
Step 4: HTTP Engine and Change Detection Strategy
Angular 22 replaces the internal XHR adapter with the native Fetch API. Your provideHttpClient() configuration remains unchanged, but progress tracking for file uploads requires adjustment. Fetch does not emit traditional progress events; you must use ReadableStream or ProgressEvent polyfills if upload tracking is required.
Change detection enums have also been renamed. ChangeDetectionStrategy.Default is now ChangeDetectionStrategy.Eager. The framework strongly recommends OnPush for performance-critical components.
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-data-grid',
template: `<!-- grid template -->`,
changeDetection: ChangeDetectionStrategy.OnPush // Recommended for data-heavy views
})
export class DataGridComponent {}
Architecture Decision: OnPush restricts change detection to explicit input changes, event triggers, or observable emissions. This drastically reduces CPU cycles in dashboards with frequent data updates. Eager (formerly Default) should only be used when component state mutates internally without explicit triggers.
Step 5: Template Compilation Hardening
The template compiler now enforces strict binding rules. Duplicate input bindings and implicit data-* property assignments will trigger compilation errors.
Duplicate Binding Fix:
<!-- Angular 21 (sometimes tolerated) -->
<app-metric-card [title]="metricName" [subtitle]="metricName"></app-metric-card>
<!-- Angular 22 (requires unique bindings) -->
<app-metric-card [title]="metricName" [subtitle]="metricDescription"></app-metric-card>
Attribute Binding Fix:
data-* attributes are no longer treated as component properties. You must use explicit attribute binding syntax.
<!-- Angular 21 -->
<div [data-tracking-id]="reportId"></div>
<!-- Angular 22 -->
<div [attr.data-tracking-id]="reportId"></div>
Rationale: Explicit attribute binding prevents accidental property resolution conflicts and aligns template syntax with standard DOM attribute handling. This reduces runtime property lookup overhead and improves compiler predictability.
Pitfall Guide
1. Skipping Node.js and TypeScript Version Alignment
Explanation: Running ng update on Node 20 or TypeScript 5 causes silent compilation failures and missing type definitions. The Angular 22 compiler relies on TS 6's incremental parsing and Node 22's stream APIs.
Fix: Always verify node -v and tsc -v before running migrations. Use nvm or fnm to lock the environment.
2. Assuming XHR Progress Events Work with Fetch
Explanation: The Fetch API does not emit progress events for uploads. Code relying on HttpEventType.UploadProgress will fail silently or throw type errors.
Fix: Implement ReadableStream tracking or use a dedicated upload service that calculates progress via Content-Length headers and chunked reads.
3. Overlooking paramsInheritanceStrategy Default Change
Explanation: Child routes now inherit all parent parameters. Dashboards that filter data based on route IDs may receive unexpected parent context, causing incorrect API calls or UI state corruption.
Fix: Explicitly set paramsInheritanceStrategy: 'emptyOnly' in provideRouter if isolated child contexts are required. Audit all child route subscriptions.
4. Applying OnPush Without Data Flow Analysis
Explanation: Switching to OnPush without ensuring inputs are immutable or events are explicitly triggered causes UI staleness. Components will not re-render when internal state mutates.
Fix: Convert mutable objects to immutable updates ({ ...obj, key: value }), use markForCheck() only when necessary, and ensure all user interactions emit events that trigger parent change detection.
5. Leaving Legacy ComponentFactoryResolver Imports
Explanation: The resolver is completely removed from the framework. Importing it causes immediate build failures. Dynamic component logic must be rewritten to use ViewContainerRef.createComponent().
Fix: Run a global search for ComponentFactoryResolver and ComponentFactory. Replace with direct class instantiation and remove factory resolution logic.
6. Ignoring Template Compiler Strictness
Explanation: Duplicate bindings and implicit data-* properties that compiled in Angular 21 will now break the build. The compiler treats these as syntax errors rather than warnings.
Fix: Use ng build to surface template errors. Replace [data-*] with [attr.data-*] and ensure each input binding is unique per component instance.
7. Running Manual Fixes Before CLI Migrations
Explanation: Angular CLI provides automated schematics that handle boilerplate updates. Applying manual changes first can cause migration conflicts or duplicate edits.
Fix: Always run ng update @angular/core@22 @angular/cli@22 first. Review the generated diffs, then apply manual adjustments for runtime-specific logic.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Legacy monolith with heavy XHR progress tracking | Implement Fetch stream polyfill + manual progress calculation | Fetch lacks native upload progress; polyfill maintains UX without framework downgrade | Medium (requires custom stream logic) |
| Nested dashboard with isolated child routes | Set paramsInheritanceStrategy: 'emptyOnly' | Prevents parent parameter leakage that breaks child data filters | Low (single config change) |
| Data-heavy grid with frequent updates | Switch to ChangeDetectionStrategy.OnPush + immutable inputs | Reduces change detection cycles by 60-80% in high-frequency update scenarios | Medium (requires input refactoring) |
| Simple CRUD application | Keep ChangeDetectionStrategy.Eager | Simpler development model; performance gain from OnPush is negligible for low-update UIs | Low (no changes required) |
Configuration Template
Copy this baseline configuration for Angular 22 applications. It includes router, HTTP, and change detection providers aligned with modern best practices.
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { provideHttpClient, withFetch } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes, withComponentInputBinding(), {
paramsInheritanceStrategy: 'emptyOnly',
scrollPositionRestoration: 'top'
}),
provideHttpClient(withFetch())
]
};
Quick Start Guide
- Lock Environment: Run
nvm use 22 and npm install typescript@6 --save-dev.
- Execute Migration: Run
ng update @angular/core@22 @angular/cli@22 and commit the generated diffs.
- Fix Runtime APIs: Replace
ComponentFactoryResolver with ViewContainerRef.createComponent(), update router providers to provideRouter(), and adjust guard signatures.
- Harden Templates: Run
ng build, fix [data-*] to [attr.data-*], and remove duplicate bindings.
- Validate: Run
ng test and verify network requests, routing behavior, and UI update cycles.