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…

Providing service on the component level leads to multiple service instances ( one per component )

The 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 @Injectable decorator.

We can think about providedIn as 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 providedIn the '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.

Examples of new providedIn dependency injection syntax

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 providedIn: 'root' We are using providedIn: EagerlyImportedModule We are using providedIn: LazyLoadedModule

🥕 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.

Before 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.

Also, the 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?

Technically, the '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…

🤩 The providedIn: EagerlyImportedModule

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 @NgModule which 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 loadChildren property 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 😂🛌🌒

😴 The providedIn: LazyModule solutions

The LazyService which is providedIn: LazyModule will fail with no provider error if injected in a eager component (AppComponent). This is a match made in clean architecture heaven! (It would only work if it was provided in ‘root’…)

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!

Using providedIn: LazyModule will lead to circular dependencies warning but luckily there is a easy solution!

The most straight forward way to reproduce this would be to:

create LazyModule create LazyService and use providedIn: LazyModule create LazyComponent which is declared in LazyModule inject LazyService into LazyComponent constructor 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!

Emoji art FTW hahah!

While mildly inconvenient, one extra module is a very little effort on our part and such an approach combines the best of both worlds: