Angular 10/9 ElementRef/ViewChild/Renderer2 By Example

Angular 10/9 ElementRef/ViewChild/Renderer2 By Example

In this tutorial, we'll be learning about ElementRef with an Angular 10 example.

We'll see what ElementRef is and how it can be used with the ViewChild decorator, and AfterViewInit life-cycle event to access the DOM in the Angular way to avoid using native JavaScript APIs like querySelector, querySelectorAll, getElementbyId and getElementsByClassName for querying elements by ID, class name, or any selector.

Finally, we'll see why using ElementRef for directly accessing the DOM is not the best way in Angular 10 and previous versions but instead you need to use Renderer2.

We'll use the online Stackblitz development IDE, so you don't need to set up your development environment or create an Angular 10/9 project for our quick examples.

Angular 10 ElementRef Example

Visit the https://stackblitz.io/ website. If you don't have an account, you can simply use GitHub to quickly sign up and create a new Angular app with the latest version.

What's Angular ElementRef?

Before writing any code, let's see what ElementRef is and why it's used in Angular.

According to the official docs

Angular ElementRef is a wrapper around a native element inside of a View.

It's simply a class that wraps native DOM elements in the browser and allows you to work with the DOM by providing the nativeElement object which exposes all the methods and properties of the native elements.

Angular 10/9 Example with ElementRef, ViewChild and AfterViewInit

Let's now see a simple Angular 10 example for how to access the DOM using the ViewChild decorator combined with the ElementRef interface. Go back to your Angular application created in Stackblitz and open the the src/app/app.component.ts file. Next, add the following changes:

import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements AfterViewInit {

  @ViewChild("myDiv") divView: ElementRef;

  ngAfterViewInit(){

    console.log(this.divView);
    this.divView.nativeElement.innerHTML = "Hello Angular 10!";

  }
}

We simply import the AfterViewInit, ElementRef, ViewChild APIs. Next, we implement the AfterViewInit interface which provides our component with the ngAfterViewInit() life-cycle method that gets called after the view is intialized (This is important since we can query or modify the view only after it's intialized).

Next, we declare the divView component property and decorate it with @ViewChild() decorator which is used to create a DOM query configuration. Here, we create a query that looks for the element with the myDiv template reference.

The type of the divView variable is our ElementRef interface which means we can access the nativeElement object that reprents the DOM element in the browser.

After that, we add the ngAfterViewInit() life-cycle event and set the innerHTML of our <div> to Hello Angular 10!

This is equivalent to document.getElementById("myDiv").innerHTML = "Hello Angular 10!"; in plain JavaScript. Except that we use template reference variables instead of IDs in Angular.

We also printed the divView in the console. If you open the Stackblitz console, you'll see the properties of ElementRef:

Angular 10 ElementRef Example

To make our code works as in the screenshot, one thing left is adding a <div> with the myDiv reference in our component template. Open the src/app/app.component.html file and change as follows:

<h1>Angular 10 Example with ViewChild, AfterViewInit and ElementRef</h1>

<div #myDiv>
</div>

Note: ElementRef allows direct access to the DOM which could risk your app to XSS attacks. There are other alternatives (templating, data binding and also the Renderer2 API) to directly access the DOM which will cover in other tutorials.

You can see the live example from this link.

Using ElementRef with Directive Example

We usually put the code required for manipulating the DOM inside a directive that can be then applied to elements in templates.

For example, let's create a directive named makeRed that makes the background color red.

First, you need to have an Angular 10 project then head back to your terminal and run the following command to generate a new directive using the following command:

$ ng generate directive makered

Next, open the src/app/makered/makered.directive.ts file and update it as follows:

import { Directive, ElementRef, OnInit } from '@angular/core';

@Directive({
  selector: '[appMakered]',
})
export class MakeredDirective {
  constructor(
    private elementRef: ElementRef
  ) { }

  ngOnInit() {
    this.elementRef.nativeElement.style.backgroundColor = 'red';
  }

}

We first import ElementRef from the @angular/core package, next we inject it via the directive's constructor. Next, in the ngOnInit method of the directive we use the nativeElement interface of ElementRef to access the native style property of the DOM element to which the directive is applied.

Next, open the src/app/app.component.html file and update it as follows:

<h1>Angular 10 ElementRef with Directive Example</h1>
<div appMakered>
  Add the <code>appMakered</code> attribute to change the background color to red.
</div>

We simply apply the appMakered directive to our <div> element to make the backgound red.

See the example live here.

Is It Safe to Manipulate the DOM With ViewChild and ElementRef?

While it's quite easy to manipulate the DOM using ViewChild and ElementRef but it's not actually safe to do that.

According to the docs for ElementRef:

Use this API as the last resort when direct access to DOM is needed. Permitting direct access to the DOM can make your application more vulnerable to XSS attacks. Carefully review any use of ElementRef in your code. Use templating and data-binding provided by Angular instead. Alternatively you take a look at Renderer which provides API that can safely be used even when direct access to native elements is not supported. Relying on direct DOM access creates tight coupling between your application and rendering layers which will make it impossible to separate the two and deploy your application into a web worker.

There is an alternative API called Renderer2 so let's see an example of that.

Accessing the DOM with Renderer2, ElementRef and Angular Directives

Let's now see how to safely access and manipulate our DOM elements using Renderer2 combined to ElementRef but without using the nativeElement interface for direct access.

We'll change our example directive to use Renderer2 instead. Open the src/app/makered/makered.directive.ts file and update it as follows:

import { Directive, ElementRef, Renderer2 } from '@angular/core';

@Directive({
  selector: '[appMakered]',
})
export class MakeredDirective {

  constructor(private elementRef: ElementRef, private renderer: Renderer2) {
      this.renderer.setStyle(this.elementRef.nativeElement, 'background-color', 'red');
  }
}

We import both ElementRef and Renderer2 and we inject them via the directive's constructor. Next, we call the setStyle method of Renderer2 on the nativeElement of ElementRef to apply the style.

See the live example here.

Conclusion

In this post, we've seen what ElementRef is and how it can be used with other APIs to access the native DOM elements with simple Angular 10 examples.

ElementRef is usually combined with ViewChild and AfterViewInit to access child DOM elements from an Angular directive.

ViewChild is a custom Angular decorator for making DOM access in the Angular way. It returns the first element that matches a given component, directive or template reference selector.

Note: For querying multiple children, you need to use the ViewChildren decorator instead.

AfterViewInit is a life-cycle method of components that gets called when the view of the component is completely rendered. You need to put the code to access the view elements in this method so you can safely access them.


  • Date: