91

Say I have the following markup:

<my-comp myDirective></my-comp> 

Is there any way I can access the component instance from the directive?

More specifically I want to be able to access the properties and methods of MyComponent from MyDirective, ideally without adding anything to the HTML above.

12 Answers 12

105

You can just inject it

class MyDirective { constructor(private host:MyComponent) {} 

A severe limitation is, that you need to know the type of the component in advance.

See also https://github.com/angular/angular/issues/8277
It also provides some workarounds for when you don't know the type in advance.

Sign up to request clarification or add additional context in comments.

10 Comments

Thank you, Günter, it works. Ideally I would need a generic solution that works for any component. In fact you might have a suggestion for what I'm trying to do here: stackoverflow.com/questions/46014977
A generic solution is requested by many (as you can see in the linked issue), but currently there is no easy solution for that.
TypeScript interfaces don't exist at runtime and are therefore not supported for DI.
You might be looking for something like stackoverflow.com/questions/35971943/…
@Emobe I haven't run into many situations where I couldn't find a solution. This was one of the hardest. For others there were usually good workarounds. Most design decisions were made for efficiency and I think it's worth it.
|
33

Your directive could be the generic one that can be applied to any of your components. So, in that case, injecting the component in constructor would not be possible, So here is one other way to do the same

Inject the ViewContainerRef in constructor

constructor(private _viewContainerRef: ViewContainerRef) { } 

and then get it using

let hostComponent = this._viewContainerRef["_data"].componentView.component; 

5 Comments

As of Angular 9.0.0-next.3, this solution doesn't work anymore. Do you have any clue where they might have hidden it now?
@Fredrik_Macrobond .last(this.viewContainerRef['_hostView'][0].__ngContext_)
Anyone found a decent solution for Ivy? Angular10
A very dirty solution with Angular 12: (this.viewContainerRef as any)._hostLView[8]. 8 refers to CONTEXT (inlined) in github.com/angular/angular/blob/… To use with caution.
The main issue with this solution is that it is an 'internal' solution and, as you can see in the previous comments, you will never be sure that the internal code is not changing from one version to another. So if you take this solution, you will have to review your code every time you are upgrading Angular. It's a good solution but it has a cost...
31

If you want to use the attribute directive on your custom components you could have those components extend from an abstract class and 'forwardRef' the abstract class type to your component type. This way you can make angular's DI select on the abstract class (within your directive).

Abstract class:

export abstract class MyReference { // can be empty if you only want to use it as a reference for DI } 

Custom Component:

@Component({ // ... providers: [ {provide: MyReference, useExisting: forwardRef(() => MyCustomComponent)} ], }) export class MyCustomComponent extends MyReference implements OnInit { // ... } 

Directive:

@Directive({ selector: '[appMyDirective]' }) export class CustomDirective{ constructor(private host:MyReference) { console.log(this.host); // no accessing private properties of viewContainerRef to see here... :-) } } 

This way you can use the directive on any component that extends your abstract class.

This will of course only work on your own components.

5 Comments

This was what I needed for reference any component with my directive
Remember to make the abstract class injectable else you will get an error in your directive. For example, add @Injectable({ providedIn: 'root'}) as an attribute to your abstract class
Some notes: 1. It works! 2. The forwardRef is actually not necessary, at least with latest versions of Angular. 4. You can use the abstract class as an interface: class MyCustomComponent extends AbstractCustomComponent implements MyReference, OnInit 3. What Alf Moh says is not necessary…
This is actually genius
@PhiLho, Please provide a full example because Michiel's solution works for me, but not yours.
28

This is taken from the github issue and works like a charm. The downside is needing to know the components beforehand, but in your case you would need to know the methods you're using anyway.

import { Host, Self, Optional } from '@angular/core'; export class ExampleDirective { constructor( @Host() @Self() @Optional() public hostCheckboxComponent : MdlCheckboxComponent, @Host() @Self() @Optional() public hostSliderComponent : MdlSliderComponent ) { if(this.hostCheckboxComponent) { console.log("host is a checkbox"); } else if(this.hostSliderComponent) { console.log("host is a slider"); } } 

Credit: https://github.com/angular/angular/issues/8277#issuecomment-323678013

2 Comments

When using @Optional I get null and when I remove @Optional I get: > No provider for YouComponent found in NodeInjector
@WBuck I am currently facing the exact same issue you described. How did you solve it?
7

A possible workaround to make the directive generic is to pass the template ref of component as @Input to the directive. This adds a little bit of extra html but it worked better than many other hacks I tried.

Here's a stackblitz example to see this in action.

@Directive({selector: '[myDirective]'}) export class MyDirective implements OnInit { @Input() componentRef: any; @Input() propName: string; ngOnInit(){ if (this.componentRef != null) { // Access component properties this.componentRef[this.propName]; } } } 

Usage in view:

<!-- Pass component ref and the property name from component as inputs --> <app-component #appComponentRef myDirective [componentRef]="appComponentRef" [propName]="'somePropInComponent'" .... > 

Also works with other directives. Use exportAs property of the @Directive decorator to get reference to the directive instance.

<form #myForm="ngForm" myDirective [componentRef]="myForm" [propName]="'ngSubmit'" ....> 

Comments

4

You can access the host component using ViewContainerRef.

constructor(private el: ViewContainerRef) {} ngOnInit() { const _component = this.el && this.el.injector && this.el.injector.get(MyComponent); } 

Reference: https://angular.io/api/core/ViewContainerRef

2 Comments

If you need to make decisions based on whether the host component is something specific: !!this.el.injector.get(MyComponent, undefined, InjectFlags.Optional) Returns true if MyComponent can be obtained from the injector. Otherwise false.
This is not a good solution, if you know the class in advance (MyComponent), you can inject it directly: constructor(private component: MyComponent) {
2

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; } } 

Comments

1

NOTE: this is hacky and will likely not work in future versions of Angular. In Angular 10, I was able to access the host component like this:

Similarly to @Sunil Garg 's solution, inject the ViewContainerRef in the ctor of the directive:

constructor(_viewContainerRef: ViewContainerRef)

Get the host component like this:

let hostComponent = (<any>_viewContainerRef)._lContainer[0][8];

Comments

1

I do like this, it works on Angular 9 & 11.

nativeElement is a DOM object, I can dynamicly set current Component's

  1. instance to a custom __component field on nativeElement;

  2. access nativeElement.__component in Directve;

    @Directive() export class FromItemComponentBase { constructor(private hostElement: ElementRef) { hostElement.nativeElement.__component=this; } } 

@Component({ selector: 'input-error', templateUrl: 'component.html' }) export class FromItemErrorComponent extends FromItemComponentBase { constructor(private hostElement: ElementRef) { super(hostElement); } } @Component({ selector: 'input-password', templateUrl: 'component.html' }) export class FromItemPasswordComponent extends FromItemComponentBase { constructor(private hostElement: ElementRef) { super(hostElement); } } 

@Directive({selector: 'input-error,input-password,input-text'}) export class FormInputDirective { component:FromItemComponentBase; constructor(private hostElement: ElementRef) { this.component=hostElement.nativeElement.__component; } } 

Comments

0
constructor(private vcRef: ViewContainerRef){ let parentComponent=(<any>this.vcRef)._view.context; } 

2 Comments

While this code snippet may solve the question, including an explanation really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion.
_view.context implies _view should be private and therefore you appear to be doing this in a non-standard way, just as a shout for anyone coming across this later. cite: stackoverflow.com/questions/4484424/…
0

Having viewContainerRef: ViewContainerRef in constructor and having following code (this.viewContainerRef as any)._hostLView[8] works perfectly. But it looks hacky, so better to pass the component reference as Input parameter to the directive, as described below.

<my-comp myDirective [hostComponent]="hostComponent"></my-comp>

In myCompComponent.ts,

hostComponent = this;

In myDirective.ts,

@Input() hostComponent: Component;

Comments

0

I tried two solutions from here:
Michiel Windey's one (using an abstract class as interface for the components where the directive will be used), and Anthony's one (using @Host() @Self() @Optional()).
Both work with Angular 11.
Both are non hacky and are probably future-proof, unlike the hacky solutions using undocumented private fields…

The second one has the advantage to access the existing components without changing them. But you probably need to have lot of checks and special cases to handle, depending on which component is really injected.

The first one has the inconvenience to require the components to be modified, but you can define in the interface (abstract class) all the fields and methods you are going to use in the directive, and thus you can access them with a single parameter, without needing to check what kind of component is injected.

So your choice really depend on your use case.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.