You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(angular): default to zoneless change detection (#31196)
Issue number: resolves#30804
---------
## What is the current behavior?
Currently, on `major-9.0`, `@ionic/angular` still defaulted to Zone.js.
The `ng add` schematic registered `provideZoneChangeDetection()`, and
`zone.js` was a required peer dependency on both `@ionic/angular` and
`@ionic/angular-server`. Angular 21 bootstraps zoneless out of the box,
so Ionic was actively opting new apps back into Zone.js.
## What is the new behavior?
Ionic 9 now defaults to zoneless change detection while keeping Zone.js
as a supported opt-in. The `ng add` schematic no longer registers
`provideZoneChangeDetection()`, and `zone.js` is now an optional peer
dependency on both `@ionic/angular` and `@ionic/angular-server`.
## Does this introduce a breaking change?
- [X] Yes
- [ ] No
Apps that want to keep Zone.js on Angular 21 must opt back in with
`provideZoneChangeDetection()` AND keep `import 'zone.js'` in their
polyfills (Angular 21's default scaffold omits it). Under zoneless,
state updated from async callbacks Angular doesn't wrap needs a signal
or a `markForCheck()` call. Template bindings, `@HostListener`, reactive
forms, and synchronous lifecycle-hook state are unaffected. Angular 18
through 20 keep Angular's Zone.js default and need no change.
BREAKING.md is updated with the full migration path.
## Other information
- Test apps run dual-mode for coverage: ng21 is zoneless, ng18-20 stay
on Zone.js. Shared `base/` test pages use the new `assertZoneContext()`
helper (`base/src/app/zone-assert.util.ts`), which asserts in-zone only
when Zone.js is present, so the same pages pass in both modes.
Current dev build (2026-06-08):
```
8.8.9-dev.11781013468.1ed93da9
```
Test app:
https://ionic-framework-git-feat-zoneless-ionic1.vercel.app/angular/
Docs PR: ionic-team/ionic-docs#4541
Docs Preview URL:
https://ionic-docs-git-feat-zoneless-v2-ionic1.vercel.app/docs/updating/9-0#zoneless-change-detection
Copy file name to clipboardExpand all lines: BREAKING.md
+16-3Lines changed: 16 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -128,9 +128,17 @@ Apps that relied on `ionChange` firing on every confirmation (for example, to de
128
128
129
129
Ionic 9 requires Angular 18 or later. Angular 16 and 17 are no longer supported.
130
130
131
-
**Angular 21 Requires Explicit Zone Change Detection**
131
+
**Zoneless Change Detection by Default**
132
132
133
-
Angular 21 defaults `bootstrapModule()` and `bootstrapApplication()` to zoneless change detection. `zone.js` in your polyfills is ignored unless you opt back in explicitly, which surfaces as runtime `NG0909` errors and breaks change detection for asynchronous updates (modal and popover lifecycle, tab navigation, and anything depending on async-resolved state). Ionic 9 relies on zone-based change detection, so apps on Angular 21 must provide it explicitly.
133
+
Ionic 9 defaults to zoneless change detection. Angular 21 bootstraps zoneless out of the box, so a new Ionic 9 app on Angular 21 runs without Zone.js and requires no change-detection provider. The `ng add @ionic/angular` schematic no longer registers `provideZoneChangeDetection()`.
134
+
135
+
Because Zone.js no longer triggers change detection automatically, component state that you update from an asynchronous callback that Angular doesn't wrap (awaiting an overlay result such as `modal.onWillDismiss()`, `setTimeout`, RxJS subscriptions, `Platform` events) no longer re-renders on its own. Update a signal or call `ChangeDetectorRef.markForCheck()` in those callbacks. Template event bindings, `@HostListener`, reactive forms, and Ionic lifecycle hooks (`ionViewWillEnter`, etc.) that set state synchronously are unaffected. Refer to the [Zoneless Change Detection guide](https://ionicframework.com/docs/angular/zoneless) for the patterns.
136
+
137
+
On Angular 18 through 20, Zone.js remains Angular's default, so those versions are unaffected and require no change. To adopt zoneless there, add `provideZonelessChangeDetection()` (named `provideExperimentalZonelessChangeDetection()` on Angular 18 and 19).
138
+
139
+
**Keeping Zone.js on Angular 21 (optional)**
140
+
141
+
To keep using Zone.js on Angular 21, opt back in with `provideZoneChangeDetection()` and keep `zone.js` in your polyfills.
134
142
135
143
Standalone bootstrap:
136
144
@@ -160,7 +168,12 @@ NgModule bootstrap:
160
168
.catch((err) => console.error(err));
161
169
```
162
170
163
-
Angular forbids `provideZoneChangeDetection()` inside an NgModule's `providers` array, so for NgModule apps it must be passed as `applicationProviders` on the `bootstrapModule()` call. This step is only required on Angular 21. Angular 18 through 20 are unaffected.
171
+
Angular forbids `provideZoneChangeDetection()` inside an NgModule's `providers` array, so for NgModule apps it must be passed as `applicationProviders` on the `bootstrapModule()` call. Both paths also require `zone.js` in your polyfills, which Angular 21's default scaffold omits:
0 commit comments