From 60c715bd8d3c8208b4ad06cbd5ca1d4953e2867d Mon Sep 17 00:00:00 2001 From: ShaneK Date: Wed, 11 Jun 2025 06:41:47 -0700 Subject: [PATCH 1/3] feat(modal): add IonModalToken for injecting modal element in Angular components --- packages/angular/common/src/index.ts | 14 +++++----- .../common/src/providers/angular-delegate.ts | 26 ++++++++++++++----- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts index 6e5c909acf3..447fb3e1c14 100644 --- a/packages/angular/common/src/index.ts +++ b/packages/angular/common/src/index.ts @@ -1,34 +1,34 @@ -export { MenuController } from './providers/menu-controller'; export { DomController } from './providers/dom-controller'; +export { MenuController } from './providers/menu-controller'; export { NavController } from './providers/nav-controller'; export { Config, ConfigToken } from './providers/config'; export { Platform } from './providers/platform'; -export { bindLifecycleEvents, AngularDelegate } from './providers/angular-delegate'; +export { AngularDelegate, bindLifecycleEvents, IonModalToken } from './providers/angular-delegate'; export type { IonicWindow } from './types/interfaces'; -export type { ViewWillEnter, ViewWillLeave, ViewDidEnter, ViewDidLeave } from './types/ionic-lifecycle-hooks'; +export type { ViewDidEnter, ViewDidLeave, ViewWillEnter, ViewWillLeave } from './types/ionic-lifecycle-hooks'; export { NavParams } from './directives/navigation/nav-params'; -export { IonPopover } from './overlays/popover'; export { IonModal } from './overlays/modal'; +export { IonPopover } from './overlays/popover'; export { IonRouterOutlet, provideComponentInputBinding } from './directives/navigation/router-outlet'; +export * from './directives/control-value-accessors'; export { IonBackButton } from './directives/navigation/back-button'; +export { IonNav } from './directives/navigation/nav'; export { RouterLinkDelegateDirective, RouterLinkWithHrefDelegateDirective, } from './directives/navigation/router-link-delegate'; -export { IonNav } from './directives/navigation/nav'; export { IonTabs } from './directives/navigation/tabs'; -export * from './directives/control-value-accessors'; export { ProxyCmp } from './utils/proxy'; -export { IonicRouteStrategy } from './utils/routing'; export { OverlayBaseController } from './utils/overlay'; +export { IonicRouteStrategy } from './utils/routing'; export { raf } from './utils/util'; diff --git a/packages/angular/common/src/providers/angular-delegate.ts b/packages/angular/common/src/providers/angular-delegate.ts index fc794c0e34b..dd4964ef1c1 100644 --- a/packages/angular/common/src/providers/angular-delegate.ts +++ b/packages/angular/common/src/providers/angular-delegate.ts @@ -1,13 +1,13 @@ import { ApplicationRef, - NgZone, - Injectable, - Injector, + ComponentRef, + createComponent, EnvironmentInjector, inject, - createComponent, + Injectable, InjectionToken, - ComponentRef, + Injector, + NgZone, } from '@angular/core'; import { FrameworkDelegate, @@ -22,6 +22,9 @@ import { NavParams } from '../directives/navigation/nav-params'; import { ConfigToken } from './config'; +// Token for injecting the modal element +export const IonModalToken = new InjectionToken('IonModalToken'); + // TODO(FW-2827): types @Injectable() @@ -142,8 +145,19 @@ export const attachView = ( * The modern approach is to access the data directly * from the component's class instance. */ + const providers = getProviders(params); + + // If this is an ion-modal, provide the modal element as an injectable + // so components inside the modal can inject it directly + if (container.tagName.toLowerCase() === 'ion-modal') { + providers.push({ + provide: IonModalToken, + useValue: container, + }); + } + const childInjector = Injector.create({ - providers: getProviders(params), + providers, parent: injector, }); From 460aa73d00f1972c9c96138822a026d2f9a711bb Mon Sep 17 00:00:00 2001 From: ShaneK Date: Fri, 13 Jun 2025 08:55:24 -0700 Subject: [PATCH 2/3] fix(modal): adding IonModalToken as an Angular standalone import --- packages/angular/standalone/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index 23debccc1cd..06d1ada64f9 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -21,6 +21,7 @@ export { Config, Platform, NavParams, + IonModalToken, IonicRouteStrategy, ViewWillEnter, ViewDidEnter, From c10e821ca3948a03e4723aa827fca501bcb3868c Mon Sep 17 00:00:00 2001 From: ShaneK Date: Tue, 17 Jun 2025 10:43:50 -0700 Subject: [PATCH 3/3] test(modal): adding programatic modal angular standalone tests for creating and accessing modals through angular --- packages/angular/test/apps/ng18/package.json | 4 ++-- packages/angular/test/apps/ng19/package.json | 4 ++-- .../modal/modal.component.html | 4 ++++ .../modal/modal.component.ts | 20 +++++++++++++++++++ .../programatic-modal.component.html | 2 ++ .../programatic-modal.component.ts | 18 +++++++++++++++++ .../programatic-modal.service.ts | 18 +++++++++++++++++ .../standalone/app-standalone/app.routes.ts | 1 + .../home-page/home-page.component.html | 5 +++++ .../modal/modal.component.html | 3 +++ .../modal/modal.component.ts | 11 ++++++++++ .../programatic-modal.component.html | 2 ++ .../programatic-modal.component.ts | 18 +++++++++++++++++ .../programatic-modal.service.ts | 18 +++++++++++++++++ 14 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/modal/modal.component.html create mode 100644 packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/modal/modal.component.ts create mode 100644 packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/programatic-modal.component.html create mode 100644 packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/programatic-modal.component.ts create mode 100644 packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/programatic-modal.service.ts create mode 100644 packages/angular/test/base/src/app/standalone/programatic-modal/modal/modal.component.html create mode 100644 packages/angular/test/base/src/app/standalone/programatic-modal/modal/modal.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/programatic-modal/programatic-modal.component.html create mode 100644 packages/angular/test/base/src/app/standalone/programatic-modal/programatic-modal.component.ts create mode 100644 packages/angular/test/base/src/app/standalone/programatic-modal/programatic-modal.service.ts diff --git a/packages/angular/test/apps/ng18/package.json b/packages/angular/test/apps/ng18/package.json index ac2241a1589..ee2648f26e8 100644 --- a/packages/angular/test/apps/ng18/package.json +++ b/packages/angular/test/apps/ng18/package.json @@ -29,8 +29,8 @@ "@angular/platform-server": "^18.0.0", "@angular/router": "^18.0.0", "@angular/ssr": "^18.0.0", - "@ionic/angular": "^8.0.0", - "@ionic/angular-server": "^8.0.0", + "@ionic/angular": "^8.6.0", + "@ionic/angular-server": "^8.6.0", "core-js": "^3.33.2", "express": "^4.15.2", "ionicons": "^7.2.0", diff --git a/packages/angular/test/apps/ng19/package.json b/packages/angular/test/apps/ng19/package.json index c7b6eff1c6a..198bdc39f11 100644 --- a/packages/angular/test/apps/ng19/package.json +++ b/packages/angular/test/apps/ng19/package.json @@ -29,8 +29,8 @@ "@angular/platform-server": "^19.0.0", "@angular/router": "^19.0.0", "@angular/ssr": "^19.0.2", - "@ionic/angular": "^8.4.0", - "@ionic/angular-server": "^8.4.0", + "@ionic/angular": "^8.6.0", + "@ionic/angular-server": "^8.6.0", "core-js": "^3.33.2", "express": "^4.18.2", "ionicons": "^7.2.0", diff --git a/packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/modal/modal.component.html b/packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/modal/modal.component.html new file mode 100644 index 00000000000..4a57a4808f8 --- /dev/null +++ b/packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/modal/modal.component.html @@ -0,0 +1,4 @@ +

+ modal works! + Close Modal +

diff --git a/packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/modal/modal.component.ts b/packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/modal/modal.component.ts new file mode 100644 index 00000000000..1e68a941fc4 --- /dev/null +++ b/packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/modal/modal.component.ts @@ -0,0 +1,20 @@ +import { Component, Inject } from '@angular/core'; +import { IonButton, IonModalToken } from "@ionic/angular/standalone"; + +@Component({ + selector: 'app-modal', + templateUrl: './modal.component.html', + imports: [IonButton], + standalone: true, +}) +export class ModalComponent { + constructor(@Inject(IonModalToken) private modalToken: HTMLIonModalElement) { + this.modalToken.onDidDismiss().then(() => { + console.log('Modal dismissed'); + }); + } + + public close() { + this.modalToken.dismiss(); + } +} diff --git a/packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/programatic-modal.component.html b/packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/programatic-modal.component.html new file mode 100644 index 00000000000..d614bd4d2b4 --- /dev/null +++ b/packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/programatic-modal.component.html @@ -0,0 +1,2 @@ +Open Modal + diff --git a/packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/programatic-modal.component.ts b/packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/programatic-modal.component.ts new file mode 100644 index 00000000000..0854741fe6e --- /dev/null +++ b/packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/programatic-modal.component.ts @@ -0,0 +1,18 @@ +import { Component } from '@angular/core'; +import { IonButton } from '@ionic/angular/standalone'; +import { ProgramaticModalService } from './programatic-modal.service'; + +@Component({ + selector: 'app-test', + templateUrl: './programatic-modal.component.html', + standalone: true, + imports: [IonButton] +}) +export class ProgramaticModalComponent { + constructor(private modalService: ProgramaticModalService) {} + + public open() { + this.modalService.open(); + } +} + diff --git a/packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/programatic-modal.service.ts b/packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/programatic-modal.service.ts new file mode 100644 index 00000000000..15390733951 --- /dev/null +++ b/packages/angular/test/apps/ng19/src/app/standalone/programatic-modal/programatic-modal.service.ts @@ -0,0 +1,18 @@ +import { inject, Injectable } from '@angular/core'; +import { ModalController } from "@ionic/angular/standalone"; +import { ModalComponent } from "./modal/modal.component"; + +@Injectable({ + providedIn: 'root' +}) +export class ProgramaticModalService { + #modalController = inject(ModalController); + + async open() { + const modal = await this.#modalController.create({ + component: ModalComponent, + focusTrap: true, + }); + await modal.present(); + } +} 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 596aee68fdc..1550f8ef66f 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 @@ -11,6 +11,7 @@ export const routes: Routes = [ { path: 'action-sheet-controller', loadComponent: () => import('../action-sheet-controller/action-sheet-controller.component').then(c => c.ActionSheetControllerComponent) }, { path: 'popover', loadComponent: () => import('../popover/popover.component').then(c => c.PopoverComponent) }, { path: 'modal', loadComponent: () => import('../modal/modal.component').then(c => c.ModalComponent) }, + { path: 'programatic-modal', loadComponent: () => import('../programatic-modal/programatic-modal.component').then(c => c.ProgramaticModalComponent) }, { path: 'router-outlet', loadComponent: () => import('../router-outlet/router-outlet.component').then(c => c.RouterOutletComponent) }, { 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) }, 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 6c60543a837..9a7a71d5fe4 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 @@ -85,6 +85,11 @@ Modal Test + + + Programatic Modal Test + + Overlay Controllers Test diff --git a/packages/angular/test/base/src/app/standalone/programatic-modal/modal/modal.component.html b/packages/angular/test/base/src/app/standalone/programatic-modal/modal/modal.component.html new file mode 100644 index 00000000000..0b81c38f7c3 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/programatic-modal/modal/modal.component.html @@ -0,0 +1,3 @@ +

+ modal works! +

diff --git a/packages/angular/test/base/src/app/standalone/programatic-modal/modal/modal.component.ts b/packages/angular/test/base/src/app/standalone/programatic-modal/modal/modal.component.ts new file mode 100644 index 00000000000..8fef9eb9213 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/programatic-modal/modal/modal.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; +import { IonButton } from "@ionic/angular/standalone"; + +@Component({ + selector: 'app-modal', + templateUrl: './modal.component.html', + imports: [IonButton], + standalone: true, +}) +export class ModalComponent { +} diff --git a/packages/angular/test/base/src/app/standalone/programatic-modal/programatic-modal.component.html b/packages/angular/test/base/src/app/standalone/programatic-modal/programatic-modal.component.html new file mode 100644 index 00000000000..d614bd4d2b4 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/programatic-modal/programatic-modal.component.html @@ -0,0 +1,2 @@ +Open Modal + diff --git a/packages/angular/test/base/src/app/standalone/programatic-modal/programatic-modal.component.ts b/packages/angular/test/base/src/app/standalone/programatic-modal/programatic-modal.component.ts new file mode 100644 index 00000000000..0854741fe6e --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/programatic-modal/programatic-modal.component.ts @@ -0,0 +1,18 @@ +import { Component } from '@angular/core'; +import { IonButton } from '@ionic/angular/standalone'; +import { ProgramaticModalService } from './programatic-modal.service'; + +@Component({ + selector: 'app-test', + templateUrl: './programatic-modal.component.html', + standalone: true, + imports: [IonButton] +}) +export class ProgramaticModalComponent { + constructor(private modalService: ProgramaticModalService) {} + + public open() { + this.modalService.open(); + } +} + diff --git a/packages/angular/test/base/src/app/standalone/programatic-modal/programatic-modal.service.ts b/packages/angular/test/base/src/app/standalone/programatic-modal/programatic-modal.service.ts new file mode 100644 index 00000000000..15390733951 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/programatic-modal/programatic-modal.service.ts @@ -0,0 +1,18 @@ +import { inject, Injectable } from '@angular/core'; +import { ModalController } from "@ionic/angular/standalone"; +import { ModalComponent } from "./modal/modal.component"; + +@Injectable({ + providedIn: 'root' +}) +export class ProgramaticModalService { + #modalController = inject(ModalController); + + async open() { + const modal = await this.#modalController.create({ + component: ModalComponent, + focusTrap: true, + }); + await modal.present(); + } +}