Skip to content

Commit e4c50b3

Browse files
crisbetoAndrewKushnir
authored andcommitted
feat(common): expose component instance in NgComponentOutlet (#58698)
Exposes the current instance of the component in the `NgComponentOutlet` directive. PR Close #58698
1 parent 18991d3 commit e4c50b3

File tree

3 files changed

+31
-7
lines changed

3 files changed

+31
-7
lines changed

goldens/public-api/common/index.api.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -490,9 +490,9 @@ export class NgClass implements DoCheck {
490490
}
491491

492492
// @public
493-
export class NgComponentOutlet implements OnChanges, DoCheck, OnDestroy {
493+
export class NgComponentOutlet<T = any> implements OnChanges, DoCheck, OnDestroy {
494494
constructor(_viewContainerRef: ViewContainerRef);
495-
// (undocumented)
495+
get componentInstance(): T | null;
496496
ngComponentOutlet: Type<any> | null;
497497
// (undocumented)
498498
ngComponentOutletContent?: any[][];
@@ -511,9 +511,9 @@ export class NgComponentOutlet implements OnChanges, DoCheck, OnDestroy {
511511
// (undocumented)
512512
ngOnDestroy(): void;
513513
// (undocumented)
514-
static ɵdir: i0.ɵɵDirectiveDeclaration<NgComponentOutlet, "[ngComponentOutlet]", never, { "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>;
514+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgComponentOutlet<any>, "[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>;
515515
// (undocumented)
516-
static ɵfac: i0.ɵɵFactoryDeclaration<NgComponentOutlet, never>;
516+
static ɵfac: i0.ɵɵFactoryDeclaration<NgComponentOutlet<any>, never>;
517517
}
518518

519519
// @public

packages/common/src/directives/ng_component_outlet.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,12 @@ import {
9393
*/
9494
@Directive({
9595
selector: '[ngComponentOutlet]',
96-
standalone: true,
96+
exportAs: 'ngComponentOutlet',
9797
})
98-
export class NgComponentOutlet implements OnChanges, DoCheck, OnDestroy {
98+
export class NgComponentOutlet<T = any> implements OnChanges, DoCheck, OnDestroy {
99+
// TODO(crisbeto): this should be `Type<T>`, but doing so broke a few
100+
// targets in a TGP so we need to do it in a major version.
101+
/** Component that should be rendered in the outlet. */
99102
@Input() ngComponentOutlet: Type<any> | null = null;
100103

101104
@Input() ngComponentOutletInputs?: Record<string, unknown>;
@@ -108,7 +111,7 @@ export class NgComponentOutlet implements OnChanges, DoCheck, OnDestroy {
108111
*/
109112
@Input() ngComponentOutletNgModuleFactory?: NgModuleFactory<any>;
110113

111-
private _componentRef: ComponentRef<any> | undefined;
114+
private _componentRef: ComponentRef<T> | undefined;
112115
private _moduleRef: NgModuleRef<any> | undefined;
113116

114117
/**
@@ -118,6 +121,14 @@ export class NgComponentOutlet implements OnChanges, DoCheck, OnDestroy {
118121
*/
119122
private _inputsUsed = new Map<string, boolean>();
120123

124+
/**
125+
* Gets the instance of the currently-rendered component.
126+
* Will be null if no component has been rendered.
127+
*/
128+
get componentInstance(): T | null {
129+
return this._componentRef?.instance ?? null;
130+
}
131+
121132
constructor(private _viewContainerRef: ViewContainerRef) {}
122133

123134
private _needToReCreateNgModuleInstance(changes: SimpleChanges): boolean {

packages/common/test/directives/ng_component_outlet_spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,19 @@ describe('insert/remove', () => {
304304

305305
expect(fixture.nativeElement.textContent).toBe('Hello World');
306306
});
307+
308+
it('should be able to get the current component instance', () => {
309+
const fixture = TestBed.createComponent(TestComponent);
310+
fixture.detectChanges();
311+
const outlet = fixture.componentInstance.ngComponentOutlet!;
312+
313+
expect(outlet.componentInstance).toBeNull();
314+
315+
fixture.componentInstance.currentComponent = InjectedComponent;
316+
fixture.detectChanges();
317+
318+
expect(outlet.componentInstance).toBeInstanceOf(InjectedComponent);
319+
});
307320
});
308321

309322
describe('inputs', () => {

0 commit comments

Comments
 (0)