@Viewchild is not working inside Directive, but working in Component
Asked Answered
B

2

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() {
  }
}
Booth answered 26/5, 2017 at 15:54 Comment(4)
I am using Angular v4.1.3 with CliBooth
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 workAgave
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 componentBooth
Think directive are mare metadata/tags... to identify/ help other stuff ...just to avoid confusion..Substantial
M
10

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.

Meander answered 14/9, 2018 at 21:46 Comment(3)
I was able to get ViewChild with this method as well. Thanks.Lala
Correction: You were able to get ContentChildren with this method. They are different.Meander
Sometimes you need to stop the command ng serve and do it again. I suppose it not always reload correctly. This cost me more than 1hMenander
Z
3

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!!

Zannini answered 26/5, 2017 at 16:9 Comment(6)
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 hereBooth
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??Booth
ViewChild are the HTML elements withing the template of component, and ContentChild are the HTML elements within the directives tags (for e.g. <my-dir> <div>hello i am content </div> </my-dir> ), hence you can find content child for all directives but ViewChild only for components as they have templates.Zannini
Except if you read the source code for directives: github.com/angular/angular/blob/6.1.7/packages/core/src/… 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.Meander
Curious - has anyone figured this out yet?Amaras
Untrue, also attribute directives can be found with @ViewChild (have been working with this for years already). Only for @ViewChildren it is different 😟Ablative

© 2022 - 2024 — McMap. All rights reserved.