Search code examples
angulartypescript

Service: No provider for Renderer2


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


Solution

  • Possibly duplicating with Using Renderer in Angular 4

    You cannot inject Renderer2, but we can run RendererFactory2 to get Renderer2 instance inside @Injectable() service. There are two different ways of solving this issue.

    Get an instance of Renderer2 inside Service

    There is the way which Angular using internally in webworkers, for example. I've solved the problem with the code below:

    import { Injectable, Renderer2, RendererFactory2 } from '@angular/core';
    
    @Injectable()
    class Service {
        private renderer: Renderer2;
    
        constructor(rendererFactory: RendererFactory2) {
            this.renderer = rendererFactory.createRenderer(null, null);
        }
    }
    

    In your particular case, it will be

    @Injectable()
    class Service {
      private renderer: Renderer2;
    
      constructor(rendererFactory: RendererFactory2, @Inject(DOCUMENT) private document){
        this.renderer = rendererFactory.createRenderer(null, null);
      }
    
      loadScript(src:string){
        const script = this.renderer.createElement('script');
        this.renderer.appendChild(this.document.body,script);
        script.src = src;
      }
    }
    

    Parameters of RendererFactory2.createRenderer method are:

    • hostElement with type any
    • type with type RendererType2|null

    You can see that (null, null) parameters are here: https://github.com/angular/angular/blob/e3140ae888ac4037a5f119efaec7b1eaf8726286/packages/core/src/render/api.ts#L129

    Pass Renderer2 from component

    // declare public property in your service
    @Injectable()
    class Service {
      renderer: Renderer;
    }
    
    // pass renderer to service in your component file
    class App {
      name:string;
      constructor(service: Service, renderer: Renderer2) {
          service.renderer = renderer;
      }
    }