87

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?

Kamil Naja
  • 6,267
  • 6
  • 33
  • 47
Ben Dilts
  • 10,535
  • 16
  • 54
  • 85

3 Answers3

119

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

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Abdulrahman Alsoghayer
  • 16,462
  • 7
  • 51
  • 56
  • 9
    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. – micronyks Apr 01 '16 at 05:10
  • 7
    @micronyks 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`. – Abdulrahman Alsoghayer Apr 01 '16 at 05:19
  • 2
    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()` – Günter Zöchbauer Apr 01 '16 at 05:19
  • 1
    I agree but if you have more than one directives, you can also pass appropriate type.Can't you? – micronyks Apr 01 '16 at 05:22
  • 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. – Günter Zöchbauer Apr 01 '16 at 05:30
  • @micronyks I updated my answer with some code. I hope it'll clarify the difference between the three approaches. – Abdulrahman Alsoghayer Apr 01 '16 at 05:30
  • @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? – micronyks Apr 01 '16 at 05:45
  • look at this... same multiple directives and with another completely different directive http://plnkr.co/edit/8RQiStVPx7CCmEIkPsPz?p=preview – micronyks Apr 01 '16 at 05:49
  • @micronyks 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 [plunker](http://plnkr.co/edit/kkz6Q919WBQe3h35Pl3L?p=preview) – Abdulrahman Alsoghayer Apr 01 '16 at 05:54
  • @micronyks in your plunker, can you access the directive with content `Second` without using `exportAs` ? – Abdulrahman Alsoghayer Apr 01 '16 at 05:58
  • 7
    Another option is to use the {read: SomeType} parameter in ViewChild as explained here: http://stackoverflow.com/a/37476195/1736032. For example: `@ViewChild('cdire', {read:MyCustomDirective}) secondMyCustomDirective: MyCustomDirective`, and `
    Second
    ` (no exportAs needed).
    – dae721 Sep 14 '16 at 19:32
  • I also have the same concern how can reference a directive by template when reading angular doc https://angular.io/docs/ts/latest/guide/template-syntax.html#!#ref-vars. Now I see we need `exportAs`. Very good point! Thanks! – Nguyen Tran Mar 01 '17 at 03:20
  • `@ViewChild(MyCustomDirective, { static: false })` will do the trick. At least in Angular 8 for sure. – webpreneur Apr 01 '20 at 07:26
25

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

ccjmne
  • 9,333
  • 3
  • 47
  • 62
  • 1
    how much cleaner is that! fwiw it `Self()` is the correct way to go and the value is available in the constructor. (womm) – jenson-button-event Jan 16 '20 at 06:44
  • 2
    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) – igorGIS Oct 19 '21 at 10:27
  • 1
    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)... – Alexander Sep 21 '22 at 13:33
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.

Tobias J
  • 19,813
  • 8
  • 81
  • 66
  • 7
    The instances created using @ContentChild are undefined. Why ? For example, inside a component function `this.firstMyCustomDirective` is `undefined` – Saurabh Tiwari Jun 04 '17 at 17:23
  • 3
    ContentChild or ViewChild do not seem to work and return an undefined variable – ninja Aug 30 '17 at 06:28
  • 3
    https://stackoverflow.com/questions/34326745/whats-the-difference-between-viewchild-and-contentchild I think there are some misunderstandings. – maxisam Nov 17 '17 at 21:01
  • 2
    As of 2019, this does not seem to be correct, ContentChild and ViewChild both seem to return undefined in ngAfterViewInit – EnderWiggin Jul 10 '19 at 11:05