[Fixed] How to extend Renderer2 in Angular 11?

Issue

I need to implement removeAllClasses() and addClasses(string[]) methods by extending Renderer2.

import { Injectable, Renderer2 } from '@angular/core';
@Injectable({
  providedIn: 'root'
})
export abstract class RendererExtended extends Renderer2 (
  protected constructor() {
    super();
  }

  public removeAllClasses(el: any) {
    this.removeAttribute(el, 'class');
  }
}

And when I try to use it in component:

constructor(
 private renderer: RendererExtended) {
}

private showOrHideArrow() {
  this.renderer.removeAllClasses(this.iconComponentRef.location.nativeElement);
  this.renderer.addClass(this.iconComponentRef.location.nativeElement, 'hovered');
}
  

I see an error:
ERROR TypeError: _this.renderer.addClass is not a function

Solution

It’s a little bit more complicated than that. Renderer2 is abstract since Angular doesn’t have to use DOM renderer. It could use some other custom renderer and render templates somewhere else.

When you inject Renderer2 into your component, Angular uses the injection token Renderer2Interceptor and looks for a proper provider. By default it would be DefaultDomRenderer2 (look here) that is provided via the factory, but it could be something else.

If you want to add your custom renderer, you can read about it in official docs. You could create one that extends the DefaultDomRenderer2 and adds your custom logic on top of that, but that’s a bit of a hassle, especially if you’re not sure what you’re doing.

Instead, the easy way around could be creating a service that injects the RendererFactory2 to create the renderer, i.e.

import { Injectable, Renderer2, RendererFactory2} from '@angular/core';
@Injectable({
  providedIn: 'root'
})
export class RendererWrapperService (
  renderer2: Renderer2;

  constructor(readonly rendererFactory: RendererFactory2) {
    this.rendererFactory.createRenderer(null, null);
  }

  public removeAllClasses(el: any) {
    this.renderer2.removeAttribute(el, 'class');
  }
}

The renderer2 it injected as public property, so if you inject your service into your component you should still be able to use it, e.g.

constructor(private readonly rendererEx: RendererWrapperService ) {
}

private showOrHideArrow() {
  this.rendererEx.removeAllClasses(this.iconComponentRef.location.nativeElement);
  this.rendererEx.renderer2.addClass(this.iconComponentRef.location.nativeElement, 'hovered');
}
  

That being said, you’re swimming against the current trying to manipulate DOM manually. You’re better off using stuff like ngClass or similar. I assume you’re hacking around it since you’re using some external library / package that doesn’t expose way to manipulate it’s styling. But if you’re experiencing such issues early on in development, you’re probably better off ditching that library and writing the feature yourself instead. But that’s just my two cents.

EDIT: Updated the answer as per comment – Renderer2 gets injected per component and will not be provided at the root level so it wouldn’t be available at the service level. RendererFactory2 can be injected instead. Updated the answer

Leave a Reply

(*) Required, Your email will not be published