Skip to content

Commit 1082bf0

Browse files
committed
fix(cdk/dialog): fall back to node injector token doesn't exist in template injector
In #24570 the template dialog was changed so that the dialog ref is resolved from the template injector, rather than the DOM. The problem is that existing logic for constructing the injector was reused which means that the lookup path for a token starting from inside the `ng-template` would be `start -> node injector -> template injector -> app injector` instead of `start -> node injector -> template injector -> node injector -> app injector`. This has caused some regressions for users of `@angular/router` like angular/angular#46500. These changes resolve the issue by not providing a fallback injector so that the framework automatically falls back to the node injector.
1 parent fdb30ad commit 1082bf0

File tree

2 files changed

+56
-6
lines changed

2 files changed

+56
-6
lines changed

src/cdk/dialog/dialog.spec.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
ChangeDetectionStrategy,
1111
Component,
1212
Directive,
13+
inject,
1314
Inject,
1415
InjectionToken,
1516
Injector,
@@ -52,8 +53,13 @@ describe('Dialog', () => {
5253
DialogWithInjectedData,
5354
DialogWithoutFocusableElements,
5455
DirectiveWithViewContainer,
56+
TemplateInjectorParentComponent,
57+
TemplateInjectorInnerDirective,
58+
],
59+
providers: [
60+
{provide: Location, useClass: SpyLocation},
61+
{provide: TEMPLATE_INJECTOR_TEST_TOKEN, useValue: 'hello from test module'},
5562
],
56-
providers: [{provide: Location, useClass: SpyLocation}],
5763
});
5864

5965
TestBed.compileComponents();
@@ -710,6 +716,22 @@ describe('Dialog', () => {
710716

711717
expect(overlayContainerElement.querySelector('cdk-dialog-container')).toBeTruthy();
712718
}));
719+
720+
it(
721+
'should fall back to node injector in template dialog if token does not exist in ' +
722+
'template injector',
723+
fakeAsync(() => {
724+
const templateInjectFixture = TestBed.createComponent(TemplateInjectorParentComponent);
725+
templateInjectFixture.detectChanges();
726+
727+
dialog.open(templateInjectFixture.componentInstance.templateRef);
728+
templateInjectFixture.detectChanges();
729+
730+
expect(templateInjectFixture.componentInstance.innerComponentValue).toBe(
731+
'hello from parent component',
732+
);
733+
}),
734+
);
713735
});
714736

715737
describe('hasBackdrop option', () => {
@@ -1233,3 +1255,28 @@ class DialogWithoutFocusableElements {}
12331255
encapsulation: ViewEncapsulation.ShadowDom,
12341256
})
12351257
class ShadowDomComponent {}
1258+
1259+
const TEMPLATE_INJECTOR_TEST_TOKEN = new InjectionToken<string>('TEMPLATE_INJECTOR_TEST_TOKEN');
1260+
1261+
@Component({
1262+
template: `<ng-template><template-injector-inner></template-injector-inner></ng-template>`,
1263+
providers: [
1264+
{
1265+
provide: TEMPLATE_INJECTOR_TEST_TOKEN,
1266+
useValue: 'hello from parent component',
1267+
},
1268+
],
1269+
})
1270+
class TemplateInjectorParentComponent {
1271+
@ViewChild(TemplateRef) templateRef: TemplateRef<any>;
1272+
innerComponentValue = '';
1273+
}
1274+
1275+
@Directive({
1276+
selector: 'template-injector-inner',
1277+
})
1278+
class TemplateInjectorInnerDirective {
1279+
constructor(parent: TemplateInjectorParentComponent) {
1280+
parent.innerComponentValue = inject(TEMPLATE_INJECTOR_TEST_TOKEN);
1281+
}
1282+
}

src/cdk/dialog/dialog.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ export class Dialog implements OnDestroy {
221221
dialogRef: DialogRef<R, C>,
222222
config: DialogConfig<D, DialogRef<R, C>>,
223223
): BasePortalOutlet {
224-
const userInjector = config.injector ?? config.viewContainerRef?.injector;
224+
const userInjector = config.injector || config.viewContainerRef?.injector;
225225
const providers: StaticProvider[] = [
226226
{provide: DialogConfig, useValue: config},
227227
{provide: DialogRef, useValue: dialogRef},
@@ -265,9 +265,8 @@ export class Dialog implements OnDestroy {
265265
dialogContainer: BasePortalOutlet,
266266
config: DialogConfig<D, DialogRef<R, C>>,
267267
) {
268-
const injector = this._createInjector(config, dialogRef, dialogContainer);
269-
270268
if (componentOrTemplateRef instanceof TemplateRef) {
269+
const injector = this._createInjector(config, dialogRef, dialogContainer, undefined);
271270
let context: any = {$implicit: config.data, dialogRef};
272271

273272
if (config.templateContext) {
@@ -283,6 +282,7 @@ export class Dialog implements OnDestroy {
283282
new TemplatePortal<C>(componentOrTemplateRef, null!, context, injector),
284283
);
285284
} else {
285+
const injector = this._createInjector(config, dialogRef, dialogContainer, this._injector);
286286
const contentRef = dialogContainer.attachComponentPortal<C>(
287287
new ComponentPortal(
288288
componentOrTemplateRef,
@@ -301,14 +301,17 @@ export class Dialog implements OnDestroy {
301301
* @param config Config object that is used to construct the dialog.
302302
* @param dialogRef Reference to the dialog being opened.
303303
* @param dialogContainer Component that is going to wrap the dialog content.
304+
* @param fallbackInjector Injector to use as a fallback when a lookup fails in the custom
305+
* dialog injector, if the user didn't provide a custom one.
304306
* @returns The custom injector that can be used inside the dialog.
305307
*/
306308
private _createInjector<R, D, C>(
307309
config: DialogConfig<D, DialogRef<R, C>>,
308310
dialogRef: DialogRef<R, C>,
309311
dialogContainer: BasePortalOutlet,
312+
fallbackInjector: Injector | undefined,
310313
): Injector {
311-
const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
314+
const userInjector = config.injector || config.viewContainerRef?.injector;
312315
const providers: StaticProvider[] = [
313316
{provide: DIALOG_DATA, useValue: config.data},
314317
{provide: DialogRef, useValue: dialogRef},
@@ -333,7 +336,7 @@ export class Dialog implements OnDestroy {
333336
});
334337
}
335338

336-
return Injector.create({parent: config.injector || userInjector || this._injector, providers});
339+
return Injector.create({parent: userInjector || fallbackInjector, providers});
337340
}
338341

339342
/**

0 commit comments

Comments
 (0)