For Angular 12, this comment pointed me in the right direction for a dirty solution. I know this is not a good way of solving this in principle, but my use case required being able to access the component instance without knowing what it was at write-time, due to separation of concerns across multiple modules.
TL;DR:
class MyDirective {
constructor(private vcRef: ViewContainerRef) {}
private getHostComponent(): any {
return this.vcRef._lContainer[0][8];
}
}
You can access the ViewContainerRef
's _lContainer
property, which represents the state associated with the container. This LContainer
has an entry at index 0 (internally the HOST
constant) which is an LView
if the container is on a component node.
The LView
, in turn, has an entry at position 8 (internally the CONTEXT
constant) which is a reference to the component instance if the component this is attached to is a non-root component element (e.g. <app-*
).
This means you can access the host context component by accessing lContainer[HOST][CONTEXT]
.
Long answer to copy-paste with explanations:
class MyDirective {
constructor(private vcRef: ViewContainerRef) {}
private getHostElementFromViewContainerRef(): unknown | null {
// TL;DR of the below method:
// return this.vcRef._lContainer[0][8];
// Inspired by https://stackoverflow.com/questions/46014761/how-to-access-host-component-from-directive#comment119646192_48563965
const vcRef = this.vcRef as any; // We're accessing private properties so we cast to any to avoid awkward TS validation issues
// We fetch the component associated with the element this directive is attached to by navigating via the ViewContainerRef.
// The VCRef contains a reference to the LContainer, which represents the state associated with the container:
// https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/container.ts#L65
const lContainer = vcRef._lContainer;
if (!lContainer) {
return null;
}
// LView has all its elements defined as array elements, with keys hardcoded to numeric constants:
// https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/view.ts#L26-L57
// We care about two of them:
const HOST = 0; // https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/view.ts#L29
const CONTEXT = 8; // https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/view.ts#L37
// LContainer is an array, with the element at the HOST position being an LView if the container is on a Component Node.
// This means that this may not work if this directive is declared on a native HTML element.
// Note that LContainer uses the same indexes as LView, so it's the same HOST constant as declared in the LView interfaces file.
// https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/container.ts#L66-L72
const lView = lContainer[HOST];
if (!lView) {
return null;
}
// For a non-root component, the context is the component instance.
// So if this directive is correctly attached to an Angular Component (e.g. `<app-*`),
// this array entry will contain the instance of that component.
// https://github.com/angular/angular/blob/12.2.x/packages/core/src/render3/interfaces/view.ts#L173-L180
const contextElement = lView[CONTEXT];
return contextElement || null;
}
}