Skip to content

Commit a8fd2d9

Browse files
authored
fix(angular): ngOnDestroy runs inside angular zone (#24949)
Resolves #22571
1 parent 9e84ef7 commit a8fd2d9

File tree

5 files changed

+53
-15
lines changed

5 files changed

+53
-15
lines changed

angular/src/directives/navigation/stack-controller.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export class StackController {
139139
enteringView.ref.changeDetectorRef.reattach();
140140

141141
return this.transition(enteringView, leavingView, animation, this.canGoBack(1), false, animationBuilder)
142-
.then(() => cleanupAsync(enteringView, views, viewsSnapshot, this.location))
142+
.then(() => cleanupAsync(enteringView, views, viewsSnapshot, this.location, this.zone))
143143
.then(() => ({
144144
enteringView,
145145
direction,
@@ -201,7 +201,7 @@ export class StackController {
201201
this.skipTransition = true;
202202
this.pop(1);
203203
} else if (this.activeView) {
204-
cleanup(this.activeView, this.views, this.views, this.location);
204+
cleanup(this.activeView, this.views, this.views, this.location, this.zone);
205205
}
206206
}
207207

@@ -294,20 +294,36 @@ export class StackController {
294294
}
295295
}
296296

297-
const cleanupAsync = (activeRoute: RouteView, views: RouteView[], viewsSnapshot: RouteView[], location: Location) => {
297+
const cleanupAsync = (
298+
activeRoute: RouteView,
299+
views: RouteView[],
300+
viewsSnapshot: RouteView[],
301+
location: Location,
302+
zone: NgZone
303+
) => {
298304
if (typeof (requestAnimationFrame as any) === 'function') {
299305
return new Promise<void>((resolve) => {
300306
requestAnimationFrame(() => {
301-
cleanup(activeRoute, views, viewsSnapshot, location);
307+
cleanup(activeRoute, views, viewsSnapshot, location, zone);
302308
resolve();
303309
});
304310
});
305311
}
306312
return Promise.resolve();
307313
};
308314

309-
const cleanup = (activeRoute: RouteView, views: RouteView[], viewsSnapshot: RouteView[], location: Location) => {
310-
viewsSnapshot.filter((view) => !views.includes(view)).forEach(destroyView);
315+
const cleanup = (
316+
activeRoute: RouteView,
317+
views: RouteView[],
318+
viewsSnapshot: RouteView[],
319+
location: Location,
320+
zone: NgZone
321+
) => {
322+
/**
323+
* Re-enter the Angular zone when destroying page components. This will allow
324+
* lifecycle events (`ngOnDestroy`) to be run inside the Angular zone.
325+
*/
326+
zone.run(() => viewsSnapshot.filter((view) => !views.includes(view)).forEach(destroyView));
311327

312328
views.forEach((view) => {
313329
/**

angular/test/test-app/e2e/src/nested-outlet.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ describe('Nested Outlet', () => {
2020
cy.ionPageVisible('app-nested-outlet-page2');
2121

2222
cy.get('ion-router-outlet ion-router-outlet app-nested-outlet-page2 h1').should('have.text', 'Nested page 2');
23+
24+
cy.get('#goto-nested-page1').click();
25+
cy.ionPageVisible('app-nested-outlet-page');
26+
27+
cy.get('#goto-nested-page2').click();
2328
});
29+
2430
});
2531

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
import { Component } from '@angular/core';
1+
import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
22

33
@Component({
44
selector: 'app-nested-outlet-page',
55
templateUrl: './nested-outlet-page.component.html',
66
})
7-
export class NestedOutletPageComponent {
7+
export class NestedOutletPageComponent implements OnDestroy, OnInit {
8+
9+
ngOnInit() {
10+
NgZone.assertInAngularZone();
11+
}
12+
13+
ngOnDestroy() {
14+
NgZone.assertInAngularZone();
15+
}
816
}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<ion-content>
2-
<h1>Nested page 2</h1>
3-
<p>
4-
<ion-button routerLink="/nested-outlet/page">Go To FIRST</ion-button>
5-
</p>
6-
</ion-content>
2+
<h1>Nested page 2</h1>
3+
<p>
4+
<ion-button routerLink="/nested-outlet/page" id="goto-nested-page1">Go To FIRST</ion-button>
5+
</p>
6+
</ion-content>
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
import { Component } from '@angular/core';
1+
import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
22

33
@Component({
44
selector: 'app-nested-outlet-page2',
55
templateUrl: './nested-outlet-page2.component.html',
66
})
7-
export class NestedOutletPage2Component {
7+
export class NestedOutletPage2Component implements OnDestroy, OnInit {
8+
9+
ngOnInit() {
10+
NgZone.assertInAngularZone();
11+
}
12+
13+
ngOnDestroy() {
14+
NgZone.assertInAngularZone();
15+
}
816
}

0 commit comments

Comments
 (0)