ViewChild, ViewChildren & QueryList In Angular

ViewChild, ViewChildren & QueryList In Angular

To Query and retrieve the reference of the DOM element in the Component, use the ViewChild or ViewChildren decorators. ViewChild returns the first matched element, while ViewChildren produces a QueryList of all matching elements. These references can be used to manipulate the component's element properties.

To query a DOM element(s), the first argument to the ViewChild or ViewChildren must be the query selector, which can be a string or a type. The static option controls whether the query is run before or after change detection. When the element is associated with various kinds, the read-option allows us to query a different token than the default. All of this will be covered in this article.

ViewChild

The ViewChild query retrieves the first matching element from the DOM and modifies the component variable we're using it on.

Syntax

The viewChild has the syntax indicated below.

ViewChild(selector: string | Function | Type<any>, opts: { read?: any; static: boolean; }): any

On a Component Property, we use the viewChild decorator. There are two arguments required. A selector and a set of options.

selector: A string, a type, or a function that returns a string or type can all be used. The change detector searches for the first element that fits the selector and adds the element's reference to the component property. The change detector updates the component property when the DOM changes and a new element meets the selector.

opts: There are two options.

static: When the query is resolved, this value is set. True when the view is created for the first time (before the first change detection). If you want it to be resolved after each change detection, set False.

read: It can be used to read the various tokens from the queried items.

ViewChild Examples

Let's look at a few instances to learn about ViewChild.

Injecting Component or Directive Reference

Getting the reference of the Child Component in the Parent Component and manipulating its properties is one of ViewChild's uses. One of the ways the Parent can communicate with the kid components is through this method.

Consider the following ChildComponent as an example. It employs two approaches. Increment and Decrement are two terms that are used interchangeably.

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--;
  }
}

To gain a reference to the ChildComponent, we can use the ViewChild in the parent component.

@ViewChild(ChildComponent, {static:true}) child: ChildComponent;

The ViewChild in the code above searches the view for the first instance of the ChildComponent and updates the child variable. We can now call the ChildComponent's Increment and Decrement functions from the Parent.

The full code can be found 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();
  }
 
}

Using Template Reference Variable

Instead of using the component type, you can use Template Reference Variable.

You can assign a Template Reference Variable to a component, for example.

<child-component #child></child-component>

After that, use it in the ViewChild query to acquire the component's reference.

@ViewChild("child", { static: true }) child: ChildComponent;

Injecting HTML Element Using ElementRef

You can also use the Viewchild to query HTML elements.

To begin, give the HTML element a Template variable (#para in the example below). The ViewChild can then be used to query the element.

The ElementRef returned by ViewChild is nothing more than 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"
    }
}

Multiple Instances

The Template may contain many instances of the same component or element.

<child-component></child-component>
<child-component></child-component>

The first component is always returned by ViewChild.

@ViewChild(ChildComponent, {static:true}) child: ChildComponent;

We can use the ViewChildren, which we will discuss later in this article, to get all the instances of the Child Component.

ViewChild returns Undefined

One of the most typical issues we get while using ViewChild is that it returns undefined.

The issue occurs because we attempt to use the value before the ViewChild has fully initialized it.

The code follows, for example, returns Cannot read property 'increment' of undefined. When the constructor is called, the component's view has not yet been initialized. As a result, Angular has yet to update the kid variable with the ChildComponet reference.

export class AppComponent {
  title = 'Parent calls an @ViewChild()';
  
  @ViewChild(ChildComponent, {static:true}) child: ChildComponent;
 
  constructor() {
    this.child.increment()
  } 
 
}
 
//
Cannot read property 'increment' of undefined

Wait until Angular Initializes the View before proceeding. Once the View Initialization is complete, Angular raises the AfterViewInit life cycle hook. As a result, we can access the child variable using ngAfterViewInit.

ngAfterViewInit() {
  this.child.increment()
}

Now there are no mistakes in the code. Also, when ViewChild updates the values, the static option is taken into account.

Using Static Option in ViewChild

In the previous code, we used the static:true keyword.

The ViewChild query resolution time is determined by the static option.
  • Before any change detection is conducted, static:true resolves ViewChild.
  • After each change detection run, static:false will resolve it.
When the child is drawn dynamically, the static value becomes relevant. For instance, inside a ngIf or ngSwitch.

