10

I am just playing around with @ViewChild/@ContentChild, and I was surprised to see that the @ViewChild is not working inside directive and is working fine for component.But in Directive its not working. I tried with AfterViewInit hook, so life cycle hook is not the reason. Something else is the issue here,Please find the code below.

app.component.html

<div appMain >
  <div #testContentDiv style="background-color: grey">
    <p>This is the first p tag</p>
    <p>This is the second p tag</p> 
  </div>
  <div #testViewDiv style="background-color: yellow">
    <p>This is the first p tag</p>
    <p>This is the second p tag</p>
  </div>
  <app-test-child></app-test-child>
</div>

test-dir.ts --Directive

import { Directive, ViewChild, ElementRef, OnInit, AfterViewInit, AfterContentInit, ContentChild } from '@angular/core';

@Directive({
  selector: '[appMain]'
})
export class MainDirective implements OnInit, AfterContentInit, AfterViewInit {

  constructor() { }
  // tslint:disable-next-line:member-ordering
  @ContentChild('testContentDiv') testContent: ElementRef;
  @ViewChild('testViewDiv') testView: ElementRef;
  ngOnInit() {
    //Called after the constructor, initializing input properties, and the first call to ngOnChanges.
    //Add 'implements OnInit' to the class.
    // console.log(this.test.nativeElement);

  }

  ngAfterContentInit() {
    //Called after ngOnInit when the component's or directive's content has been initialized.
    //Add 'implements AfterContentInit' to the class.
    console.log('Content Div: ngAfterContentInit:  ' + this.testContent.nativeElement);
    // console.log('View Div: ngAfterContentInit: ' + this.testView.nativeElement);


  }

  ngAfterViewInit() {
    //Called after ngAfterContentInit when the component's view has been initialized. Applies to components only.
    //Add 'implements AfterViewInit' to the class.
    console.log('Content Div:ngAfterViewInit: ' + this.testContent.nativeElement);
    console.log('View Div: ngAfterViewInit: ' + this.testView.nativeElement);

  }
}

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = "App works";

  constructor() {
  }
}
  • I am using Angular v4.1.3 with Cli – Brihaspati Bharani May 26 '17 at 15:56
  • _I was surprised to see that the @ViewChild is not working inside directiv_ - directives don't have views/templates, what were you expecting to find? `@ContentChild` should work – Max Koretskyi May 26 '17 at 16:02
  • ContentChild does work. But why not ViewChild? I am not trying to view any template, instead I am trying it reference the element using ViewChild which is already initialised for the component – Brihaspati Bharani May 26 '17 at 16:54
  • Think directive are mare metadata/tags... to identify/ help other stuff ...just to avoid confusion.. – dipak May 26 '17 at 17:14

2 Answers2

9

At least as of Angular v6.x:

Accoring to the Angular source code for directives, it is indeed possible to select children. However, the standard way of just using the @ViewChildren or @ContentChildren decorators does not appear to work for me. Also, I am unable to get @ViewChildren to work despite the docs.

@ContentChildren, however, does work for me. You need to decorate the Directive itself with the queries property as such (this directive is dumbed down for clarity, you'll still need a selector and other stuff to make it work):

@Directive({
  queries: {
    // Give this the same name as your local class property on the directive. "myChildren" in this case
    myChildren: new ContentChildren(YourChild),
  },
})
export class MyDirective implements AfterContentInit {
  // Define the query list as a property here, uninitialized.
  private myChildren: QueryList<YourChild>;

  /**
   * ngAfterContentInit Interface Method
   */
  public ngAfterContentInit() {
    // myChildren is now initialized and ready for use.
  }
}

This suffices for me so I'm not going to waste more time figuring out why ViewChildren doesn't work. My understanding of the difference between ViewChildren and ContentChildrenis that ContentChildren selects from <ng-content> tags, where ViewChildren selects straight from the view itself. So, the behavior seems backwards to me, but there is probably a justification for it.

As expected, ContentChildren are not available until the ngAfterContentInit hook, so don't let that one bite you.

dudewad
  • 13,215
  • 6
  • 37
  • 46
2

There are three kinds of directives in Angular:

Components — directives with a template.

Structural directives — change the DOM layout by adding and removing DOM elements.

Attribute directives — change the appearance or behavior of an element, component, or another directive.

So by definition Components are the only directive with a template so you can find @ViewChild only for components.

Read more about it here.

Hope this helps!!

Madhu Ranjan
  • 17,334
  • 7
  • 60
  • 69
  • 1
    If it cannot be used in directive, then why does it compile?? And also ViewChild documentation doesn't say that it cannot be used, there is something else that I am missing here – Brihaspati Bharani May 26 '17 at 16:50
  • I am not trying view the template, but instead I am trying to get reference to the initialised template through ViewChild, shouldn't that be possible?? – Brihaspati Bharani May 26 '17 at 16:52
  • 1
    `ViewChild` are the HTML elements withing the template of component, and `ContentChild` are the HTML elements within the directives tags (for e.g. `
    hello i am content
    ` ), hence you can find content child for all directives but ViewChild only for components as they have templates.
    – Madhu Ranjan May 26 '17 at 17:10
  • 1
    Except if you read the source code for directives: https://github.com/angular/angular/blob/6.1.7/packages/core/src/metadata/directives.ts#L200 It clearly states that you can have view and content child queries. So, no, you're wrong - there is a way to do this. I cannot get it to work yet but will post an answer when I figure it out. – dudewad Sep 14 '18 at 21:35
  • Curious - has anyone figured this out yet? – coder101 Apr 22 '19 at 16:38
  • Untrue, also attribute directives can be found with `@ViewChild` (have been working with this for years already). Only for `@ViewChildren` it is different – Youp Bernoulli Dec 13 '22 at 10:30