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
10 changes: 7 additions & 3 deletions packages/angular/common/src/types/overlay-options.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
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<T extends ComponentRef = ComponentRef> = CoreModalOptions<T> & {
injector?: Injector;
};

/**
* Popover options with Angular-specific injector support.
* Extends @ionic/core PopoverOptions with an optional injector property.
*/
export type PopoverOptions = CorePopoverOptions & {
export type PopoverOptions<T extends ComponentRef = ComponentRef> = CorePopoverOptions<T> & {
injector?: Injector;
};
Original file line number Diff line number Diff line change
@@ -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<typeof Component>', 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();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@
Modal Custom Injector Test
</ion-label>
</ion-item>
<ion-item routerLink="/standalone/modal-options-generic">
<ion-label>
Modal Options Generic Test
</ion-label>
</ion-item>
<ion-item routerLink="/standalone/overlay-controllers">
<ion-label>
Overlay Controllers Test
Expand Down
Original file line number Diff line number Diff line change
@@ -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: `
<ion-header>
<ion-toolbar>
<ion-title>Modal Options Generic Test</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-button id="open-modal" (click)="openModal()">
Open Modal
</ion-button>
</ion-content>
`,
standalone: true,
imports: [IonContent, IonHeader, IonTitle, IonToolbar, IonButton],
})
export class ModalOptionsGenericComponent {
private modalController = inject(ModalController);

async openModal() {
// Regression: ModalOptions<T> must accept a generic type parameter (#31012)
const opts: ModalOptions<typeof GenericModalComponent> = {
component: GenericModalComponent,
componentProps: {
greeting: 'hello world',
},
};

const modal = await this.modalController.create(opts);
await modal.present();
}
}
Original file line number Diff line number Diff line change
@@ -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: `
<ion-header>
<ion-toolbar>
<ion-title>Generic Modal</ion-title>
<ion-buttons slot="end">
<ion-button id="close-modal" (click)="dismiss()">Close</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<p id="greeting">{{ greeting }}</p>
</ion-content>
`,
standalone: true,
imports: [IonContent, IonHeader, IonTitle, IonToolbar, IonButton, IonButtons],
})
export class GenericModalComponent {
@Input() greeting = '';
modal: HTMLIonModalElement | undefined;

dismiss() {
this.modal?.dismiss();
}
}
Loading