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