diff --git a/packages/angular/common/src/providers/angular-delegate.ts b/packages/angular/common/src/providers/angular-delegate.ts index bde802ab115..bba50323591 100644 --- a/packages/angular/common/src/providers/angular-delegate.ts +++ b/packages/angular/common/src/providers/angular-delegate.ts @@ -233,6 +233,14 @@ export const attachView = ( applicationRef.attachView(componentRef.hostView); + /** + * Run change detection so template bindings (e.g. ``) + * 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; diff --git a/packages/angular/test/base/e2e/src/standalone/nav-modal-root.spec.ts b/packages/angular/test/base/e2e/src/standalone/nav-modal-root.spec.ts new file mode 100644 index 00000000000..36fa53d2ba3 --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/nav-modal-root.spec.ts @@ -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(); + }); +}); diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index c365195662d..d09b36f40aa 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -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) }, diff --git a/packages/angular/test/base/src/app/standalone/home-page/home-page.component.html b/packages/angular/test/base/src/app/standalone/home-page/home-page.component.html index 3ad10254b30..76d4bc564f7 100644 --- a/packages/angular/test/base/src/app/standalone/home-page/home-page.component.html +++ b/packages/angular/test/base/src/app/standalone/home-page/home-page.component.html @@ -54,6 +54,11 @@ Nav Test + + + Nav Modal Root Test + + Router Link Test diff --git a/packages/angular/test/base/src/app/standalone/nav-modal-root/nav-modal-root-page.component.ts b/packages/angular/test/base/src/app/standalone/nav-modal-root/nav-modal-root-page.component.ts new file mode 100644 index 00000000000..468580a9a06 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/nav-modal-root/nav-modal-root-page.component.ts @@ -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: ` + + + Nav Modal Root Page + + + + + + `, + standalone: true, + imports: [IonContent, IonHeader, IonTitle, IonToolbar], +}) +export class NavModalRootPageComponent {} diff --git a/packages/angular/test/base/src/app/standalone/nav-modal-root/nav-modal-root.component.ts b/packages/angular/test/base/src/app/standalone/nav-modal-root/nav-modal-root.component.ts new file mode 100644 index 00000000000..fb2747e4384 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/nav-modal-root/nav-modal-root.component.ts @@ -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: ` + + + Nav Modal Root + + + + Open Modal + + `, + 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(); + } +} diff --git a/packages/angular/test/base/src/app/standalone/nav-modal-root/nav-wrapper.component.ts b/packages/angular/test/base/src/app/standalone/nav-modal-root/nav-wrapper.component.ts new file mode 100644 index 00000000000..5e0e6a7919f --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/nav-modal-root/nav-wrapper.component.ts @@ -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: ``, + standalone: true, + imports: [IonNav], +}) +export class NavWrapperComponent { + @Input() rootPage: any; +}