Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/angular/common/src/providers/angular-delegate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,14 @@ export const attachView = (

applicationRef.attachView(componentRef.hostView);

/**
* Run change detection so template bindings (e.g. `<ion-nav [root]="rootPage">`)
* apply during this synchronous pass, before the web component runs its load
* lifecycle. `createComponent` only runs the creation pass, so without this the
* binding lands after the element has loaded, too late for `ion-nav` to read it.
*/
componentRef.changeDetectorRef.detectChanges();

elRefMap.set(hostElement, componentRef);
elEventsMap.set(hostElement, unbindEvents);
return hostElement;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { expect, test } from '@playwright/test';

test.describe('Nav: root binding inside a modal (standalone)', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/standalone/nav-modal-root');
});

test('should render the nav root when bound from componentProps', async ({ page }) => {
await page.locator('#open-nav-modal').click();

await expect(page.locator('ion-modal')).toBeVisible();
await expect(page.locator('ion-modal ion-nav')).toBeVisible();
await expect(page.locator('#nav-modal-root-content')).toBeVisible();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const routes: Routes = [
{ path: 'back-button', loadComponent: () => import('../back-button/back-button.component').then(c => c.BackButtonComponent) },
{ path: 'router-link', loadComponent: () => import('../router-link/router-link.component').then(c => c.RouterLinkComponent) },
{ path: 'nav', loadComponent: () => import('../nav/nav.component').then(c => c.NavComponent) },
{ path: 'nav-modal-root', loadComponent: () => import('../nav-modal-root/nav-modal-root.component').then(c => c.NavModalRootComponent) },
{ path: 'providers', loadComponent: () => import('../providers/providers.component').then(c => c.ProvidersComponent) },
{ path: 'overlay-controllers', loadComponent: () => import('../overlay-controllers/overlay-controllers.component').then(c => c.OverlayControllersComponent) },
{ path: 'button', loadComponent: () => import('../button/button.component').then(c => c.ButtonComponent) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
Nav Test
</ion-label>
</ion-item>
<ion-item routerLink="/standalone/nav-modal-root">
<ion-label>
Nav Modal Root Test
</ion-label>
</ion-item>
<ion-item routerLink="/standalone/router-link">
<ion-label>
Router Link Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Component } from '@angular/core';
import { IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/angular/standalone';

@Component({
selector: 'app-nav-modal-root-page',
template: `
<ion-header>
<ion-toolbar>
<ion-title>Nav Modal Root Page</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<div id="nav-modal-root-content">Root page rendered inside nav</div>
</ion-content>
`,
standalone: true,
imports: [IonContent, IonHeader, IonTitle, IonToolbar],
})
export class NavModalRootPageComponent {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Component, inject } from '@angular/core';
import { IonButton, IonContent, IonHeader, IonTitle, IonToolbar, ModalController } from '@ionic/angular/standalone';

import { NavModalRootPageComponent } from './nav-modal-root-page.component';
import { NavWrapperComponent } from './nav-wrapper.component';

@Component({
selector: 'app-nav-modal-root',
template: `
<ion-header>
<ion-toolbar>
<ion-title>Nav Modal Root</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-button id="open-nav-modal" (click)="openModal()">Open Modal</ion-button>
</ion-content>
`,
standalone: true,
imports: [IonButton, IonContent, IonHeader, IonTitle, IonToolbar],
})
export class NavModalRootComponent {
private modalController = inject(ModalController);

async openModal() {
const modal = await this.modalController.create({
component: NavWrapperComponent,
componentProps: {
rootPage: NavModalRootPageComponent,
},
});

await modal.present();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Component, Input } from '@angular/core';
import { IonNav } from '@ionic/angular/standalone';

/**
* Presented as a modal via `ModalController` with the nav root passed through
* `componentProps`. Reproduces #31220, where the `[root]` binding used to land
* after `ion-nav` had already loaded, leaving the nav empty.
*/
@Component({
selector: 'app-nav-wrapper',
template: `<ion-nav [root]="rootPage"></ion-nav>`,
standalone: true,
imports: [IonNav],
})
export class NavWrapperComponent {
@Input() rootPage: any;
}
Loading