The ViewChild or ViewChildren decorators are used to Query and get the reference of the DOM element in the Component. ViewChild returns the first matching element and ViewChildren returns all the matching elements as a QueryList of items. We can use these references to manipulate element properties in the component.
To Query a DOM element(s), we must supply the query selector, which can be a string or a type as the first argument to the ViewChild or ViewChildren. The argument static determines whether the query is performed, before or after the change detection. The read option allows us to query a different token rather than the default and is useful when the element is associated with multiple types. We will learn all these in this tutorial.
The ViewChild query returns the first matching element from the DOM and updates the component variable on which we apply it.
The Syntax of the viewChild is as shown below.
ViewChild(selector: string | Function | Type<any>, opts: { read?: any; static: boolean; }): any
We apply the viewChild decorator on a Component Property. It takes two arguments. A selector and opts.
selector: can be a string, a type or a function that returns a string or type. The change detector looks for the first element that matches the selector and updates the component property with the reference to the element. If the DOM changes and a new element matches the selector, the change detector updates the component property.
opts: has two options.
static Determines when the query is resolved. True is when the view is initialized (before the first change detection) for the first time. False if you want it to be resolved after every change detection
read: Use it to read the different token from the queried elements.
Now, let us learn ViewChild using few examples.
One of the use cases of ViewChild is to get the reference of the Child Component in the Parent Component and manipulate its properties. This is one of the ways by which the Parent can communicate with the child components.
For Example consider the following ChildComponent. It has two methods. Increment & Decrement.
import { Component } from '@angular/core';
@Component({
selector: 'child-component',
template: `<h2>Child Component</h2>
current count is {{ count }}
`
})
export class ChildComponent {
count = 0;
increment() {
this.count++;
}
decrement() {
this.count--;
}
}
We can use the ViewChild in the parent component to get the reference to the ChildComponent
@ViewChild(ChildComponent, {static:true}) child: ChildComponent;
In the code above, the ViewChild looks for the first occurrence of the ChildComponent in the view and updates the child variable. Now we can invoke the methods Increment & Decrement of the ChildComponent from the Parent.
The complete code is as shown below.
import { Component, ViewChild } from '@angular/core';
import { ChildComponent } from './child.component';
@Component({
selector: 'app-root',
template: `
<h1>{{title}}</h1>
<p> current count is {{child.count}} </p>
<button (click)="increment()">Increment</button>
<button (click)="decrement()">decrement</button>
<child-component></child-component>
` ,
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Parent calls an @ViewChild()';
@ViewChild(ChildComponent, {static:true}) child: ChildComponent;
increment() {
this.child.increment();
}
decrement() {
this.child.decrement();
}
}
You can make use of Template Reference Variable instead of the component type.
For Example, you can assign a Template Reference Variable to a component.
<child-component #child></child-component>
and then use it in the ViewChild query to get the reference to the component.
@ViewChild("child", { static: true }) child: ChildComponent;
The Viewchild can also be used to query HTML elements.
First, assign a Template variable (#para in the example below) to the HTML element. You can then use the ViewChild to query the element.
ViewChild returns a ElementRef, which is nothing but a wrapper around the native HTML element.
import {AfterViewInit, Component, ElementRef, ViewChild} from '@angular/core';
@Component({
selector: 'htmlelement',
template: `
<p #para>Some text</p>
`,
})
export class HTMLElementComponent implements AfterViewInit {
@ViewChild('para',{static:false}) para: ElementRef;
ngAfterViewInit() {
console.log(this.para.nativeElement.innerHTML);
this.para.nativeElement.innerHTML="new text"
}
}
There could be multiple instances of the same component or element in the Template.
<child-component></child-component>
<child-component></child-component>
The ViewChild always returns the first component.
@ViewChild(ChildComponent, {static:true}) child: ChildComponent;
To get all the instances of the Child Component, we can make use of the ViewChildren, which we cover later in this tutorial.
ViewChild Returning undefined is one of the common errors, we encounter when we use them.
The error is due to the fact that we try to use the value, before the ViewChild initializes it.
For Example, the code below results in Cannot read property 'increment' of undefined. i.e. the component’s view is not yet initialized when the constructor is run. Hence, the Angular yet to update child variable with the reference to the ChildComponet.
export class AppComponent {
title = 'Parent calls an @ViewChild()';
@ViewChild(ChildComponent, {static:true}) child: ChildComponent;
constructor() {
this.child.increment()
}
}
//
Cannot read property 'increment' of undefined
The solution is to wait until the Angular Initializes the View. Angular raises the AfterViewInit life cycle hook once it completes the View Initialization. So we can use the ngAfterViewInit to access the child variable.
ngAfterViewInit() {
this.child.increment()
}
Now, the code does not give any errors.
The above code will also work with the
ngOnInit Life cycle hook. But it is not guaranteed to work all the time as the
Angular might not initialize all parts of the view, before raising the ngOnInit
hook. Hence it is always
better to use the ngAfterViewInit hook.
Also, when ViewChild updates the values also depends on the static option
We used the {static:true} in the above code.
The static option determines the timing of the ViewChild query resolution.
The value of the static becomes important when the child is rendered dynamically. For Example inside a ngIf or ngSwitch etc.
For Example consider the following code, where we have moved the child-component inside the ngIf.
child.component.html code box -->
<h1>ViewChild Example</h1>
<input type="checkbox" id="showCounter" name="showCounter" [(ngModel)]="showCounter">
<ng-container *ngIf="showCounter">
<p> current count is {{child?.count}} </p>
<button (click)="increment()">Increment</button>
<button (click)="decrement()">decrement</button>
<child-component></child-component>
</ng-container>
import { Component, ViewChild, AfterViewInit, OnInit, ChangeDetectorRef } from '@angular/core';
import { ChildComponent } from './child.component';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html' ,
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'ViewChild Example)';
showCounter: boolean = true
@ViewChild(ChildComponent, { static: true }) child: ChildComponent;
increment() {
this.child.increment();
}
decrement() {
this.child.decrement();
}
}
The above code results in a TypeError: Cannot read property 'increment' of undefined. The error occurs even if we assign true to showCounter
Because in the above case Angular does not render the child component immediately. But after the first change detection which detects the value of showCounter and renders the child component.
Since we used static: true, the angular will try to resolve the ViewChild before the first change detection is run. Hence the child variable always will be undefined.
Since we used static: true, the angular will try to resolve the ViewChild before the first change detection is run. Hence the child variable always will be undefined.
Now, change the static: false. Now the code will work correctly. I.e because after every change detection the Angular updates the ViewChild
A Single element can be associated with multiple types.
For Example, consider the following code. #nameInput template variable is now associated with both input & ngModel
<input #nameInput [(ngModel)]="name">
The viewChild code below, returns the instance of the input element as elementRef.
@ViewChild('nameInput',{static:false}) nameVar;
If we want to get the instance of the ngModel, then we use the Read token and ask for the type.
@ViewChild('nameInput',{static:false, read: NgModel}) inRef;
@ViewChild('nameInput',{static:false, read: ElementRef}) elRef;
@ViewChild('nameInput', {static:false, read: ViewContainerRef }) vcRef;
Every element in Angular is always has a ElementRef and ViewContainerRef associated with it. If the element is a component or directive then there is always a component or directive instance. You can also apply more than one directive to an element.
The ViewChild without read token always returns the component instance if it is a component. If not it returns the elementRef.
You can also inject the services provided in the child component.
import { ViewChild, Component } from '@angular/core';
@Component({
selector: 'app-child',
template: `<h1>Child With Provider</h1>`,
providers: [{ provide: 'Token', useValue: 'Value' }]
})
export class ChildComponent{
}
And in the Parent component, you can access the provider using the read property.
import { ViewChild, Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `<app-child></app-child>`,
})
export class AppComponent{
@ViewChild(ChildComponent , { read:'Token', static:false } ) childToken: string;
}
You can access the TemplateRef as shown below
<ng-template #sayHelloTemplate>
<p> Say Hello</p>
</ng-template>
Component code
@ViewChild("sayHelloTemplate", { static: false }) tempRef: TemplateRef;
ViewChildren decorator is used to getting the list of element references from the View.
ViewChildren is different from the ViewChild. ViewChild always returns the reference to a single element. If there are multiple elements the ViewChild returns the first matching element,
ViewChildren always returns all the elements as a QueryList. You can iterate through the list and access each element.
The Syntax of the viewChildren is as shown below. It is very much similar to syntax of viewChild except for the static option.
ViewChildren(selector: string | Function | Type<any>, opts: { read?: any; }): any
The ViewChildren is always resolved after the change
detection is run. i.e why it does not have static option. And also you cannot
refer to it in the
ngOnInit hook as it is yet to initialize.
The QueryList is the return type of ViewChildren and contentChildren .
QueryList stores the items returned by the viewChildren or contentChildren in a list.
The Angular updates this list, whenever the state of the application change. It does it on each change detection.
Changes can be observed by subscribing to the changes Observable.
You can use the following methods & properties.
first: returns the first item in the list.last: get the last item in the list.length: get the length of the items.changes: Is an observable. It emits a new value, whenever the Angular adds, removes
or moves the
child elements.It also supports JavaScript array methods like map(),filter() ,find(), reduce(),forEach(),some(). etc
In the example below, we have three input elements all using the ngModel directive
We use the ViewChildren to get the QueryList of all input elements
Finally, we can use the this.modelRefList.forEach to loop through the QueryList and access each element.
import { ViewChild, Component, ViewChildren, QueryList, AfterViewInit } from '@angular/core';
import { NgModel } from '@angular/forms';
@Component({
selector: 'app-viewchildren1',
template: `
<h1>ViewChildren Example</h1>
<input name="firstName" [(ngModel)]="firstName">
<input name="midlleName" [(ngModel)]="middleName">
<input name="lastName" [(ngModel)]="lastName">
<button (click)="show()">Show</button>
`,
})
export class ViewChildrenExample1Component {
firstName;
middleName;
lastName;
@ViewChildren(NgModel) modelRefList: QueryList<NgModel>;
show() {
this.modelRefList.forEach(element => {
console.log(element)
//console.log(element.value)
});
}
}
We can subscribe to the changes observable to find if any new elements are added/removed or moved.
In the example below, we have included ngIf directive to hide/show the input elements.
We subscribe to the changes observable in the component class. Every time we use the ngIf to hide or add the component, the changes observable emits the latest QueryList.
We subscribe to the changes observable in the component class. Every time we use the ngIf to hide or add the component, the changes observable emits the latest QueryList.
import { ViewChild, Component, ViewChildren, QueryList, AfterViewInit } from '@angular/core';
import { NgModel } from '@angular/forms';
@Component({
selector: 'app-viewchildren2',
template: `
<h1>ViewChildren Example</h1>
<input *ngIf="showFirstName" name="firstName" [(ngModel)]="firstName">
<input *ngIf="showMiddleName" name="midlleName" [(ngModel)]="middleName">
<input *ngIf="showlastName" name="lastName" [(ngModel)]="lastName">
<input type="checkbox" id="showFirstName" name="showFirstName" [(ngModel)]="showFirstName">
<input type="checkbox" id="showMiddleName" name="showMiddleName" [(ngModel)]="showMiddleName">
<input type="checkbox" id="showlastName" name="showlastName" [(ngModel)]="showlastName">
<button (click)="show()">Show</button>
`,
})
export class ViewChildrenExample2Component implements AfterViewInit {
firstName;
middleName;
lastName;
showFirstName=true;
showMiddleName=true;
showlastName=true;
@ViewChildren(NgModel) modelRefList: QueryList<NgModel>;
ngAfterViewInit() {
this,this.modelRefList.changes
.subscribe(data => {
console.log(data)
}
)
}
show() {
this.modelRefList.forEach(element => {
console.log(element)
//console.log(element.value)
});
}
}