Service: No provider for Renderer2

AngularTypescript

Angular Problem Overview


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

Angular Solutions


Solution 1 - Angular

Possibly duplicating with https://stackoverflow.com/questions/43070308/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;
  }
}

Solution 2 - Angular

You can initialize service with an instance of Renderer2 in root component

@Injectable()
class MyService {
  renderer : Renderer2;
}
...
class App {
  name:string;
  constructor(service: MyService, renderer: Renderer2) {
      service.renderer = renderer;
  }
}

See also

Solution 3 - Angular

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 );
  }
}

Solution 4 - Angular

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBeetleJuiceView Question on Stackoverflow
Solution 1 - AngularEugene GavrilovView Answer on Stackoverflow
Solution 2 - AngularyurzuiView Answer on Stackoverflow
Solution 3 - AngularFlorianView Answer on Stackoverflow
Solution 4 - AngularSimon_WeaverView Answer on Stackoverflow