From efa4fe39fc6932009ded0735f6774f2a7864803c Mon Sep 17 00:00:00 2001 From: Rafal Slawik Date: Fri, 16 May 2025 10:24:18 +0000 Subject: [PATCH] feat(common): accept undefined inputs in NgTemplateOutlet Extend types of inputs to include `undefined` to avoid `?? null` when using singals (e.g. `viewChild`). Fixes #51225 --- goldens/public-api/common/index.api.md | 6 ++-- .../src/directives/ng_template_outlet.ts | 6 ++-- .../directives/ng_template_outlet_spec.ts | 29 +++++++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/goldens/public-api/common/index.api.md b/goldens/public-api/common/index.api.md index fa399abb00ef..10280f54a046 100644 --- a/goldens/public-api/common/index.api.md +++ b/goldens/public-api/common/index.api.md @@ -716,9 +716,9 @@ export class NgTemplateOutlet implements OnChanges { constructor(_viewContainerRef: ViewContainerRef); // (undocumented) ngOnChanges(changes: SimpleChanges): void; - ngTemplateOutlet: TemplateRef | null; - ngTemplateOutletContext: C | null; - ngTemplateOutletInjector: Injector | null; + ngTemplateOutlet: TemplateRef | null | undefined; + ngTemplateOutletContext: C | null | undefined; + ngTemplateOutletInjector: Injector | null | undefined; // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration, "[ngTemplateOutlet]", never, { "ngTemplateOutletContext": { "alias": "ngTemplateOutletContext"; "required": false; }; "ngTemplateOutlet": { "alias": "ngTemplateOutlet"; "required": false; }; "ngTemplateOutletInjector": { "alias": "ngTemplateOutletInjector"; "required": false; }; }, {}, never, never, true, never>; // (undocumented) diff --git a/packages/common/src/directives/ng_template_outlet.ts b/packages/common/src/directives/ng_template_outlet.ts index 1b9600b45bf5..44a865fc9388 100644 --- a/packages/common/src/directives/ng_template_outlet.ts +++ b/packages/common/src/directives/ng_template_outlet.ts @@ -54,15 +54,15 @@ export class NgTemplateOutlet implements OnChanges { * declarations. * Using the key `$implicit` in the context object will set its value as default. */ - @Input() public ngTemplateOutletContext: C | null = null; + @Input() public ngTemplateOutletContext: C | null | undefined = null; /** * A string defining the template reference and optionally the context object for the template. */ - @Input() public ngTemplateOutlet: TemplateRef | null = null; + @Input() public ngTemplateOutlet: TemplateRef | null | undefined = null; /** Injector to be used within the embedded view. */ - @Input() public ngTemplateOutletInjector: Injector | null = null; + @Input() public ngTemplateOutletInjector: Injector | null | undefined = null; constructor(private _viewContainerRef: ViewContainerRef) {} diff --git a/packages/common/test/directives/ng_template_outlet_spec.ts b/packages/common/test/directives/ng_template_outlet_spec.ts index bac56ce74d81..c4b91e421d29 100644 --- a/packages/common/test/directives/ng_template_outlet_spec.ts +++ b/packages/common/test/directives/ng_template_outlet_spec.ts @@ -70,6 +70,12 @@ describe('NgTemplateOutlet', () => { detectChangesAndExpectText(''); })); + it('should do nothing if templateRef is `undefined`', waitForAsync(() => { + const template = ``; + fixture = createTestComponent(template); + detectChangesAndExpectText(''); + })); + it('should insert content specified by TemplateRef', waitForAsync(() => { const template = `foo` + @@ -93,6 +99,21 @@ describe('NgTemplateOutlet', () => { detectChangesAndExpectText(''); })); + it('should clear content if TemplateRef becomes `undefined`', waitForAsync(() => { + const template = + `foo` + + ``; + fixture = createTestComponent(template); + fixture.detectChanges(); + const refs = fixture.debugElement.children[0].references!['refs']; + + setTplRef(refs.tplRefs.first); + detectChangesAndExpectText('foo'); + + setTplRef(undefined); + detectChangesAndExpectText(''); + })); + it('should swap content if TemplateRef changes', waitForAsync(() => { const template = `foobar` + @@ -117,6 +138,14 @@ describe('NgTemplateOutlet', () => { detectChangesAndExpectText('foo'); })); + it('should display template if context is `undefined`', waitForAsync(() => { + const template = + `foo` + + ``; + fixture = createTestComponent(template); + detectChangesAndExpectText('foo'); + })); + it('should reflect initial context and changes', waitForAsync(() => { const template = `{{foo}}` +