feat(angular): add custom injector support for modal and popover controllers (#30899)

Issue number: resolves #30638

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
When using `ModalController.create()` or `PopoverController.create()` in
Angular, components rendered inside overlays cannot access non-global
services or tokens from the component tree. For example, route-scoped
services or Angular's Dir directive for bidirectional text support are
not accessible from within a modal, requiring complex workarounds with
wrapper components.

## What is the new behavior?
`ModalController.create()` and `PopoverController.create()` now accept
an optional injector property that allows passing a custom Angular
Injector. This enables overlay components to access services and tokens
that are not available in the root injector, such as route-scoped
services or the Dir directive from Angular CDK.

```typescript
const customInjector = Injector.create({
  providers: [{ provide: MyService, useValue: myServiceInstance }],
  parent: this.injector,
});
```

```typescript
const modal = await this.modalController.create({
  component: MyModalComponent,
  injector: customInjector,
});
```

## Does this introduce a breaking change?

- [ ] Yes
- [X] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

Current dev build:
```
8.7.17-dev.11769628168.11eca7cd
```
This commit is contained in:
Shane
2026-01-29 08:35:06 -08:00
committed by GitHub
parent 0cf4c03e29
commit 822da428af
19 changed files with 292 additions and 22 deletions

View File

@@ -9,6 +9,7 @@ export { AngularDelegate, bindLifecycleEvents, IonModalToken } from './providers
export type { IonicWindow } from './types/interfaces';
export type { ViewDidEnter, ViewDidLeave, ViewWillEnter, ViewWillLeave } from './types/ionic-lifecycle-hooks';
export type { ModalOptions, PopoverOptions } from './types/overlay-options';
export { NavParams } from './directives/navigation/nav-params';

View File

@@ -36,7 +36,8 @@ export class AngularDelegate {
create(
environmentInjector: EnvironmentInjector,
injector: Injector,
elementReferenceKey?: string
elementReferenceKey?: string,
customInjector?: Injector
): AngularFrameworkDelegate {
return new AngularFrameworkDelegate(
environmentInjector,
@@ -44,7 +45,8 @@ export class AngularDelegate {
this.applicationRef,
this.zone,
elementReferenceKey,
this.config.useSetInputAPI ?? false
this.config.useSetInputAPI ?? false,
customInjector
);
}
}
@@ -59,7 +61,8 @@ export class AngularFrameworkDelegate implements FrameworkDelegate {
private applicationRef: ApplicationRef,
private zone: NgZone,
private elementReferenceKey?: string,
private enableSignalsSupport?: boolean
private enableSignalsSupport?: boolean,
private customInjector?: Injector
) {}
attachViewToDom(container: any, component: any, params?: any, cssClasses?: string[]): Promise<any> {
@@ -93,7 +96,8 @@ export class AngularFrameworkDelegate implements FrameworkDelegate {
componentProps,
cssClasses,
this.elementReferenceKey,
this.enableSignalsSupport
this.enableSignalsSupport,
this.customInjector
);
resolve(el);
});
@@ -131,7 +135,8 @@ export const attachView = (
params: any,
cssClasses: string[] | undefined,
elementReferenceKey: string | undefined,
enableSignalsSupport: boolean | undefined
enableSignalsSupport: boolean | undefined,
customInjector?: Injector
): any => {
/**
* Wraps the injector with a custom injector that
@@ -158,7 +163,7 @@ export const attachView = (
const childInjector = Injector.create({
providers,
parent: injector,
parent: customInjector ?? injector,
});
const componentRef = createComponent<any>(component, {

View File

@@ -0,0 +1,18 @@
import type { Injector } from '@angular/core';
import type { 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 & {
injector?: Injector;
};
/**
* Popover options with Angular-specific injector support.
* Extends @ionic/core PopoverOptions with an optional injector property.
*/
export type PopoverOptions = CorePopoverOptions & {
injector?: Injector;
};