diff --git a/packages/angular/common/src/types/overlay-options.ts b/packages/angular/common/src/types/overlay-options.ts index 93ec10cb5f1..786db567d16 100644 --- a/packages/angular/common/src/types/overlay-options.ts +++ b/packages/angular/common/src/types/overlay-options.ts @@ -1,11 +1,15 @@ import type { Injector } from '@angular/core'; -import type { ModalOptions as CoreModalOptions, PopoverOptions as CorePopoverOptions } from '@ionic/core/components'; +import type { + ComponentRef, + ModalOptions as CoreModalOptions, + PopoverOptions as CorePopoverOptions, +} from '@ionic/core/components'; /** * Modal options with Angular-specific injector support. * Extends @ionic/core ModalOptions with an optional injector property. */ -export type ModalOptions = CoreModalOptions & { +export type ModalOptions = CoreModalOptions & { injector?: Injector; }; @@ -13,6 +17,6 @@ export type ModalOptions = CoreModalOptions & { * Popover options with Angular-specific injector support. * Extends @ionic/core PopoverOptions with an optional injector property. */ -export type PopoverOptions = CorePopoverOptions & { +export type PopoverOptions = CorePopoverOptions & { injector?: Injector; }; diff --git a/packages/angular/test/base/e2e/src/standalone/modal-options-generic.spec.ts b/packages/angular/test/base/e2e/src/standalone/modal-options-generic.spec.ts new file mode 100644 index 00000000000..dfe5d84713f --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/modal-options-generic.spec.ts @@ -0,0 +1,24 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Modal Options: Generic Type Parameter', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/standalone/modal-options-generic'); + }); + + test('should open modal created with ModalOptions', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/31012', + }); + + await page.locator('ion-button#open-modal').click(); + + await expect(page.locator('ion-modal')).toBeVisible(); + + const greeting = page.locator('#greeting'); + await expect(greeting).toHaveText('hello world'); + + await page.locator('#close-modal').click(); + await expect(page.locator('ion-modal')).not.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 3f9f82933f6..dd0ccbb4ac5 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 @@ -24,6 +24,7 @@ export const routes: Routes = [ }, { path: 'programmatic-modal', loadComponent: () => import('../programmatic-modal/programmatic-modal.component').then(c => c.ProgrammaticModalComponent) }, { path: 'modal-custom-injector', loadComponent: () => import('../modal-custom-injector/modal-custom-injector.component').then(c => c.ModalCustomInjectorComponent) }, + { path: 'modal-options-generic', loadComponent: () => import('../modal-options-generic/modal-options-generic.component').then(c => c.ModalOptionsGenericComponent) }, { path: 'popover-custom-injector', loadComponent: () => import('../popover-custom-injector/popover-custom-injector.component').then(c => c.PopoverCustomInjectorComponent) }, { 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) }, 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 641018e757c..00c3bf97452 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 @@ -125,6 +125,11 @@ Modal Custom Injector Test + + + Modal Options Generic Test + + Overlay Controllers Test diff --git a/packages/angular/test/base/src/app/standalone/modal-options-generic/modal-options-generic.component.ts b/packages/angular/test/base/src/app/standalone/modal-options-generic/modal-options-generic.component.ts new file mode 100644 index 00000000000..04d630bf992 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/modal-options-generic/modal-options-generic.component.ts @@ -0,0 +1,38 @@ +import { Component, inject } from '@angular/core'; +import { IonContent, IonHeader, IonTitle, IonToolbar, IonButton, ModalController } from '@ionic/angular/standalone'; +import type { ModalOptions } from '@ionic/angular/standalone'; +import { GenericModalComponent } from './modal/modal.component'; + +@Component({ + selector: 'app-modal-options-generic', + template: ` + + + Modal Options Generic Test + + + + + Open Modal + + + `, + standalone: true, + imports: [IonContent, IonHeader, IonTitle, IonToolbar, IonButton], +}) +export class ModalOptionsGenericComponent { + private modalController = inject(ModalController); + + async openModal() { + // Regression: ModalOptions must accept a generic type parameter (#31012) + const opts: ModalOptions = { + component: GenericModalComponent, + componentProps: { + greeting: 'hello world', + }, + }; + + const modal = await this.modalController.create(opts); + await modal.present(); + } +} diff --git a/packages/angular/test/base/src/app/standalone/modal-options-generic/modal/modal.component.ts b/packages/angular/test/base/src/app/standalone/modal-options-generic/modal/modal.component.ts new file mode 100644 index 00000000000..619adc64331 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/modal-options-generic/modal/modal.component.ts @@ -0,0 +1,29 @@ +import { Component, Input } from '@angular/core'; +import { IonContent, IonHeader, IonTitle, IonToolbar, IonButton, IonButtons } from '@ionic/angular/standalone'; + +@Component({ + selector: 'app-generic-modal', + template: ` + + + Generic Modal + + Close + + + + +

{{ greeting }}

+
+ `, + standalone: true, + imports: [IonContent, IonHeader, IonTitle, IonToolbar, IonButton, IonButtons], +}) +export class GenericModalComponent { + @Input() greeting = ''; + modal: HTMLIonModalElement | undefined; + + dismiss() { + this.modal?.dismiss(); + } +}