Get reference to a directive used in a component
Asked Answered
V

3

88

I have a component whose template looks something like this:

<div [my-custom-directive]>Some content here</div>

I need access to the MyCustomDirective class instance used here. When I want to get access to a child component, I use a ViewChild query.

Is there an equivalent feature to get access to a child directive?

Viborg answered 31/3, 2016 at 23:35 Comment(0)
G
121

You can use exportAs property of the @Directive annotation. It exports the directive to be used in the parent view. From the parent view, you can bind it to a view variable and access it from the parent class using @ViewChild().

Example With a plunker:

@Directive({
  selector:'[my-custom-directive]',
  exportAs:'customdirective'   //the name of the variable to access the directive
})
class MyCustomDirective{
  logSomething(text){
    console.log('from custom directive:', text);
  }
}

@Component({
    selector: 'my-app',
    directives:[MyCustomDirective],
    template: `
    <h1>My First Angular 2 App</h1>

    <div #cdire=customdirective my-custom-directive>Some content here</div>
    `
})
export class AppComponent{
  @ViewChild('cdire') element;

  ngAfterViewInit(){
    this.element.logSomething('text from AppComponent');
  }
}

Update

As mentioned in the comments, there is another alternative to the above approach.

Instead of using exportAs, one could directly use @ViewChild(MyCustomDirective) or @ViewChildren(MyCustomDirective)

Here is some code to demonstrate the difference between the three approaches:

@Component({
    selector: 'my-app',
    directives:[MyCustomDirective],
    template: `
    <h1>My First Angular 2 App</h1>

    <div my-custom-directive>First</div>
    <div #cdire=customdirective my-custom-directive>Second</div>
    <div my-custom-directive>Third</div>
    `
})
export class AppComponent{
  @ViewChild('cdire') secondMyCustomDirective; // Second
  @ViewChildren(MyCustomDirective) allMyCustomDirectives; //['First','Second','Third']
  @ViewChild(MyCustomDirective) firstMyCustomDirective; // First

}

Update

Another plunker with more clarification

Graviton answered 1/4, 2016 at 0:13 Comment(15)
The answer is great. But this can also be done without cdire directly like, @ViewChild(MyCustomDirective) element:MyCustomDirective; Then, in ngAfterViewInit - this.element.logSomething('text from...'). So why your approach when directly by just passing the type you can do so? Just for the clarification.Domicile
@Domicile Your approach is good. however, it assumes there's only one MyCustomDirective. If there are multiple directives, it'll match the first one. But, when using exportAs, you can specify any one in particular, for example, the second MyCustomDirective.Graviton
Using a template variable makes it easier to point out one single element if your template contains more than one that would otherwise match. It always depends on what you actually try to accomplish and what your situation is. If you want to get all then you also would use @ViewChildren()Lash
I agree but if you have more than one directives, you can also pass appropriate type.Can't you?Domicile
Actually, I haven't tried with directives myself yet, only with components, but from what I remember from reading in the docs it should work with directives as well.Lash
@Domicile I updated my answer with some code. I hope it'll clarify the difference between the three approaches.Graviton
@Abdulrahman. Yes absolutely. you can do it and its working. But I didn't get whether you are agreed with me or not? Are you?Domicile
look at this... same multiple directives and with another completely different directive plnkr.co/edit/8RQiStVPx7CCmEIkPsPz?p=previewDomicile
@Domicile I agree with you that your approach is working. But, what I and Günter Zöchbauer are trying to clarify is that when you have multiple instances of the same directive, you can't really access the one you want with ViewChild() unless you want all or the first instance of the type. I added new plunkerGraviton
@Domicile in your plunker, can you access the directive with content Second without using exportAs ?Graviton
Another option is to use the {read: SomeType} parameter in ViewChild as explained here: https://mcmap.net/q/187870/-what-is-the-read-parameter-in-viewchild-for. For example: @ViewChild('cdire', {read:MyCustomDirective}) secondMyCustomDirective: MyCustomDirective, and <div #cdire my-custom-directive>Second</div> (no exportAs needed).Magically
I also have the same concern how can reference a directive by template when reading angular doc angular.io/docs/ts/latest/guide/template-syntax.html#!#ref-vars. Now I see we need exportAs. Very good point! Thanks!Basidiospore
@ViewChild(MyCustomDirective, { static: false }) will do the trick. At least in Angular 8 for sure.Abmho
I use first approach using exportAs and works perfectly! Now I have an issue with the jest test for the parent component. I used this reference on the parent template #directiveLinkWithSkewDownNormReference="customNumericFieldsDirective" but then I get this error on the parent component jest test NG0301: Export of name 'customNumericFieldsDirective' not found!Botanical
Here the stackoverflow post with my issue #77846148Botanical
S
28

The sole remaining solution since 2019

As mentioned in the comments of the other answers, these other (previously valid) methods would not work with the more recent versions of Angular.


Rejoice, however, for there is an even simpler way to get it injected: directly from the constructor!

@Component({
   // ...
})
export class MyComponent implements OnInit {

  // Would be *undefined*
  // @ContentChild(MyDirective, { static: true })
  // private directive: MyDirective;

  constructor(private directive: MyDirective) { }

  ngOnInit(): void {
    assert.notEqual(this.directive, null); // it passes!
  }
}

Additionally, you can add multiple annotations to tell the Dependency Injection engine where to look for the content to inject, using @Self or @Optional for example 🙂

Shoreward answered 24/10, 2019 at 20:38 Comment(3)
how much cleaner is that! fwiw it Self() is the correct way to go and the value is available in the constructor. (womm)Tallage
I have an issue with this approach, it seems DI inject newly created instance of directive, not exactly those that used in my component, thus though directive instance not null as described, most of it properties not initialized (i did an experiment, add 'instanceId' field and it was not the same as in my component)Parahydrogen
How to get the directive instances within specific components which are situated in my page component? Let say I have the component which is not accessible on the page right away, but hidden under ngIf. When it is finally available (I managed to get the reference to the component itself using the approach ViewChild-setter-BehaviorSubject subscription), I want to get the reference to its directive(s)...Nerva
C
24

It appears that since @Abdulrahman's answer, directives can no longer be accessed from @ViewChild or @ViewChildren as these pass only items on the DOM element itself.

Instead, you must access directives using @ContentChild/@ContentChildren.

@Component({
    selector: 'my-app',
    template: `
    <h1>My First Angular 2 App</h1>

    <div my-custom-directive>First</div>
    <div #cdire=customdirective my-custom-directive>Second</div>
    <div my-custom-directive>Third</div>
    `
})
export class AppComponent{
  @ContentChild('cdire') secondMyCustomDirective; // Second
  @ContentChildren(MyCustomDirective) allMyCustomDirectives; //['First','Second','Third']
  @ContentChild(MyCustomDirective) firstMyCustomDirective; // First
}

There is also no longer a directives property on @Component attribute.

Continent answered 7/4, 2017 at 17:43 Comment(4)
The instances created using @ContentChild are undefined. Why ? For example, inside a component function this.firstMyCustomDirective is undefinedAversion
ContentChild or ViewChild do not seem to work and return an undefined variableIronlike
#34327245 I think there are some misunderstandings.Hengelo
As of 2019, this does not seem to be correct, ContentChild and ViewChild both seem to return undefined in ngAfterViewInitCatullus

© 2022 - 2024 — McMap. All rights reserved.