Service: No provider for Renderer2
AngularTypescriptAngular 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 typeany
type
with typeRendererType2|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
.