Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@Viewchild is not working inside Directive, but working in Component

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() {
  }
}
like image 577
mitrabrihas Avatar asked May 26 '17 15:05

mitrabrihas


People also ask

Can you use ViewChild in directive?

The @ViewChild decorator allows us to inject into a component class references to elements used inside its template, that's what we should use it for. Using @ViewChild we can easily inject components, directives or plain DOM elements.

What is the difference between ViewChild () and ContentChild ()?

ViewChild is used to select an element from component's template while ContentChild is used to select projected content.

Can directive be used as component?

A component is a single unit that encapsulates both view and logic whereas directives are used to enhance the behavior of components or dom elements and it doesn't have any templates. Component extends directive so every component is a directive.

What is the difference between ViewChild and ViewChildren?

Working with @ViewChildren is similar to @ViewChild, but the difference between the two is @ViewChildren provides a list of element references rather than returning a single reference. It is used to reference multiple elements. We can then iterate the list of the element referenced by the variable.


2 Answers

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.

like image 148
dudewad Avatar answered Sep 28 '22 04:09

dudewad


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

like image 30
Madhu Ranjan Avatar answered Sep 28 '22 04:09

Madhu Ranjan