55

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.

AngularChef
  • 12,435
  • 6
  • 48
  • 64

8 Answers8

66

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.

Günter Zöchbauer
  • 490,478
  • 163
  • 1,733
  • 1,404
  • 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: https://stackoverflow.com/questions/46014977 – AngularChef Sep 02 '17 at 14:51
  • 1
    A generic solution is requested by many (as you can see in the linked issue), but currently there is no easy solution for that. – Günter Zöchbauer Sep 02 '17 at 15:09
  • @GünterZöchbauer I presume we could use an interface e.g. `constructor(private host: HostedComponentInterface){}` and require that the user of the directive implements that interface?? – Drenai Dec 12 '17 at 11:13
  • TypeScript interfaces don't exist at runtime and are therefore not supported for DI. – Günter Zöchbauer Dec 12 '17 at 11:17
  • You might be looking for something like https://stackoverflow.com/questions/35971943/query-is-not-working-on-inherited-classes/35971968#35971968 – Günter Zöchbauer Dec 12 '17 at 11:57
  • My component never gets injected, are there any limitations not specified here? – Lovro Gregorčič Dec 13 '17 at 14:53
  • I don't know of any limitations. Try to create a minimal reproduction in http://stackblitz.com. – Günter Zöchbauer Dec 13 '17 at 14:54
  • angular has so many limitations like this but never a workaround/solution. There's such a lack of support with concepts that regard just accessing a component/directive without having to know the exact component ahead of time. – Emobe May 24 '19 at 14:20
  • 1
    @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. – Günter Zöchbauer May 24 '19 at 15:50
  • I think the answer from Sunil Garg is a better one because this cannot be used in some general directive. – Loic Sep 23 '20 at 07:42
18

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;
Sunil Garg
  • 10,122
  • 12
  • 92
  • 133
  • 7
    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 Aug 23 '19 at 09:30
  • 2
    @Fredrik_Macrobond _.last(this.viewContainerRef['_hostView'][0].__ngContext__) – Devin Garner May 14 '20 at 23:10
  • Anyone found a decent solution for Ivy? Angular10 – Stephen Lautier Oct 28 '20 at 19:25
  • A very dirty solution with Angular 12: `(this.viewContainerRef as any)._hostLView[8]`. 8 refers to CONTEXT (inlined) in https://github.com/angular/angular/blob/a92a89b0eb127a59d7e071502b5850e57618ec2d/packages/core/src/render3/interfaces/view.ts#L180 To use with caution. – ARno May 25 '21 at 14:37
17

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.

Michiel Windey
  • 187
  • 1
  • 4
11

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

    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

Anthony
  • 7,121
  • 3
  • 33
  • 66
1
constructor(private vcRef: ViewContainerRef){
        let parentComponent=(<any>this.vcRef)._view.context;
}
Akshay Bohra
  • 335
  • 3
  • 6
  • 2
    While this code snippet may solve the question, [including an explanation](http://meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers) 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. – Derek Brown Jun 23 '18 at 02:16
  • `_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: https://stackoverflow.com/questions/4484424/underscore-prefix-for-property-and-method-names-in-javascript – N.J.Dawson Jul 19 '18 at 15:59
1

I do like this, it works on Angular 9.

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;
  }
}
shine
  • 440
  • 2
  • 7
1

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

Dharini
  • 11
  • 3
0

A possible workaround 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.

@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" .... >
AC101
  • 519
  • 5
  • 10