How to use @ViewChild() in Angular 9
What is @ViewChild() and when should we use it?
ViewChild is a decorator and it's mostly used within angular when you need to select a particular element from the view.
Table of contents:
1. Select a particular directive
2. Select a particular HTML element
3. Difference between ViewChild { static: false }
and { static: true }
4. Using read
to inject a different configuration object
The following examples will use Angular 9, I say this because starting from version 9 you don't need to specify the second argument which defaults to false ({ static: false }), anything else remains the same.
1. Select a particular directive
@Directive({selector: 'child-directive'})
class ChildDirective {
@Input() id! :number;
ngAfterViewInit() {
console.log("ngAfterViewInit child")
}
}
@Component({
selector: 'select-directive',
template: `
<child-directive id="1"></child-directive>
`
})
class SelectDirectiveComponent implements OnInit, AfterViewInit {
@ViewChild(ChildDirective) child!: ChildDirective;
ngAfterViewInit() {
console.log("ngAfterViewInit parent with child = ", this.child);
}
}
As you might seen above it's pretty simple to select an html element from the view.
By using @ViewChild(ChildDirective) child: ChildDirective;
we tell angular that we need to inject into our reference variable child
the element from our view template with type ChildDirective.
By default angular will search for our ChildDirective
after all the elements from our view have been created and the bindings have been initiated. Will see later, how to get the reference to our view before all the binding has been performed.
2. Select a particular element from view
@Component({
selector: 'select-element',
template: `
<div #myDiv>
</div>
`
})
class SelectElementComponent implements AfterViewInit {
@ViewChild('myDiv') child: HTMLElement;
ngAfterViewInit() {
console.log("ngAfterViewInit", this.child);
}
}
Selecting an html element from view, it's pretty similar with selecting a directive, the main changes are with the aguments that we pass to our @ViewChild
decorator.
Instead of using a type, we are going to specify the element that needs to be selected by using a string.
In our example above we are using #myDiv
to identify our element in the view and @ViewChild('myDiv')
to tell angular that we need to select that spcific element from our view.
3. Difference between ViewChild static false and static true properties
If you had a chance to look over ViewChild decorator you might have seen that @ViewChild
decorator accepts 2 arguments.
Now we are going to explore what ca we do if we are to use the second argument.
The second argument that ViewChild receives is an object which can have two properties
static
and read
.
Let's talk first about the static
option.
Before Angular 9, this static
option had a value of true by default.
What { static: true }
does?
Having static
set to true
will result in telling angular that we need to get the reference to that target element as soon as the component is created, however this means that we are going to get the reference
before our element had a chance to bind the inputs and init it's view.
Example
@Directive({selector: 'child-directive'})
class ChildDirective {
@Input() id! :number;
ngAfterViewInit() {
console.log("child");
}
}
@Component({
selector: 'select-directive',
template: `
<child-directive id="1"></child-directive>
`
})
class SelectDirectiveComponent implements AfterViewInit {
@ViewChild(ChildDirective, { static: true}) child: ChildDirective;
ngOnInit() {
console.log("ngOnInit", this.child);
}
ngAfterViewInit() {
console.log("ngAfterViewInit", this.child);
}
}
Will print:
ngOnInit ChildDirective {}
ngAfterViewInit child
ngAfterViewInit ChildDirective {id: "1"}
As you have seen, only after the child has it's view created we will see the data for our ChildDirective
available in ngAfterViewInit
from parent element.
When should you use { static: true }
?
One usecase could be, when you need to access a child element's instance fields that don't rely for their values on inputs from other elements.
Example:
@Directive({selector: 'child-directive'})
class ChildDirective {
@Input() id! :number;
public childName = 'childName';
ngAfterViewInit() {
console.log("child ngAfterViewInit");
}
}
@Component({
selector: 'select-directive',
template: `
<child-directive id="1"></child-directive>
`
})
class SelectDirectiveComponent implements AfterViewInit {
@ViewChild(ChildDirective, { static: true}) child: ChildDirective;
ngOnInit() {
console.log("ngOnInit", this.child);
}
ngAfterViewInit() {
console.log("ngAfterViewInit", this.child);
}
}
So, { static: true }
only makes sense when you have a child that's using an instance field with a predefined value.
What { static: false } does?
It means that angular is going to look for our target element only after the view has been created and in most of the cases this is the way you'll use it.
Example:
@Directive({selector: 'child-directive'})
class ChildDirective {
@Input() id! :number;
public childName = 'childName';
ngAfterViewInit() {
console.log("ngAfterViewInit child");
}
}
@Component({
selector: 'select-directive',
template: `
<child-directive id="1"></child-directive>
`
})
class SelectDirectiveComponent implements AfterViewInit {
@ViewChild(ChildDirective, { static: false}) child: ChildDirective;
ngOnInit() {
console.log("ngOnInit", this.child);
}
ngAfterViewInit() {
console.log("ngAfterViewInit", this.child);
}
}
Will output something like this:
ngOnInit undefined
ngAfterViewInit child
ngAfterViewInit ChildDirective {childName: "childName", id: "1"}
So, this is the most used approach and it will make angular look for our target element only after the view has been created.
4. Using read to inject a different configuration object
export const TestInjectable = new InjectionToken<Inject>('someToken');
interface Inject {
val: string;
}
@Directive({selector: 'child-directive',
providers: [{
provide: TestInjectable, useValue: {val: 'someValue'}
}]
})
class ChildDirective {
@Input() id! :number;
public childName = 'childName';
ngAfterViewInit() {
console.log("ngAfterViewInit child");
}
}
@Component({
selector: 'select-directive-read',
template: `
<child-directive id="1"></child-directive>
`
})
class SelectDirectiveReadComponent implements AfterViewInit {
@ViewChild(ChildDirective, { read: TestInjectable}) child: Inject;
@ViewChild(ChildDirective, { read: ElementRef}) childTypeElementRef: ElementRef;
@ViewChild(ChildDirective, { read: ChildDirective}) childTypeChildrenDirective: ChildDirective;
ngAfterViewInit() {
console.log("ngAfterViewInit", this.child);
console.log("ngAfterViewInit", this.childTypeElementRef);
console.log("ngAfterViewInit", this.childTypeChildrenDirective);
}
}
When you run this code, you'll see that:
-
this.childTypeElementRef
will have an instance of ElementRef -
this.child
will have an instance of TestInjectable -
this.childTypeChildrenDirective
will have an instance of ChildDirective
Why does all the the above code works?
Thanks to the read configuration parameter, we can specify for our ViewChild decorator that we need to inject a specific instance from the element that angular has queried for.
Using InjectionToken
we provided a value of type TestInjectable
for ChildDirective and this means we can use
@ViewChild(ChildDirective, { read: TestInjectable}) child: Inject;
to get that specific instance from our ChildDirective
.
For others instances like ElementRef
and ChildDirective
angular will perform the same checks.
When should we use the read
option?:
When we need to get an injectable instnace from a child element it's useful to use a ViewChild.
Live example also available here
Stay tuned to get more tutorials on Web development
Article first posted on danielpdev.io
Top comments (1)
Hey Daniel, Excellent article and well written!