Skip to content

Angular Renderer

Angular Renderer Cover

In past articles, we used the nativeElement to directly query and modify the DOM. Querying native elements by bypassing any abstraction may come with a couple of problems though. For example, by using native element methods, we can’t execute modifications in non-DOM environments such as native mobile applications, desktop applications, and web worker rendering. Additionally, according to this official Angular article, permitting direct access to DOM can make our applications more vulnerable to XSS attacks. These issues are not minor at all! In order to circumvent this Angular provides us with a really neat abstraction called the Renderer.

Renderer

Renderer2 is an abstraction provided by Angular to manipulate elements by not touching them directly (via DOM). Let us first clear a probable source of confusion. Is it Renderer or Renderer2? Well, the OG Renderer class has been marked deprecated since Angular 4, and completely removed in Angular 9 so if you’re using an older version of Renderer it is a must to migrate, as it adds a new set of functionality and increased security. If you see us talking about Renderer in this article, you can be sure that we are talking about the latest version.

So, as mentioned above we are abstracting DOM manipulations by using a Renderer. Everything we’ve done by querying native elements can be done with Renderer too, including creating elements, appending children, setting and removing attributes and styles, and listening to events. There is also the added advantage of having Components and views in sync with change detections and data bindings, thanks to Angular, which would be bypassed by using native elements.

Renderer – an example

Let us create a directive inside our playground. A directive is a custom HTML attribute that changes the styling or behavior of elements. There are a couple of custom ones built-in into Angular such as ngClass, ngStyle, ngIf, ngFor.

How is this new directive looking? Well, it is quite a simple one:

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

@Directive({
  selector: '[appColorful]',
})
export class ColorfulDirective {
  constructor(private renderer: Renderer2, private element: ElementRef) {}

  ngOnInit() {
    this.renderer.addClass(this.element.nativeElement, 'colorful');
  }
}
<p appColorful>129301040941904109</p>

The one noticeable thing we’ve done here is the Renderer implementation which is used to add a new class to the element using addClass (notice that we also used ElementRef to get the underlining native element). We could also set new attributes. For example, let us add a title attribute, so on every hover, we would show a little tooltip. Well, we can just use the setAttribute method to add a new attribute!

  ngOnInit() {
    this.renderer.addClass(this.element.nativeElement, 'colorful');
    this.renderer.setAttribute(
      this.element.nativeElement,
      'title',
      this.element.nativeElement.outerText
    );
  }

Renderer2 also implements the opposites of these methods – we could also remove a class or an attribute by using removeClass and removeAttribute methods.

    this.renderer.removeClass(this.element.nativeElement, 'colorful');
    this.renderer.removeAttribute(
      this.element.nativeElement,
      'title'
    );

What else do we have in the toolbox? There is the createElement method which creates an instance of an element, based on the provided argument. In our case below, we would want to create a div element. Then we also created a text element using createText. Then we appended the text to the div element using appendChild, and then using the same method we appended the updated div element to the native element.

    const div = this.renderer.createElement('div');
    const text = this.renderer.createText('Keep your ID number hidden!');

    this.renderer.appendChild(div, text);
    this.renderer.appendChild(this.element.nativeElement, div);

We get the following result:

Angular Renderer – creating and appending new elements

That is not all! We could still use setStyle/removeStyle to add and remove inline styles:

this.renderer.setStyle(div, 'border', '2px dashed green');

And a number of other methods. The full reference list for Renderer2 which includes other methods such as nextSibling, insertBefore, parentNode, selectRootElement and others can be seen by clicking here.

Renderer2 listener

Renderer also comes with it’s own implementation of DOM event listener. Usually, when listening for an event using native DOM APIs, we would call the addEventListener and provide the event we want to listen on and the accompanying behavior:

someElement.addEventListener("click", onMouseClick);

To remove it, we would need to call removeEventListener.

someElement.removeEventListener("click", onMouseClick);

We would need to do that often, as not removing event listeners can cause memory leaks and performance issues. This could become tedious if we handle lots of events.

Renderer comes to the rescue! The listen method returns another method that can be destroyed when we need to remove the listener. Another positive of the listen method is that we can manage complex listeners by adding them or removing them on specific conditions.

Let us create a really simple listener, just to see how it is implemented:

    this.renderer.listen(this.element.nativeElement, 'click', () => {
      const div = this.renderer.createElement('div');
      const text = this.renderer.createText('Clicked!');
      this.renderer.appendChild(div, text);
      this.renderer.appendChild(this.element.nativeElement, div);
    });

With this snippet added, every time we click on the id element, we would append a new text – “Clicked!”.

Conclusion

To conclude the article, let us repeat the cases where we would use Renderer. For example, when we want to bypass Angular’s templating and make custom UI changes. Also, when we want to apply to changes to non-DOM environments too (server-side rendering, web workers)! The layer of abstraction really helps when it comes to security, too!

The StackBlitz for this project can be found below: