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() {
}
}
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.
ViewChild is used to select an element from component's template while ContentChild is used to select projected content.
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.
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.
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 ContentChildren
is 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.
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!!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With