How to avoid Angular injectable instances duplication

Post Editor

In this article we'll explore when and why Angular creates two instances of the same service and what solutions exist to ensure a service remains a singleton in the entire application.

4 min read
post

How to avoid Angular injectable instances duplication

In this article we'll explore when and why Angular creates two instances of the same service and what solutions exist to ensure a service remains a singleton in the entire application.

post
post
4 min read

This topic has been repeatedly described in other articles and documentation, but when I faced this problem, I still had to collect information bit by bit and do a lot of test examples on Stackblitz to get to know how to do it right, so I decided to write this article as a note for myself in future.

Intro
Link to this section

If you want to create only one instance of any service for a whole application, you want to create a Singleton.

From wikipedia:

In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one “single” instance.

Why you may want it?

The most common reason is to share some valuable state between all parts of your application.

Take a look at simple application configuration service:

<>Copy
@Injectable() export class SettingsService { private settings = new Map(); public get(key: string): any { return this.settings.get(key); } public set(key: string, value: any): any { return this.settings.set(key, value); } }
Configuration service called SettingsService

and it’s module:

<>Copy
@NgModule({ imports: [BrowserModule], declarations: [ApplicationComponent], bootstrap: [ApplicationComponent], providers: [SettingsService] }) export class AppModule {}
AppModule with SettingsService

Basically I want to use the same settings for a whole application:

<>Copy
@Component({ selector: 'app', template: '' }) class ApplicationComponent { constructor(private settings: SettingsService) { settings.set('FEATURE', true); } }
Application component sets configuration

And then use it in some component:

<>Copy
this.isFeatureAvailable = settings.get('FEATURE'); ... <div *ngIf="isFeatureAvailable"><super-feature></super-feature><div>
Typical usage of app configuration

But sometimes Angular can create more than one instance of SettingsService, so your settings will vary by instance and it will lead to serious configuration problems in your application.

Well, let’s see why this is happening and how to handle it.

The problem
Link to this section

Angular will create new instances for any of InjectionToken or Injectable in cases of using:

This is happening because Angular creates a new module  Injector for any lazy loaded module, this behavior is perfectly described in docs and this article.

Here is the demo with the problem demonstration.

The solutions
Link to this section

The most important thing to understand here — adding any Injectable (or InjectionToken) to the @NgModule.providers list for any Eager and Lazy module pair will duplicate such Injectable!

So, first step is not to add services that should be singletons to @NgModule.providers list of any module.

Basically you can add service to the Application module providers and it will work. But other developers may not know that you want to use this service as a singleton, and somebody will add this service to the providers list of lazy loaded module and Angular will create second instance of it.

You should choose which strategy to use, because there are two with its’ pros and cons:

  • static forRoot() @NgModule method
  • @Injectable({ providedIn: ‘root’ })

forRoot()
Link to this section

forRoot method is a kind of agreement/convention between Angular developers to call this method in the root module only (AppModule for example), so any service will be provided only once.

For this technique you should create module and implement static forRoot(): ModuleWithProviders method.

Example:

<>Copy
@NgModule({ imports: [CommonModule] }) export class SettingsModule { public static forRoot(): ModuleWithProviders { return { ngModule: SettingsModule, providers: [SettingsService] }; } }
SettingsModule with forRoot

Note: forRoot method is not handled by any code in Angular Compiler so you can name it as you want (forMySuperHotRootAppModule()?), but it is not recommended.

Good example of forRoot usage it is RouterModule.

Here is the demo with forRoot solution.

providedIn: ‘root’
Link to this section

When you mark Injectable as provided in root, Angular resolver will know that such Injectable, used in lazy module, was added to the root module, and will look for it in the root injector, not newly created lazy loaded module injector (default behavior).

<>Copy
@Injectable({ providedIn: 'root' }) export class SettingsService { private settings = new Map(); public get(key: string): any { return this.settings.get(key); } public set(key: string, value: any): any { return this.settings.set(key, value); } }
SettingsService with providedIn: 'root'

Huge plus of this solution — angular’s ability to use tree shaking with providedIn.

Also with providedIn your tests will not fail, because if all of your services provided in root (99,99% should be provided in root I bet) TestBed will resolve it correctly.

Here is the demo with providedIn solution.

The singleton guard
Link to this section

You can easily get to know if somebody created the second instance of your service.

<>Copy
@Injectable({ providedIn: 'root' }) export class GuardedSingletonService { constructor(@Optional() @SkipSelf() parent?: GuardedSingletonService) { if (parent) { throw Error( `[GuardedSingletonService]: trying to create multiple instances, but this service should be a singleton.` ); } } }
Throws error if instance already exists

Also it can be done as a base class:

<>Copy
export class RootInjectorGuard { constructor(type: Type<any>) { const parent = inject(type, InjectFlags.Optional | InjectFlags.SkipSelf); if (parent) { throw Error(`[${type}]: trying to create multiple instances, but this service should be a singleton.`); } } }
Guard for singleton services

Usage:

<>Copy
@Injectable({ providedIn: 'root' }) export class MySingletonService extends RootInjectorGuard { constructor() { super(MySingletonService); } }
Now more than one instance of MySingletonService can't be created

Bonus
Link to this section

Here are some questions I searched answers for when faced this issue.

How to handle it with InjectionToken?
InjectionToken have options as second argument .

<>Copy
class MyDep {} class MyService { constructor(readonly myDep: MyDep) {} } const MyServiceToken = new InjectionToken('MyToken', { providedIn: 'root', factory: () => new MyService(inject(MyDep)) });
InjectionToken with providedIn: 'root'

What if I use forRoot in combination with providedIn: 'root'?
There is no differences between using: forRoot or providedIn or forRoot+providedIn. Service will be created only once.

What if I use forRoot and providers list?
Service will be duplicated.

What if I use providedIn and providers list?
Service will be duplicated.

Further reading
Link to this section

Conclusion
Link to this section

Creating multiple instances of the same services can become a problem and Angular provides some abilities to handle it.

Thanks for reading!

Share

About the author

author_image

Writer at Indepth.dev, web enthusiast and frontend developer at OnTarget Labs.

author_image

About the author

Nikita Balakirev

Writer at Indepth.dev, web enthusiast and frontend developer at OnTarget Labs.

About the author

author_image

Writer at Indepth.dev, web enthusiast and frontend developer at OnTarget Labs.

Looking for a JS job?
Job logo
Fullstack (Angular, Node.js) Developer

Nextian Corp.

Worldwide
Remote
$84k - $107k
Job logo
Application Developer (Angular)

Karsun Solutions, LLC

Worldwide
Remote
$104k - $132k
Job logo
Angular Developer

Ryan Consulting Group

Worldwide
Remote
$77k - $80k
More jobs

Featured articles

Angularpost
17 January 202323 min read
Improve page performance and LCP with NgOptimizedImage

Explore mechanisms of NgOptimizedImage directive to improve overall page performance, targeting especially the Largest Contentful Paint (LCP) metric from Core Web Vitals. Enhance pages, make the best user experience and improve the web.