Wednesday, June 17, 2020

Querying the DOM with @ViewChild and @ViewChildren

Introduction

DOM manipulation is responsible for modifying existing DOM elements and changing their current appearance or behavior. You may also need to modify other crucial parts of an application, such as components, directives, and other DOM elements. Both ViewChild and ViewChildren are used to communicate between the components to access the data.

@ViewChild and @ViewChildren are the types of decorators used to access the child component class and its different properties into the parent component. It's similar to the inheritance.

Let's learn about each of them in more detail.

What is @ViewChild in Angular?

The @ViewChild decorator is used to query a single DOM element from the DOM tree and lets you manipulate it. To select an element from the template, three different parameters can be used.

  • Selector: The selector of the element to query. It can be a directive type or a name.
  • Read: Read a different token from the queried elements.
  • Static: This is one of the new features introduced in Angular 8, and it indicates whether to resolve query results before change detection runs.

Let's see an example of how to use the @ViewChild decorator.

First, create a new component other than the app component by using the below ng command.

1
Ng generate component democomponent
shell

democomponent.component.html

1
2
3
<div>
    This is {{ componentName }} component
</div>
html

Here we have used the local input variable, which is declared within the component class to print the value coming from the parent component.

Declare the @Input variable in a component, as below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Component, OnInit, Input } from "@angular/core";

@Component({
  selector: "app-democomponent",
  templateUrl: "./democomponent.component.html",
  styleUrls: ["./democomponent.component.css"]
})

export class DemocomponentComponent implements OnInit {

  // To get component name from app component
  @Input() componentName: string;
  constructor() {}
}
typescript

We are done with the demo component. Now, we will use our child component, which we have created previously, and manipulate it.

app.component.html

1
2
3
<app-democomponent componentName="DEMO">

</app-democomponent>
html

If you notice, we have used additional property along with the child component selector, which is componentName=DEMO.

app.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Component, ViewChild } from "@angular/core";
import { DemocomponentComponent } from "./democomponent/democomponent.component";

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})

export class AppComponent {
  name = "Angular";

  // Getting the reference
  @ViewChild(DemocomponentComponent, { static: false }) hello: DemocomponentComponent;
  ngAfterViewInit() {
     console.log("Hello ", this.hello.componentName);
  }
}
typescript

We can directly access the property of a child component using the 'hello' variable.

1
2
3
ngAfterViewInit() {
  console.log("Hello ", this.hello.componentName);
}
typescript

Notice that we have used ngAfterViewInit() hooks. Why, you might ask? When the application loads, the child element may not be loaded completely, and if we try to access the component, it returns undefined. To resolve this, we use the lifecycle hook, which allows us to access any element after the view is initialized.

We can also access any specific element by using @ViewChild instead of referencing the whole component, which we have done so far. Replace the code with the below code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Component, ViewChild, ElementRef  } from "@angular/core";
import { DemocomponentComponent } from "./democomponent/democomponent.component";

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})

export class AppComponent {
  name = "Angular";
  // Getting the reference of the button control
  @ViewChild("myButton", { static: false }) myButton: ElementRef;

  ngAfterViewInit() {
   console.log("Hello ", this.myButton.nativeElement);
  }
}
typescript

What we have changed is that the button control element is now the element we have used in our HTML.

app.component.html

1
2
3
<div>
 <button type="button" #myButton>My Button</button>
</div>
html

To access the native element, we can use ElementRef, which returns the native HTML element. It can be accessed from the component like this:

1
@ViewChild("myButton", { static: false }) myValue: ElementRef;
typescript

And it can access the native element using ngAfterViewInit(), like this:

1
2
3
ngAfterViewInit() {
    console.log("Hello ", this.myValue.nativeElement);
}
typescript

The output of the above example will look like this:

Native element using TemplateRef

As you can see into the above output into the console, we get the response as a native element because ElementRef returns the native element based on the reference taken along with @ViewChild.

Thus, we can try any of the above ways to access the child or native element from the template.

What is @ViewChildren in Angular?

Unlike @ViewChild, the @ViewChildren decorator is used to accessing multiple elements. The response of the element list will always be a QueryList.

The QueryList will always be updated whenever any child element added, updated, or removed from the HTML DOM tree.

Let's look at an example to get a better idea of how @ViewChildren works.

Create a new component using the below ng command.

1
Ng generate component hello
shell

hello.component.html

1
2
3
<div>
    <h1>Hello {{name}}!</h1> 
</div>
html

hello.component.ts

1
2
3
4
5
6
7
8
9
10
11
import { Component, Input } from '@angular/core';

@Component({
  selector: "hello",
  templateUrl: "./hello.component.html",
  styleUrls: ["./hello.component.css"]
})

export class HelloComponent  {
  @Input() name: string;
}
typescript

In this component, the @Input decorator is used to get the value from the parent component.

Let dig deeper and use QueryList along with @ViewChildren in a simple example.

app.component.html

1
2
3
4
5
<hello name="{{ name }}"></hello>

<hello name="{{ name }}"></hello>

<hello name="{{ name }}"></hello>
html

As you can see, we have used the selector three times along with the name property.Now let's try to access it using @ViewChildren, as below.

app.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import {
 Component,
 ViewChildren,
 AfterViewInit,
 QueryList
} from "@angular/core";
import { HelloComponent } from "./hello.component";

@Component({
 selector: "my-app",
 templateUrl: "./app.component.html",
 styleUrls: ["./app.component.css"]
})

export class AppComponent implements AfterViewInit {

 name = "Angular";

 // Accessing multiple native DOM elements using QueryList
 @ViewChildren(HelloComponent) myValue: QueryList<HelloComponent>;

 ngAfterViewInit() {
     console.log("Hello ", this.myValue);
 }

}
typescript

Here in this component, we need to access multiple native DOM elements using @ViewChildren by referencing the child component, as below.

1
@ViewChildren(HelloComponent) myValue: QueryList<HelloComponent>;
typescript

As we know, the hello component is our child component and used three times in the template. After accessing the list, it will always return as QueryList.

The output of the above code will look like this:

QueryList

It seems messed up; lets convert it into an array for better visibility.

1
2
3
ngAfterViewInit() {
   console.log("Hello ", this.myValue.toArray());
}
typescript

Now, when we run the updated example, the exact output should look like this:

QueryList to Array

In this way, we can access multiple DOM elements by using the decorator @ViewChildren.

The example above shows how we can take advantage of @ViewChildren and @ViewChild decorators. We can decorate a property, and this way, query the view of a component.

When to use @ViewChild and @ViewChildren in Angular?

As discussed, both @ViewChild and @ViewChildren only look at the DOM elements that are in our view templates directly.

Another critical difference is that @ViewChild returns a single native DOM element as a reference, while the @ViewChildren decorator returns the list of different native DOM elements in the form of QueryList, which contains the set of elements.

@ViewChild() in a component can communicate with another component or a directive. But if we want to access multiple child references, then we have to use @ViewChildren.

@ViewChildren works the exact same way as @ViewChild, except it returns the QueryList of all elements or directives that match with the given selector, instead of just the first match.

Similar to @ViewChild, if the view DOM changes and new children match the given selector, the property will be updated.

These are some differences between the @ViewChild and @Viewhildren decorators and how to use them in different situations.

Summary

In this guide, we learned about querying the DOM using two different methods, @ViewChild and @ViewChildren.

We have also seen where we can use each of them indifferent scenarios. We will learn other related concepts in upcoming guides, so keep reading and stay tuned.

No comments:

Post a Comment