diff --git a/src/cdk/dialog/dialog.spec.ts b/src/cdk/dialog/dialog.spec.ts index 063e4eb0e598..c9d2123c2844 100644 --- a/src/cdk/dialog/dialog.spec.ts +++ b/src/cdk/dialog/dialog.spec.ts @@ -10,6 +10,7 @@ import { ChangeDetectionStrategy, Component, Directive, + inject, Inject, InjectionToken, Injector, @@ -52,8 +53,13 @@ describe('Dialog', () => { DialogWithInjectedData, DialogWithoutFocusableElements, DirectiveWithViewContainer, + TemplateInjectorParentComponent, + TemplateInjectorInnerDirective, + ], + providers: [ + {provide: Location, useClass: SpyLocation}, + {provide: TEMPLATE_INJECTOR_TEST_TOKEN, useValue: 'hello from test module'}, ], - providers: [{provide: Location, useClass: SpyLocation}], }); TestBed.compileComponents(); @@ -710,6 +716,22 @@ describe('Dialog', () => { expect(overlayContainerElement.querySelector('cdk-dialog-container')).toBeTruthy(); })); + + it( + 'should fall back to node injector in template dialog if token does not exist in ' + + 'template injector', + fakeAsync(() => { + const templateInjectFixture = TestBed.createComponent(TemplateInjectorParentComponent); + templateInjectFixture.detectChanges(); + + dialog.open(templateInjectFixture.componentInstance.templateRef); + templateInjectFixture.detectChanges(); + + expect(templateInjectFixture.componentInstance.innerComponentValue).toBe( + 'hello from parent component', + ); + }), + ); }); describe('hasBackdrop option', () => { @@ -1233,3 +1255,28 @@ class DialogWithoutFocusableElements {} encapsulation: ViewEncapsulation.ShadowDom, }) class ShadowDomComponent {} + +const TEMPLATE_INJECTOR_TEST_TOKEN = new InjectionToken('TEMPLATE_INJECTOR_TEST_TOKEN'); + +@Component({ + template: ``, + providers: [ + { + provide: TEMPLATE_INJECTOR_TEST_TOKEN, + useValue: 'hello from parent component', + }, + ], +}) +class TemplateInjectorParentComponent { + @ViewChild(TemplateRef) templateRef: TemplateRef; + innerComponentValue = ''; +} + +@Directive({ + selector: 'template-injector-inner', +}) +class TemplateInjectorInnerDirective { + constructor(parent: TemplateInjectorParentComponent) { + parent.innerComponentValue = inject(TEMPLATE_INJECTOR_TEST_TOKEN); + } +} diff --git a/src/cdk/dialog/dialog.ts b/src/cdk/dialog/dialog.ts index 19c85320e2b6..3bc7838c2ff9 100644 --- a/src/cdk/dialog/dialog.ts +++ b/src/cdk/dialog/dialog.ts @@ -221,7 +221,7 @@ export class Dialog implements OnDestroy { dialogRef: DialogRef, config: DialogConfig>, ): BasePortalOutlet { - const userInjector = config.injector ?? config.viewContainerRef?.injector; + const userInjector = config.injector || config.viewContainerRef?.injector; const providers: StaticProvider[] = [ {provide: DialogConfig, useValue: config}, {provide: DialogRef, useValue: dialogRef}, @@ -265,9 +265,8 @@ export class Dialog implements OnDestroy { dialogContainer: BasePortalOutlet, config: DialogConfig>, ) { - const injector = this._createInjector(config, dialogRef, dialogContainer); - if (componentOrTemplateRef instanceof TemplateRef) { + const injector = this._createInjector(config, dialogRef, dialogContainer, undefined); let context: any = {$implicit: config.data, dialogRef}; if (config.templateContext) { @@ -283,6 +282,7 @@ export class Dialog implements OnDestroy { new TemplatePortal(componentOrTemplateRef, null!, context, injector), ); } else { + const injector = this._createInjector(config, dialogRef, dialogContainer, this._injector); const contentRef = dialogContainer.attachComponentPortal( new ComponentPortal( componentOrTemplateRef, @@ -301,14 +301,17 @@ export class Dialog implements OnDestroy { * @param config Config object that is used to construct the dialog. * @param dialogRef Reference to the dialog being opened. * @param dialogContainer Component that is going to wrap the dialog content. + * @param fallbackInjector Injector to use as a fallback when a lookup fails in the custom + * dialog injector, if the user didn't provide a custom one. * @returns The custom injector that can be used inside the dialog. */ private _createInjector( config: DialogConfig>, dialogRef: DialogRef, dialogContainer: BasePortalOutlet, + fallbackInjector: Injector | undefined, ): Injector { - const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector; + const userInjector = config.injector || config.viewContainerRef?.injector; const providers: StaticProvider[] = [ {provide: DIALOG_DATA, useValue: config.data}, {provide: DialogRef, useValue: dialogRef}, @@ -333,7 +336,7 @@ export class Dialog implements OnDestroy { }); } - return Injector.create({parent: config.injector || userInjector || this._injector, providers}); + return Injector.create({parent: userInjector || fallbackInjector, providers}); } /**