Skip to content

Commit 90acad1

Browse files
authored
fix(angular): NavController works with nested outlets (#28421)
Issue number: resolves #28417 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> The common `IonRouterOutlet` was trying to inject another common `IonRouterOutlet` into `parentOutlet`: https://github.com/ionic-team/ionic-framework/blob/dc94ae01fe9e6c1b570559f0c8308b31e96cc5bf/packages/angular/common/src/directives/navigation/router-outlet.ts#L119 None existed, so this field was `null`. This is a problem if developers are using the module `IonRouterOutlet` since parent router outlets will not be currently injected because Angular is trying to use the common `IonRouterOutlet` not the module `IonRouterOutlet`: https://github.com/ionic-team/ionic-framework/blob/main/packages/angular/src/directives/navigation/ion-router-outlet.ts. The same goes for the standalone `IonRouterOutlet`. This resulted in things such as `NavController.pop` not working in nested outlets because the parentOutlet was not defined. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - `IonRouterOutlet` now injects the correct router outlet instance for `parentOutlet` ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> Dev build: `7.5.3-dev.11698328998.1a79f815`
1 parent a5c68aa commit 90acad1

File tree

7 files changed

+73
-4
lines changed

7 files changed

+73
-4
lines changed
Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
1-
import { Directive } from '@angular/core';
1+
import { Location } from '@angular/common';
2+
import { Directive, Attribute, Optional, SkipSelf, ElementRef, NgZone } from '@angular/core';
3+
import { Router, ActivatedRoute } from '@angular/router';
24
import { IonRouterOutlet as IonRouterOutletBase } from '@ionic/angular/common';
35

46
@Directive({
57
selector: 'ion-router-outlet',
68
})
79
// eslint-disable-next-line @angular-eslint/directive-class-suffix
8-
export class IonRouterOutlet extends IonRouterOutletBase {}
10+
export class IonRouterOutlet extends IonRouterOutletBase {
11+
/**
12+
* We need to pass in the correct instance of IonRouterOutlet
13+
* otherwise parentOutlet will be null in a nested outlet context.
14+
* This results in APIs such as NavController.pop not working
15+
* in nested outlets because the parent outlet cannot be found.
16+
*/
17+
constructor(
18+
@Attribute('name') name: string,
19+
@Optional() @Attribute('tabs') tabs: string,
20+
commonLocation: Location,
21+
elementRef: ElementRef,
22+
router: Router,
23+
zone: NgZone,
24+
activatedRoute: ActivatedRoute,
25+
@SkipSelf() @Optional() readonly parentOutlet?: IonRouterOutlet
26+
) {
27+
super(name, tabs, commonLocation, elementRef, router, zone, activatedRoute, parentOutlet);
28+
}
29+
}

packages/angular/standalone/src/navigation/router-outlet.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { Directive } from '@angular/core';
1+
import { Location } from '@angular/common';
2+
import { Directive, Attribute, Optional, SkipSelf, ElementRef, NgZone } from '@angular/core';
3+
import { Router, ActivatedRoute } from '@angular/router';
24
import { IonRouterOutlet as IonRouterOutletBase, ProxyCmp } from '@ionic/angular/common';
35
import { defineCustomElement } from '@ionic/core/components/ion-router-outlet.js';
46

@@ -10,4 +12,23 @@ import { defineCustomElement } from '@ionic/core/components/ion-router-outlet.js
1012
standalone: true,
1113
})
1214
// eslint-disable-next-line @angular-eslint/directive-class-suffix
13-
export class IonRouterOutlet extends IonRouterOutletBase {}
15+
export class IonRouterOutlet extends IonRouterOutletBase {
16+
/**
17+
* We need to pass in the correct instance of IonRouterOutlet
18+
* otherwise parentOutlet will be null in a nested outlet context.
19+
* This results in APIs such as NavController.pop not working
20+
* in nested outlets because the parent outlet cannot be found.
21+
*/
22+
constructor(
23+
@Attribute('name') name: string,
24+
@Optional() @Attribute('tabs') tabs: string,
25+
commonLocation: Location,
26+
elementRef: ElementRef,
27+
router: Router,
28+
zone: NgZone,
29+
activatedRoute: ActivatedRoute,
30+
@SkipSelf() @Optional() readonly parentOutlet?: IonRouterOutlet
31+
) {
32+
super(name, tabs, commonLocation, elementRef, router, zone, activatedRoute, parentOutlet);
33+
}
34+
}

packages/angular/test/base/e2e/src/lazy/nested-outlet.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,9 @@ describe('Nested Outlet', () => {
2727
cy.get('#goto-nested-page2').click();
2828
});
2929

30+
// Fixes https://github.com/ionic-team/ionic-framework/issues/28417
31+
it('parentOutlet should be defined', () => {
32+
cy.get('#parent-outlet span').should('have.text', 'true');
33+
});
3034
});
3135

packages/angular/test/base/e2e/src/standalone/tabs.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,9 @@ describe('Tabs', () => {
1313
cy.get('app-tab-two').should('be.visible');
1414
cy.contains('Tab 2');
1515
});
16+
17+
// Fixes https://github.com/ionic-team/ionic-framework/issues/28417
18+
it('parentOutlet should be defined', () => {
19+
cy.get('#parent-outlet span').should('have.text', 'true');
20+
});
1621
});

packages/angular/test/base/src/app/lazy/nested-outlet-page/nested-outlet-page.component.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@ <h1>Nested page 1</h1>
44
<ion-button routerLink="/lazy/tabs" id="goto-tabs">Go To Tabs</ion-button>
55
<ion-button routerLink="/lazy/nested-outlet/page2" id="goto-nested-page2">Go To SECOND</ion-button>
66
</p>
7+
<p id="parent-outlet">
8+
Has Parent Outlet: <span>{{ hasParentOutlet }}</span>
9+
</p>
710
</ion-content>

packages/angular/test/base/src/app/lazy/nested-outlet-page/nested-outlet-page.component.ts

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

34
@Component({
45
selector: 'app-nested-outlet-page',
56
templateUrl: './nested-outlet-page.component.html',
67
})
78
export class NestedOutletPageComponent implements OnDestroy, OnInit {
9+
hasParentOutlet = false;
10+
11+
constructor(private routerOutlet: IonRouterOutlet) {
12+
this.hasParentOutlet = routerOutlet.parentOutlet != null;
13+
}
814

915
ngOnInit() {
1016
NgZone.assertInAngularZone();
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
import { Component } from '@angular/core';
2+
import { IonRouterOutlet } from '@ionic/angular/standalone';
23

34
@Component({
45
selector: 'app-tab-one',
56
template: `
67
Tab 1
8+
9+
<p id="parent-outlet">
10+
Has Parent Outlet: <span>{{ hasParentOutlet }}</span>
11+
</p>
712
`,
813
standalone: true,
914
})
1015
export class TabOneComponent {
16+
hasParentOutlet = false;
1117

18+
constructor(private routerOutlet: IonRouterOutlet) {
19+
this.hasParentOutlet = routerOutlet.parentOutlet != null;
20+
}
1221
}

0 commit comments

Comments
 (0)