Skip to content

Commit 5a8ebb1

Browse files
authored
feat(material/dialog): add support for explicit injector (#24580)
1 parent add5a21 commit 5a8ebb1

File tree

7 files changed

+155
-4
lines changed

7 files changed

+155
-4
lines changed

src/cdk-experimental/dialog/dialog-config.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {ViewContainerRef} from '@angular/core';
8+
import {Injector, ViewContainerRef} from '@angular/core';
99
import {Direction} from '@angular/cdk/bidi';
1010
import {ComponentType} from '@angular/cdk/overlay';
1111
import {CdkDialogContainer} from './dialog-container';
@@ -36,6 +36,12 @@ export class DialogConfig<D = any> {
3636
*/
3737
viewContainerRef?: ViewContainerRef;
3838

39+
/**
40+
* Injector used for the instantiation of the component to be attached. If provided,
41+
* takes precedence over the injector indirectly provided by `ViewContainerRef`.
42+
*/
43+
injector?: Injector;
44+
3945
/** The id of the dialog. */
4046
id?: string;
4147

src/cdk-experimental/dialog/dialog.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ export class Dialog implements OnDestroy {
213213
*/
214214
protected _attachDialogContainer(overlay: OverlayRef, config: DialogConfig): CdkDialogContainer {
215215
const container = config.containerComponent || this._injector.get(DIALOG_CONTAINER);
216-
const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
216+
const userInjector = config.injector ?? config.viewContainerRef?.injector;
217217
const injector = Injector.create({
218218
parent: userInjector || this._injector,
219219
providers: [{provide: DialogConfig, useValue: config}],

src/material-experimental/mdc-dialog/dialog.spec.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ import {
1717
ChangeDetectionStrategy,
1818
Component,
1919
ComponentFactoryResolver,
20+
createNgModuleRef,
2021
Directive,
2122
Inject,
23+
Injectable,
2224
Injector,
25+
NgModule,
2326
NgZone,
2427
TemplateRef,
2528
ViewChild,
@@ -2018,6 +2021,37 @@ describe('MDC-based MatDialog with animations enabled', () => {
20182021
}));
20192022
});
20202023

2024+
describe('MatDialog with explicit injector provided', () => {
2025+
let overlayContainerElement: HTMLElement;
2026+
let fixture: ComponentFixture<ModuleBoundDialogParentComponent>;
2027+
2028+
beforeEach(fakeAsync(() => {
2029+
TestBed.configureTestingModule({
2030+
imports: [MatDialogModule, BrowserAnimationsModule],
2031+
declarations: [ModuleBoundDialogParentComponent],
2032+
});
2033+
2034+
TestBed.compileComponents();
2035+
}));
2036+
2037+
beforeEach(inject([OverlayContainer], (oc: OverlayContainer) => {
2038+
overlayContainerElement = oc.getContainerElement();
2039+
}));
2040+
2041+
beforeEach(() => {
2042+
fixture = TestBed.createComponent(ModuleBoundDialogParentComponent);
2043+
});
2044+
2045+
it('should use the standalone injector and render the dialog successfully', () => {
2046+
fixture.componentInstance.openDialog();
2047+
fixture.detectChanges();
2048+
2049+
expect(
2050+
overlayContainerElement.querySelector('module-bound-dialog-child-component')!.innerHTML,
2051+
).toEqual('<p>Pasta</p>');
2052+
});
2053+
});
2054+
20212055
@Directive({selector: 'dir-with-view-container'})
20222056
class DirectiveWithViewContainer {
20232057
constructor(public viewContainerRef: ViewContainerRef) {}
@@ -2131,3 +2165,38 @@ class DialogWithoutFocusableElements {}
21312165
encapsulation: ViewEncapsulation.ShadowDom,
21322166
})
21332167
class ShadowDomComponent {}
2168+
2169+
@Component({template: ''})
2170+
class ModuleBoundDialogParentComponent {
2171+
constructor(private _injector: Injector, private _dialog: MatDialog) {}
2172+
2173+
openDialog(): void {
2174+
const ngModuleRef = createNgModuleRef(
2175+
ModuleBoundDialogModule,
2176+
/* parentInjector */ this._injector,
2177+
);
2178+
2179+
this._dialog.open(ModuleBoundDialogComponent, {injector: ngModuleRef.injector});
2180+
}
2181+
}
2182+
2183+
@Injectable()
2184+
class ModuleBoundDialogService {
2185+
name = 'Pasta';
2186+
}
2187+
2188+
@Component({
2189+
template: '<module-bound-dialog-child-component></module-bound-dialog-child-component>',
2190+
})
2191+
class ModuleBoundDialogComponent {}
2192+
2193+
@Component({selector: 'module-bound-dialog-child-component', template: '<p>{{service.name}}</p>'})
2194+
class ModuleBoundDialogChildComponent {
2195+
constructor(public service: ModuleBoundDialogService) {}
2196+
}
2197+
2198+
@NgModule({
2199+
declarations: [ModuleBoundDialogComponent, ModuleBoundDialogChildComponent],
2200+
providers: [ModuleBoundDialogService],
2201+
})
2202+
class ModuleBoundDialogModule {}

src/material/dialog/dialog-config.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {ViewContainerRef, ComponentFactoryResolver} from '@angular/core';
9+
import {ViewContainerRef, ComponentFactoryResolver, Injector} from '@angular/core';
1010
import {Direction} from '@angular/cdk/bidi';
1111
import {ScrollStrategy} from '@angular/cdk/overlay';
1212
import {defaultParams} from './dialog-animations';
@@ -44,6 +44,12 @@ export class MatDialogConfig<D = any> {
4444
*/
4545
viewContainerRef?: ViewContainerRef;
4646

47+
/**
48+
* Injector used for the instantiation of the component to be attached. If provided,
49+
* takes precedence over the injector indirectly provided by `ViewContainerRef`.
50+
*/
51+
injector?: Injector;
52+
4753
/** ID for the dialog. If omitted, a unique one will be generated. */
4854
id?: string;
4955

src/material/dialog/dialog.spec.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ import {
2020
ComponentFactoryResolver,
2121
NgZone,
2222
ViewEncapsulation,
23+
Injectable,
24+
NgModule,
25+
createNgModuleRef,
2326
} from '@angular/core';
2427
import {By} from '@angular/platform-browser';
2528
import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
@@ -2072,6 +2075,37 @@ describe('MatDialog with animations enabled', () => {
20722075
}));
20732076
});
20742077

2078+
describe('MatDialog with explicit injector provided', () => {
2079+
let overlayContainerElement: HTMLElement;
2080+
let fixture: ComponentFixture<ModuleBoundDialogParentComponent>;
2081+
2082+
beforeEach(fakeAsync(() => {
2083+
TestBed.configureTestingModule({
2084+
imports: [MatDialogModule, BrowserAnimationsModule],
2085+
declarations: [ModuleBoundDialogParentComponent],
2086+
});
2087+
2088+
TestBed.compileComponents();
2089+
}));
2090+
2091+
beforeEach(inject([OverlayContainer], (oc: OverlayContainer) => {
2092+
overlayContainerElement = oc.getContainerElement();
2093+
}));
2094+
2095+
beforeEach(() => {
2096+
fixture = TestBed.createComponent(ModuleBoundDialogParentComponent);
2097+
});
2098+
2099+
it('should use the standalone injector and render the dialog successfully', fakeAsync(() => {
2100+
fixture.componentInstance.openDialog();
2101+
fixture.detectChanges();
2102+
2103+
expect(
2104+
overlayContainerElement.querySelector('module-bound-dialog-child-component')!.innerHTML,
2105+
).toEqual('<p>Pasta</p>');
2106+
}));
2107+
});
2108+
20752109
@Directive({selector: 'dir-with-view-container'})
20762110
class DirectiveWithViewContainer {
20772111
constructor(public viewContainerRef: ViewContainerRef) {}
@@ -2188,3 +2222,38 @@ class DialogWithoutFocusableElements {}
21882222
encapsulation: ViewEncapsulation.ShadowDom,
21892223
})
21902224
class ShadowDomComponent {}
2225+
2226+
@Component({template: ''})
2227+
class ModuleBoundDialogParentComponent {
2228+
constructor(private _injector: Injector, private _dialog: MatDialog) {}
2229+
2230+
openDialog(): void {
2231+
const ngModuleRef = createNgModuleRef(
2232+
ModuleBoundDialogModule,
2233+
/* parentInjector */ this._injector,
2234+
);
2235+
2236+
this._dialog.open(ModuleBoundDialogComponent, {injector: ngModuleRef.injector});
2237+
}
2238+
}
2239+
2240+
@Injectable()
2241+
class ModuleBoundDialogService {
2242+
name = 'Pasta';
2243+
}
2244+
2245+
@Component({
2246+
template: '<module-bound-dialog-child-component></module-bound-dialog-child-component>',
2247+
})
2248+
class ModuleBoundDialogComponent {}
2249+
2250+
@Component({selector: 'module-bound-dialog-child-component', template: '<p>{{service.name}}</p>'})
2251+
class ModuleBoundDialogChildComponent {
2252+
constructor(public service: ModuleBoundDialogService) {}
2253+
}
2254+
2255+
@NgModule({
2256+
declarations: [ModuleBoundDialogComponent, ModuleBoundDialogChildComponent],
2257+
providers: [ModuleBoundDialogService],
2258+
})
2259+
class ModuleBoundDialogModule {}

src/material/dialog/dialog.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ export abstract class _MatDialogBase<C extends _MatDialogContainerBase> implemen
257257
* @returns A promise resolving to a ComponentRef for the attached container.
258258
*/
259259
private _attachDialogContainer(overlay: OverlayRef, config: MatDialogConfig): C {
260-
const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
260+
const userInjector = config.injector ?? config.viewContainerRef?.injector;
261261
const injector = Injector.create({
262262
parent: userInjector || this._injector,
263263
providers: [{provide: MatDialogConfig, useValue: config}],

tools/public_api_guard/material/dialog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ export class MatDialogConfig<D = any> {
173173
hasBackdrop?: boolean;
174174
height?: string;
175175
id?: string;
176+
injector?: Injector;
176177
maxHeight?: number | string;
177178
maxWidth?: number | string;
178179
minHeight?: number | string;

0 commit comments

Comments
 (0)