1

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

5
  • 1
    Why is RendererExtended abstract? I'm think Angular is not going to instantiate as long as it is abstract. On a side note, I don't think you really need to extend the Renderer2, to accomplish "removeAllClasses", but YMMV. Commented Mar 31, 2021 at 7:07
  • Or: do you have a method addClass in your renderer? It seems the exception is thrown after removeAllClasses has been called. Commented Mar 31, 2021 at 7:09
  • I didn't have a look on renderer but you may need to pass to your class some elements that you need in renderer class via super() function. Commented Mar 31, 2021 at 7:11
  • Why is RendererExtended abstract? That's because Renderer2 is abstract. If RendererExtended is non-abstract, I will needed to implement all methods from parent. Commented Mar 31, 2021 at 7:27
  • I don't have addClass method in my Renderer. Exception called on this.renderer.addClass(). Removing this.renderer.removeAllClasses does not change the behavior Commented Mar 31, 2021 at 7:34

1 Answer 1

2

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

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, @TotallyNewb. It works, but only with RendererFactory2 in RendererWrapperService constructor: constructor(private rendererFactory: RendererFactory2) { this.renderer2 = rendererFactory.createRenderer(null, null); } If you fix you answer, I'll mark it as a solution.
Ah, good catch - renderer is created per component so it wouldn't be possible. Update the answer - glad I could point you in the good direction :)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.