Angular’s ‘root’ and ‘any’ provider scopes

Santosh Yadav
Feb 15 · 5 min read

AngularInDepth is moving away from Medium. This article, its updates and more recent articles are hosted on the new platform inDepth.dev

In this blog post we will explore 2 values for providedIn property used with Angular provider Scope.

Introduction

If you are following Angular 9 release, you may have heard about providedIn has few more properties, module injector scope has been possible since version 2, tree-shakable module scope became available in version 6 with providedIn: MyServiceModule,and now we have ‘any’ and ‘platform’ .

In this blog post we will see the example why we needed 'any' and how it is useful. Will leave ‘platform’ for next blog-post.

Tree-shakable Providers

In Angular 6 providedIn property was added to providers, to make services tree shakable. If you are new to Angular, let me give you an simple explanation what we mean by tree shaking. It is a process to remove unused code from our application. It means if you created a service but do not use it, in your application the code will not be part of final production build. To learn more you can refer to this blog post from Lars Gyrup Brink Nielsen:

Why the any scope?

So we now know why 'root' was introduced, the idea was to make services tree-shakable. To understand providedIn: 'any' we have to talk a little bit about implementation of forRoot, forChild and lazy loading. If you have used Angular Router or NgRx then you know about these methods.

The problem of working with lazy loaded module is that if we use providedIn: 'root' even though we think we should get a new instance of a service, it gives the same instance and that might not be the behavior we expect. When working with a lazy-loaded module, a new instance should be created when we load the module.

Let’s write some code to see what was the issue earlier and how 'any' resolves that for us.

What we are going to achieve

  • A config service which will take some config parameter apiEndpoint and timeout.
  • 2 lazy-loaded modules: employee and department. They want to use the config service, but with different values.

The problem with using the root scope

  • Create a new Angular 9 app using the below command
npx -p @angular/cli@next ng new providerdemo
  • Now let’s create 2 new lazy-loaded modules with components. Run the below commands
npx -p @angular/cli@next ng g module employee --routing --route employeenpx -p @angular/cli@next ng g module department --routing --route department --module app
  • Create a new value provider and an interface. You can add it in a new shared folder, as this code will be shared between multiple modules.
demo.config.ts
demo.token.ts
  • Next, create a new service. We will call it ConfigService which will read the value from token and use it to perform some operation. Use the below command to create it.
npx -p @angular/cli@next ng g service shared/config
  • Once created, add the below code to your service
  • Now let’s use this service in the Employee and Department Components we created with their respective modules. We are just printing the values received from Token.
employee.component.ts
department.component.ts
  • Next, let’s try to pass 2 different configuration for Employee and Department, add the below code into both the modules.
employee.module.ts
department.module.ts
  • As seen, the difference is in the config values. Let’s run the application and see what we get. Remember the providedIn value is still 'root'. To test the application in it’s current state, let’s add links to the routes. Add the below snippet in app.component.html
<a routerLink="employee">Employee</a>
<br>
<a routerLink="department">Department</a>
<router-outlet></router-outlet>
  • Next, run the application using the below command
npx -p @angular/cli@next ng serve -o
  • The application works, but when we click on a link, boom we have an error. Our expectation was to see the different config values, but we have the below error.
provider-error

So what went wrong here?

We did everything right. Our expectation was to get different config values for both the employee and department components, but we ended up getting an error. This is due to the providedIn: 'root' value. Refer to Figure 1 to visualize what happened.

Figure 1. Root provider scope.

When we provide the service with providedIn: 'root', it is registered with our AppModule. As soon as we tried to activate one of the routes, the service expected the config value which was not provided. So let’s make it work by making changes to our AppModule.

Add the below code to your app.module.ts

Now the application works, but when we click on the links, we see the below 2 values for both the employee and department components. This is the expected behavior as per Figure 1.

apiEndPoint: 'def.com'
timeout: 5000

What we wanted to achieve

Figure 2. Injector provider scope.

In reality, we wanted something like Figure 2 , where each module has their own instance. With providedIn: 'root', this was not possible.

  • To resolve this issues, the previous solution was implementing forRoot and forChild static methods, so each component can have their own instances.
  • The other way to achieve this was to provide the ConfigService in each and every module. The problem is then, that the service is no longer tree-shakable.
@NgModule({
providers: [
ConfigService,
{ provide: CONFIG_TOKEN, useValue: CONFIG_VALUE }
]
}
export class EmployeeModule { }
  • Now let’s change the providedIn option for ConfigService to below
@Injectable({
providedIn: 'any'
})
  • Run the application again and notice the console now.
Final App
  • Bingo, we got the separate instances without implementing forRoot or forChild static methods or compromise regarding tree-shakable providers.

So what happened after changing to providedIn: 'any'? As shown in Figure 3, now all eagerly loaded modules will share one common instance and all lazy-loaded modules will have their own instance of ConfigService.

Figure 3. Any provider scope.

Conclusion

Earlier for all developers it was a challenge whenever they wanted to make sure they get a new instance of a service for lazy-loaded modules. Now with the 'any' option to provide services in injector scopes, it’s simplest thing to do. We can provide any tokens and developers can provided the values per lazy-loaded module. The service will always create a new instance per lazy-loaded module.

You can download the code from GitHub.

Angular In Depth

The place where advanced Angular concepts are explained

Thanks to Lars Gyrup Brink Nielsen

Santosh Yadav

Written by

Santosh is GDE for Angular and Web Technologies, he is open source contributor for Angular, NgRx and Writer at AngularInDepth and DotNetTricks.

Angular In Depth

The place where advanced Angular concepts are explained

More From Medium

More from Angular In Depth

More from Angular In Depth

Angular Bad Practices: Revisited

More from Angular In Depth

607

More from Angular In Depth

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade