🤫 Psst! Do you think that NgRx or Redux are overkill for your needs? Looking for something simpler? Check out @angular-extensions/model library!
I know, I know… Angular 7 is out already but this topic is as relevant as ever! Angular 6 brought us new better
providedIn syntax for registration of services into Angular dependency injection mechanism.
As it turned out, this topic can evoke quite emotional responses and there is a lot of confusion across GitHub comments, Slack and Stack Overflow so let’s make this clear once and for all!
📖 What we’re going to learn
- Dependency Injection (DI) recapitulation (optional😉)
- The Old Way™ of doing DI in Angular —
- The New Way™ of doing DI in Angular —
providedIn: 'root' | SomeModule
- Possible scenarios when using
- Recommendation on how to use new syntax in your projects
💉 Dependency Injection
Let’s do a quick recapitulation of what is a dependency injection. Feel free to skip this part and get straight to the main course 🍽️
Dependency Injection (DI) is a way to create objects that depend on the other objects. A Dependency Injection system supplies the dependent objects (called the dependencies) when it creates an instance of an object — Angular Docs
Formal definitions are nice but let’s talk about it in more relaxed fashion. Our components and services are classes. Every class has a special function called
constructor which is called when we want to create an object (instance) of that class to be used in our application.
I suppose we have all seen code like
constructor(private http: HttpClient) in one of our services. If we wanted to create our service without Angular DI mechanism we would have to provide
Our code would look something like this
const myService = new MyService(httpClient). But from where do we get the
Similarly, we would have to do
const httpClient = new HttpClient(httpHandler) to get it. Now, what about the
httpHandler? When does this stop? As we can see, wiring stuff together manually would be a tedious and error prone process…
Angular DI mechanism does all what was described above automatically. All we have to do is to specify dependencies in the constructor of our components and they will be provided to them without any effort on our part. But nothing is for free…
👴 The Old Way™ of doing DI in Angular
To make this fine piece of engineering work Angular has to be aware of every single entity that we want to inject into our components and services.
Before the release of Angular 6, the only way to do that, was to specify services in the
providers:  property of the
@NgModule decorator (or
@Directive but more on that later...)
providers:  property can lead to three different scenarios based on specific circumstances…
- We’re specifying
providers: in the
@NgModuledecorator of an eager-ly imported module
- We’re specifying
providers: in the
@NgModuledecorator of a lazy loaded module
- We’re specifying
providers: in the
@Directivedecorator (aka declarables)
In this case, service will be registered as a global singleton. Service will be provided as a singleton even if it is included in the
providers: of multiple eager modules. Only one instance will be created by the injector and this is because they will all end up registered with the root level injector.
Instance of the service provided in the lazy module will be created on the child injector (of the lazy module) when initialized later during the application run-time. Injecting such a service into the eager part would lead to
No provider for MyService! error.
Declarables — @Component or @Directive
Service is instantiated per component and is accessible in the component and all its child components in the sub-tree.
Thanks Lars Gyrup Brink Nielsen for pointing out that the component providers are made available to the component and all its view AND content child components (i.e. components in its template but also projected components rendered by
<ng-content></ng-content>).View providers are only made available for the component and its view child components. They are declared by the
viewProvidersoption in the component decorator.
In this case, the service is not a singleton and we get a new instance of the provided service every time we use component in the template of another component. It also means that the service instance will be destroyed together with the component…
RandomService is registered in the
providers:  of the
RandomComponent so we will get different random number every time we use
<random></random> component in our template.
This would not be the case if the
RandomService was provided on the module level and would be available as a singleton. In that case every usage of
<random></random> component would display same random number because the number is generated during the service instantiation.
Follow me on Twitter to get notified about the newest Angular blog posts and interesting frontend stuff!🐤
👶 The New Way™ of doing DI in Angular
With the advent of Angular 6 we got this new shiny tool for modeling the dependencies in our applications. The official name is “Tree-shakable providers” and we use it by employing new
providedIn property of the
We can think about
providedInas a specifying dependencies in reverse fashion. Instead of module providing all its services, it is now the service itself declaring where it should be provided…
Modules can be
'root' or in any of the available modules (eg
providedIn: SomeModule). Adding to that,
'root' is in fact an alias for the
AppModule (and hence root injector) which is a nice convenience feature which saves us importing of the
AppModule all around our code-base.
OK, the new syntax is pretty straight forward. Let’s put it into practice and explore some of the interesting scenarios we may find ourselves in during the development of our applications…
- We are using
- We are using
- We are using
🥕 The providedIn: ‘root’ solution
This is the most common solution which will work for us under the most circumstances.
The main benefit of this solution is that the services will be bundled only if they are really used. “Used” stands for being injected into some component or other service (which has to be used too😉).
This new approach usually doesn’t do much of a difference when developing single SPA application where we usually use every service that we write and unused stuff simply gets deleted…
On the other hand
providedIn: 'root' has a huge positive impact on developers of reusable libraries for both technical and business functionality.
providedIn, libraries had to provide all their publicly available services in the
providers:  field of the main module. The module then had to be imported by the consumer application which would result in bundling of all (possible large number) provided service even if we only wanted to use just one of them.
providedIn: 'root'solution removes the need to import the library module at all, we can simply inject needed services and it just works!
Lazy loading & The providedIn: ‘root’ solution
What would happen if we used
providedIn: 'root' to implement service which we want to use in lazy loaded module?
'root' stands for
AppModule but Angular is smart enough to bundle service in the lazy loaded bundle if it is only injected in the lazy components / services. But there is a catch ( even though some people would say it’s a feature 😋 ).
If we later additionally inject service which is meant to belong to lazy module in any eager part of the application it will be then automatically bundled in the
main bundle. To summarize…
- If the service is only injected in the lazy part, it will be bundled in lazy bundle
- If the service is injected in the eager part (while still possibly being injected in the lazy part), it will be bundled in the eager main bundle
The problem with this behavior is that it can get quite unpredictable in the larger applications with tons of modules and hundreds of services.
Being able to inject any service anywhere will lead to creation of many hidden dependencies that can be hard to understand and impossible to untangle! 🗡️
Luckily there is a way to prevent this and we will explore approaches how to enforce module boundaries in the following sections, but first…
This solution generally doesn’t make sense and we should stick with
providedIn: 'root' instead.
It can be used to prevent rest of the application from injecting the service without importing of the corresponding module but this is not really necessary in the eager module scenarios.
In case we would really have a need for such a guarantee it can be achieved much easier using old
providers: property of a
@NgModulewhich does exactly the same and doesn’t lead to circular dependency warnings and necessary workarounds…
Stick with providedIn: ‘root’ in every eagerly imported module scenario
📑 Side note — The multiple benefits of the lazy loaded modules
One of the best things about Angular is how easy it is to split our applications into completely isolated chunks of logic which has following benefits…
- Smaller initial bundle which translates into faster load and start times (obvious one)
- Lazy loaded module are truly isolated. The only point where they should be referenced by the host application is the
loadChildrenproperty of some route.
This means, if used correctly, that it is possible to delete or externalize whole module into standalone app / library. The module with possibly hundreds of components and services can be shuffled around without affecting the rest of the application which is amazing!
Another huge benefit of this isolation is that making changes to the logic of the lazy module should never be able to cause errors in the rest of the application. Sounds like a worry-less sleep even on the release day 😂🛌🌒
providedIn: LazyModule solutions
This solution is great because it helps us to prevent usage of our services outside of the desired module. Keeping dependency graph in check can be useful when developing huge applications when unconstrained possibility to inject everything everywhere can lead to a huge mess which may be impossible to untangle!
Fun fact: if we inject lazy service in the eager part of app, the build (even AOT) will work and produce bundles without any error. Of course, the application will crash immediately during the bootstrap with “No provider for LazyService” error. It would be nice if this could also fail during the build time, right? Angular people, anyone?
Unfortunately there is a small catch… Circular Dependencies!
The most straight forward way to reproduce this would be to:
LazyComponentwhich is declared in
- PROFIT?! 💸 NO! 🙅 Circular dependencies warning instead… ⚠️
…or more schematically the dependencies will end up looking like this …
service -> module -> component -> service
It’s a bit unfortunate but that’s just how typed languages work (at least the ones I know 😅)
Luckily we can make this work by creating a
LazyServiceModule which will be a sub-module of the
LazyModule and it will be used as an “anchor” for all the lazy services we want to provide. Check out the following diagram to get an idea!
While mildly inconvenient, one extra module is a very little effort on our part and such an approach combines the best of both worlds:
- it prevents us from injecting lazy services into eager part of the app
- it only bundles service if it is really injected in some other lazy component
What about providedIn: SomeComponent
Does the new syntax work for the Angular declarables, the
The short answer is no, it doesn’t!
We still have to use
providers:  in
@Directive to create multiple service instances (per component). There is currently no way around this…
providedIn: 'root', solution is amazing when developing libraries, utils or any other form of reusable Angular logic.
It really shines in scenarios when consumer application needs only a subset of available library functionality. Only the stuff which is really used will be bundled in our application and who doesn’t like small bundle size anyway!?
One practical example of this approach is a recent rewrite of the ngx-model into @angular-extensions/model which uses new syntax. Users don’t have to import
NgxModelModuleanymore and can use library simply by injecting
ModelFactoryin any of their components… Check out the implementation for more details!
providedIn: LazyServicesModule which is then imported by the
LazyModule which is then lazy loaded by the Angular Router to enforce strict module boundaries and maintainable architecture!
This approach prevents accidental injection of the lazy services in the eager part of the application.
In my personal opinion, the magical bundling of a service in lazy scenarios (as with
providedIn: 'root') based on its usage can cause a lot of confusion and is not good!
We could argue that the
'root' will just work and the service will be bundled correctly but using
providedIn: LazyServiceModule provides us with early “missing provider” error which is a great early signal and should make us rethink our architecture.
Maybe there is a more appropriate location for that particular service! Service that is needed in eager part should be also moved there in a project code-base so that everything is simple and predictable!
When to use old providers: [ ] syntax?
Do we need to pass configuration to our service?
Or in other words, do we have a use case which we have solved using
In this case we still need to use
providers:  as the new syntax doesn’t help us with customization of the services.
On the other hand, if we ever used
SomeModule.forRoot() to prevent creation of additional instances of the service by the lazy loaded modules we can simply use
providedIn: 'root' instead…
.forRoot())in the lazy module helped us because services were usually provided only when called with
.forRoot(). That way we could have been sure that our services were provided only once!
providedIn: 'root'for services which should be available in whole application as singletons
- Never use
providedIn: EagerlyImportedModule, you don’t need it and if there is some super exceptional use case then go with the
providedIn: LazyServiceModuleto prevent service injection in the eagerly imported part of the application or even better use
providers: [LazyService]in the
LazyModule. Which is simpler to implement…
- If we want to use
LazyServiceModulethen we have to import it in
LazyModuleto prevent circular dependency warning.
LazyModulewill then be lazy loaded using Angular Router for some route in a standard fashion.
providers: inside of
@Directiveto scope service only for the particular component sub-tree which will also lead to creation of multiple service instances (one service instance per one component usage)
- Always try to scope your services conservatively to prevent dependency creep and resulting tangled hell 👿🔥!
👋 That’s it for today!
I hope you enhanced your understanding of the interesting Angular dependency injection topic! Please support this article with your 👏👏👏 to help it spread to a wider audience 🙏. Don’t hesitate to ping me if you have any questions using the article responses or on Twitter @tomastrajan 💬.
And never forget, future is bright
Starting an Angular project? Check out Angular NgRx Material Starter!
If you made it this far, feel free to check out some of my other articles about Angular and frontend software development in general…