When you update to Angular v22, what does the Eager change-detection migration do to existing components, and why?
answer
- the default flipped in v22
- update keeps old behaviour
- missing value gets filled in
- Default renamed, OnPush untouched
basics
~10 sBecause v22 made OnPush the default, the v22 update migration adds changeDetection: ChangeDetectionStrategy.Eager to every component that set no value and renames explicit Default to Eager, so existing components behave exactly as before.
solid answer
~40 sIn v22 a component without a `changeDetection` value became `OnPush` by default; the old always-check behaviour is now called `ChangeDetectionStrategy.Eager`, and `Default` is a deprecated alias of it. To keep upgrades behaviour-preserving, the `change-detection-eager` migration, registered for version 22.0.0 so `ng update` applies it, walks every `@Component` from `@angular/core`: if `changeDetection` is missing it inserts `changeDetection: ChangeDetectionStrategy.Eager` and adds the import; if it is `ChangeDetectionStrategy.Default` it rewrites it to `Eager`; `OnPush` is left alone. So after the update **nothing has become OnPush**. Getting the benefit of the new default is a separate, deliberate step: remove `Eager` from components one at a time once they are verified to work under `OnPush`.
code
ts · 22 lines// Before the v22 update
import { Component } from '@angular/core';
@Component({
selector: 'app-cart-summary',
template: `{{ items.length }} items`,
})
export class CartSummary {
items: string[] = [];
}
// After the v22 update migration
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'app-cart-summary',
template: `{{ items.length }} items`,
changeDetection: ChangeDetectionStrategy.Eager,
})
export class CartSummary {
items: string[] = [];
}go deeper
Remember that since v22 components are OnPush unless they say otherwise, and that the update adds Eager to keep old components unchanged.
Explain the three cases the migration handles: missing value, explicit Default, explicit OnPush, and why Default is now a deprecated alias.
Plan the adoption: find every Eager the migration added, remove it where components are verified, and check the components it skipped.
Weigh how fast to retire Eager across teams, balancing the performance gain of OnPush against regression risk in older components.
Angular v22 changed a default that every component depends on. Understanding what the update does about it, and what it deliberately does not do, is the difference between thinking an app "is OnPush now" and knowing it is not. ## The default that changed | | Before v22 | Since v22 | | :-- | :-- | :-- | | Component with no `changeDetection` | Always checked (the strategy then called `Default`) | `OnPush` | | Name of the always-check strategy | `ChangeDetectionStrategy.Default` | `ChangeDetectionStrategy.Eager` | | `ChangeDetectionStrategy.Default` | The default | A deprecated alias of `Eager`, due to be removed | If an existing app simply picked up the new default, every component that relied on being checked on every pass, for example one that mutates an array in place or updates a plain field from a callback, could stop refreshing. That is the kind of silent behaviour change a major update must not introduce behind your back. ## What the migration does The migration is named `change-detection-eager` and is registered in `@angular/core`'s update migrations for **version 22.0.0**, described as adding `ChangeDetectionStrategy.Eager` to all components. When the update to v22 runs it, it visits every class decorated with `@Component` imported from `@angular/core`: 1. **No `changeDetection` property**: it inserts `changeDetection: ChangeDetectionStrategy.Eager` into the metadata and adds the `ChangeDetectionStrategy` import from `@angular/core` if needed. 2. **`changeDetection: ChangeDetectionStrategy.Default`**: it rewrites `Default` to `Eager`, after checking that the identifier really is `ChangeDetectionStrategy` imported from `@angular/core`. 3. **`changeDetection: ChangeDetectionStrategy.OnPush`**: nothing to do; the component already opted in. ## What it skips - **Directives.** Only `@Component` has a change-detection strategy, so directives are untouched. - **Components whose decorator argument is not a single object literal**, for example metadata built in a variable or helper. The migration returns early; you must add `Eager` by hand or verify the component under `OnPush`. - **A `changeDetection` value that is not a plain `ChangeDetectionStrategy.Default` property access**, such as a constant defined elsewhere: it is left as it is. ## Why the migration adds code instead of doing nothing This follows a pattern visible across the v22 update migrations: when a default changes, the update **pins the old behaviour explicitly**, and adopting the new default is left to you. Other v22 migrations do the same, for example adding `withXhr()` where the XHR backend is used after `FetchBackend` became HttpClient's default, and adding `withNoIncrementalHydration()` where incremental hydration was not enabled. The upgrade stays mechanical; behaviour changes stay reviewable. ## Update migrations versus modernisation schematics The Eager migration belongs to a different family from the control-flow, standalone, `inject()` or signal schematics: | | Update migrations (such as `change-detection-eager`) | Modernisation schematics (such as `control-flow`) | | :-- | :-- | :-- | | Registered in | `@angular/core`'s update migrations, with a target version | `@angular/core`'s schematic collection | | When they run | When the update to that version runs | Only when you run `ng generate @angular/core:<name>` | | Goal | Keep behaviour identical across a breaking change | Rewrite code into the current style | | Example | In v22: Eager added, and `strictTemplates: false` written where the tsconfig set no value | Any of them, whenever you choose to run it | Knowing which family a change came from tells you how to review it: an update migration's diff should change nothing observable, while a modernisation diff is a deliberate refactor. ## What to do after the update - **Do not assume the app is `OnPush`.** Search for `ChangeDetectionStrategy.Eager`: every hit is a component still checked on every pass. - **Remove `Eager` component by component**, starting with leaf and presentational components, once each is verified to update correctly under `OnPush`. - **New components need nothing**: without a `changeDetection` value they get `OnPush`. - **Replace any remaining `Default`** in code the migration could not reach, since it is deprecated. Why a component might misbehave under `OnPush`, and how signals, inputs and events mark it for check, belong to the change-detection strategies topic. The migration's promise is narrower: the day after the update, every existing component is checked exactly as it was the day before.
- After the v22 update, is an existing Angular app using OnPush by default?Not in practice. The update migration added changeDetection: ChangeDetectionStrategy.Eager to every component that had no value, so they are all still always-checked. Only components written after the update, or ones where you remove Eager, get the new OnPush default.
- Why does Angular's Eager migration skip some components?It only edits a @Component from @angular/core whose argument is a single object literal. Metadata built in a variable or helper, or a changeDetection value that is not a plain ChangeDetectionStrategy.Default access, is left alone, so those components need a manual check.
saying these in an interview costs you the question
- After ng update to v22 every existing component runs as OnPush.
- The Eager migration converts components to OnPush for you.
- ChangeDetectionStrategy.Default was removed in v22, so code using it no longer compiles.
- The migration also adds Eager to directives.
- Explicit OnPush components are switched back to Eager by the migration.