Consider the following code, in which the child component has been moved within the ngIf.

Add the following code under the child.component.html:

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

Add the following code under the child.component.ts:

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 code above throws a TypeError: Cannot read property 'increment' of undefined. Even if we set showCounter to true, the issue occurs.

Because Angular does not render the child component instantly in the example above. However, the child component is rendered after the initial change detection, which detects the value of showCounter.

Because we specified static: true, angular will attempt to resolve the ViewChild before running the initial change detection. As a result, the child variable will always be undefined.

Change the static: false to false now. The code will now function properly. That is Angular refreshes the ViewChild after each change detection.

Using the Read Option in ViewChild

Multiple types can be connected with a single element.

Consider the following code as an example. Both input and ngModel are now associated with the #nameInput template variable.

<input #nameInput [(ngModel)]="name">

The viewChild code below returns elementRef, which is the instance of the input element.

@ViewChild('nameInput',{static:false}) nameVar;

We use the Read token and ask for the type if we want to access the instance of the ngModel.

@ViewChild('nameInput',{static:false, read: NgModel}) inRef;
 
@ViewChild('nameInput',{static:false, read: ElementRef}) elRef;
@ViewChild('nameInput', {static:false, read: ViewContainerRef }) vcRef;

In Angular, every element has an ElementRef and a ViewContainerRef linked with it. There is always a component or directive instance if the element is a component or directive. An element can also have many directives applied to it.

If the ViewChild does not have a read token, it always returns the component instance. If not, the elementRef is returned.

Injecting a Provider from the Child Component

The services provided by the child component can likewise be injected.

import { ViewChild, Component } from '@angular/core';
 
@Component({
  selector: 'app-child',
  template: `<h1>Child With Provider</h1>`,
  providers: [{ provide: 'Token', useValue: 'Value' }]
})
 
export class ChildComponent{
}

The read property in the Parent component can be used to access the provider.

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;
}

Injecting TemplateRef

The TemplateRef can be accessed as indicated below:

<ng-template #sayHelloTemplate>
  <p> Say Hello</p>
</ng-template>

Component code

@ViewChild("sayHelloTemplate", { static: false }) tempRef: TemplateRef;

ViewChildren

The ViewChildren decorator is used to access the View's collection of element references.

ViewChildren is not the same as ViewChild. ViewChild always returns a single element reference. If there are numerous items, ViewChild will return the first one that matches.

ViewChildren always returns a QueryList with all of the entries. You can access each entry by iterating over the list.

Syntax

The viewChildren has the following syntax. Except for the static option, it's fairly similar to viewChild's syntax.

ViewChildren(selector: string | Function | Type<any>, opts: { read?: any; }): any

QueryList

ViewChildren and contentChildren both return QueryList as their return type.

The items returned by viewChildren or contentChildren are stored in a list by QueryList.

When the status of the application changes, Angular refreshes this list. Each time it detects a change, it does so.

An iterable interface is also implemented by the QueryList. This implies you may use for (var I of items) or ngFor in template *ngFor="let I of items" to iterate over it.

Subscribing to the changes Observable allows you to keep track of changes.

The methods and properties listed below can be used.
  • first: returns the list's first item.
  • last: retrieve the list's last item.
  • length: find out how long the objects are.
  • changes: Is anything that can be seen. When Angular adds, removes, or moves the child elements, it emits a new value.
It also supports array methods in JavaScript, such as map(), filter(), find(), reduce(), forEach(), and some().

ViewChildren Example

We have three input elements in the sample below, all of which use the ngModel directive.

To access the QueryList of all input components, we use the ViewChildren.

Finally, we can loop through the QueryList and access each element using this.modelRefList.forEach.

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)
    });
  }
}

Listening for QueryList Changes

To see if any new elements have been added, removed, or moved, we can subscribe to the changes observable.

We've used the ngIf directive to hide and show the input items in the example below.

We subscribe to the component class's observable modifications. The changes observable emits the most recent QueryList every time we use the ngIf to hide or add a component.

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)
    });
 
  }
}

Conclusion

In this article, we covered what is ViewChild, ViewChildren and QueryList in angular and how to use it.

I hope this article helps you and you will like it.👍

If you have any doubt or confusion then free to ask in the comment section.

Post a Comment

Previous Post Next Post