From e4c50b3bea22ca2afba74465893c36730952f4b9 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Sun, 17 Nov 2024 13:42:54 +0100 Subject: [PATCH] feat(common): expose component instance in NgComponentOutlet (#58698) Exposes the current instance of the component in the `NgComponentOutlet` directive. PR Close #58698 --- goldens/public-api/common/index.api.md | 8 ++++---- .../src/directives/ng_component_outlet.ts | 17 ++++++++++++++--- .../test/directives/ng_component_outlet_spec.ts | 13 +++++++++++++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/goldens/public-api/common/index.api.md b/goldens/public-api/common/index.api.md index cb7e4fb8b9b..5a337ae0a43 100644 --- a/goldens/public-api/common/index.api.md +++ b/goldens/public-api/common/index.api.md @@ -490,9 +490,9 @@ export class NgClass implements DoCheck { } // @public -export class NgComponentOutlet implements OnChanges, DoCheck, OnDestroy { +export class NgComponentOutlet implements OnChanges, DoCheck, OnDestroy { constructor(_viewContainerRef: ViewContainerRef); - // (undocumented) + get componentInstance(): T | null; ngComponentOutlet: Type | null; // (undocumented) ngComponentOutletContent?: any[][]; @@ -511,9 +511,9 @@ export class NgComponentOutlet implements OnChanges, DoCheck, OnDestroy { // (undocumented) ngOnDestroy(): void; // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration; + static ɵdir: i0.ɵɵDirectiveDeclaration, "[ngComponentOutlet]", ["ngComponentOutlet"], { "ngComponentOutlet": { "alias": "ngComponentOutlet"; "required": false; }; "ngComponentOutletInputs": { "alias": "ngComponentOutletInputs"; "required": false; }; "ngComponentOutletInjector": { "alias": "ngComponentOutletInjector"; "required": false; }; "ngComponentOutletContent": { "alias": "ngComponentOutletContent"; "required": false; }; "ngComponentOutletNgModule": { "alias": "ngComponentOutletNgModule"; "required": false; }; "ngComponentOutletNgModuleFactory": { "alias": "ngComponentOutletNgModuleFactory"; "required": false; }; }, {}, never, never, true, never>; // (undocumented) - static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵfac: i0.ɵɵFactoryDeclaration, never>; } // @public diff --git a/packages/common/src/directives/ng_component_outlet.ts b/packages/common/src/directives/ng_component_outlet.ts index 6eb2ca9b493..552036de177 100644 --- a/packages/common/src/directives/ng_component_outlet.ts +++ b/packages/common/src/directives/ng_component_outlet.ts @@ -93,9 +93,12 @@ import { */ @Directive({ selector: '[ngComponentOutlet]', - standalone: true, + exportAs: 'ngComponentOutlet', }) -export class NgComponentOutlet implements OnChanges, DoCheck, OnDestroy { +export class NgComponentOutlet implements OnChanges, DoCheck, OnDestroy { + // TODO(crisbeto): this should be `Type`, but doing so broke a few + // targets in a TGP so we need to do it in a major version. + /** Component that should be rendered in the outlet. */ @Input() ngComponentOutlet: Type | null = null; @Input() ngComponentOutletInputs?: Record; @@ -108,7 +111,7 @@ export class NgComponentOutlet implements OnChanges, DoCheck, OnDestroy { */ @Input() ngComponentOutletNgModuleFactory?: NgModuleFactory; - private _componentRef: ComponentRef | undefined; + private _componentRef: ComponentRef | undefined; private _moduleRef: NgModuleRef | undefined; /** @@ -118,6 +121,14 @@ export class NgComponentOutlet implements OnChanges, DoCheck, OnDestroy { */ private _inputsUsed = new Map(); + /** + * Gets the instance of the currently-rendered component. + * Will be null if no component has been rendered. + */ + get componentInstance(): T | null { + return this._componentRef?.instance ?? null; + } + constructor(private _viewContainerRef: ViewContainerRef) {} private _needToReCreateNgModuleInstance(changes: SimpleChanges): boolean { diff --git a/packages/common/test/directives/ng_component_outlet_spec.ts b/packages/common/test/directives/ng_component_outlet_spec.ts index 31f2aa35789..f8c77b833bf 100644 --- a/packages/common/test/directives/ng_component_outlet_spec.ts +++ b/packages/common/test/directives/ng_component_outlet_spec.ts @@ -304,6 +304,19 @@ describe('insert/remove', () => { expect(fixture.nativeElement.textContent).toBe('Hello World'); }); + + it('should be able to get the current component instance', () => { + const fixture = TestBed.createComponent(TestComponent); + fixture.detectChanges(); + const outlet = fixture.componentInstance.ngComponentOutlet!; + + expect(outlet.componentInstance).toBeNull(); + + fixture.componentInstance.currentComponent = InjectedComponent; + fixture.detectChanges(); + + expect(outlet.componentInstance).toBeInstanceOf(InjectedComponent); + }); }); describe('inputs', () => {