Jul 15, 2026

Angular 21 to 22: Breaking Changes and How to Fix Them

Angular22UpgradeOnPushSignalFormsMigration

OnPush is the new default, route params inherit everywhere, and Node 22 is mandatory. Step-by-step guide to upgrade Angular 21 to 22 without surprises.

Angular 21 removed the old guard — Karma out, zone.js optional. Angular 22 goes further: it changes what a default component is.

For the first time, OnPush is the default change detection strategy, route parameters flow down the whole tree, and the framework doubles down on being agent-friendly infrastructure. Here is what breaks, and how to fix it.

TL;DR — upgrade Angular 21 to 22 in 4 steps:

node --version # must be 22+ (Node 20 is no longer supported) npm install -g @angular/cli@latest ng update @angular/core@22 @angular/cli@22 ng test

The three changes most likely to hit you: OnPush is the new default change detection (a migration adds ChangeDetectionStrategy.Eager to your existing components), route params now inherit with 'always' instead of 'emptyOnly', and Node 22 + TypeScript 6.0 are the new minimums. Details and fixes below.

🚨 The “Stop Everything” Breaking Changes

1. OnPush Is the Default Now

This is the philosophical headline of v22. A component that doesn’t set changeDetection used to mean “check me on every cycle.” In Angular 22 it means ChangeDetectionStrategy.OnPush.

What breaks: Nothing in your existing code — if you let the migration do its job. ng update adds the new ChangeDetectionStrategy.Eager value (the old check-always behavior, now with a name) to every component that relied on the old default.

What actually changes: Every new component you generate is OnPush. If your team’s habits still assume mutation-based updates (“I changed the property, why didn’t the view update?”), v22 is where those habits die.

The Fix:

  • Let the automatic migration stamp Eager on legacy components. Don’t fight it during the upgrade.
  • For new code, embrace signals — signal(), computed(), and input() make OnPush invisible because updates are tracked at the value level.
  • Migrate old components off Eager incrementally. The Angular MCP server’s onpush_zoneless_migration tool analyzes a component’s dependency graph before you flip the strategy — I covered it in the Angular MCP guide.

2. Route Params Inherit Everywhere

The router’s paramsInheritanceStrategy default changed from 'emptyOnly' to 'always'.

What breaks: Child routes now see every parent route’s params and data. If a child route reads a param name that also exists on a parent (:id on both a parent and child, for example), it may now resolve to a value it never saw before. Components with defensive “param is undefined” branches may silently take the other branch.

The Fix: Audit components that read route params or data. If you need the old behavior back, it’s one line:

provideRouter(routes, withRouterConfig({ paramsInheritanceStrategy: 'emptyOnly', }))

Prefer renaming colliding params (:userId vs :orderId) over reverting globally — the 'always' default is genuinely better for deep-linked detail pages.

3. Node 22 and TypeScript 6.0 Are Mandatory

What breaks: The build, immediately, if your CI or local environment runs Node 20 (now past end-of-life) or TypeScript 5.9.

The Fix: Upgrade Node to 22, 24, or 26 and TypeScript to 6.0+ before running ng update. Check your CI images, your Dockerfiles, and your engines field — the local upgrade is always the easy half.

4. Smaller Sharp Edges

  • Duplicate input bindings are a compile error. Binding the same input twice on one element used to be silently tolerated; now the template compiler rejects it.
  • data-* attributes are no longer treated as property bindings — they bind as attributes, which is what you almost certainly wanted anyway.
  • The HTTP transfer cache skips credentialed requests by default (cookie-bearing and withCredentials), so authenticated responses can’t leak across SSR sessions. If you relied on caching those, you now have to opt in deliberately.

✨ The New Toys: Features You’ll Actually Use

Signal Forms Are Stable

The experimental warnings are gone. The form()/field() model from v21 is now the blessed, typed, signal-first way to build forms. If you held off adopting them in 21, v22 is the green light.

resource() and httpResource Are Production-Ready

Async data as a signal, with loading and error states built in. Together with stable Signal Forms and OnPush-by-default, the signal-first architecture is no longer “the direction Angular is heading” — it’s just how Angular works.

Selectorless Components

Import a component and use it in the template directly — no string selector indirection, better type safety, easier refactoring.

The Agentic Story Continues

v22 extends the MCP + Skills stack introduced around v21: the CLI’s MCP server plus the skills layer that teaches agents to write signal-first, OnPush-correct code. I’ve written about this in depth: agent-safe component patterns and exhaustive types for hallucination-free agents.

Summary: The Checklist

  1. Node 22+, TypeScript 6.0+ — environment first.
  2. ng update @angular/core@22 @angular/cli@22 and let the Eager migration run.
  3. Audit route param reads (or pin 'emptyOnly' back).
  4. Run the test suite; fix duplicate input bindings the compiler now flags.
  5. Adopt Signal Forms and resource() in new code; peel components off Eager over time.

The official interactive checklist lives at angular.dev/update-guide, and the release announcement at angular.dev/events/v22.


Coming from an older version? Start with Angular 20 to 21: Breaking Changes and How to Fix Them, or see every migration path in the Angular Upgrade Guide.