Service: No provider for Renderer2

Viewed 32757

Angular 4.2 with Typescript 2.3

I am refactoring a service that is responsible for creating a new script tag and adding it to the document.

Here is the old code:

loadScript(src:string){
    const script = document.createElement('script');
    document.body.appendChild(script);
    script.src = src;
}

Now, I'd like to use the Renderer2 to avoid doing direct DOM manipulation. So I've injected what I need in my service and updated the code:

constructor(private renderer:Renderer2, @Inject(DOCUMENT) private document){}

loadScript(src:string){
    const script = this.renderer.createElement('script');
    this.renderer.appendChild(this.document.body,script);
    script.src = src;
}

However, I run into this error:

Error: no provider for Renderer2!

The service belongs to a CoreModule whose only import is CommonModule from @angular/common

This plunkr demonstrates the problem

4 Answers

I tried to implement this using render2, but in a service - leading to 'StaticInjectorError(AppModule)[Renderer2]'-Error, as injecting Renderer2-Instance seems to not be possible. Solution was to inject RendererFactory2 and manullay create the reference within the service, like:

@Injectable({
  providedIn: 'root'
})
export class FgRendererService {
  /** Reference to render-instance */
  public renderer: Renderer2;
  /** CONSTRUCTOR */
  constructor(
    private _renderer: RendererFactory2
  ) {
    this.renderer = _renderer.createRenderer(null, null);
  }
  /** Add class to body-tag */
  addBodyClass( classToAdd: string ): void {
    this.renderer.addClass( document.body, classToAdd );
  }
  /** Remove class from body-tag */
  removeBodyClass( classToRemove: string ): void {
    this.renderer.removeClass( document.body, classToRemove );
  }
}

Important Note:

Whichever approach is appropriate for your needs it's important to realize that Renderer2 can have many different implementations DefaultDomRenderer2, BaseAnimationRenderer, DebugRenderer2, EmulatedEncapsulationDomRenderer2 etc. For example, each component using ViewEncapsulation gets a different renderer.

If you inject Renderer2 in a service that is provided by a sibling component then you'll get that component's renderer automatically. This is important for ViewEncapsulation because it's the renderer that actually knows how to generate those _ngcontent-appName-c339 attributes that scope your styles.

If you inject Renderer2 at the root level, an ancestor component or from your AppComponent and then try to use it to generate HTML you'll get either no host attribute or worse the wrong one.

Be sure to test your expectations carefully if using SSR or web workers too.

None of this may matter for what you're doing but it's important to be aware of the reasons why there are different instances of Renderer2.

Related