Avoid Multiple Service Instances in Lazy Loaded Modules in Angular

Share this video with your friends

Social Share Links

Send Tweet
Published 6 years ago
Updated 2 months ago

Shared Angular modules may export components as well as services which are meant to be reused across the entire application. Whenever you want to use an exported component, in another module, you need to import the module defining that component accordingly. Services on the other hand are globally registered, there is one instance per dependency injector. However, there is something you should be aware of: Lazy loaded modules have their own dependency injector. As a result, importing shared modules in a lazy loaded module, may result in multiple instances of a service being available in the app. In this lesson we will learn about the ModuleWithProviders interface that helps avoid such situations.

Instructor: [00:00] In this sample application, we have here a people Angular module, which is a very simple Angular module, consisting here of a people list component, which displays here a list of people, and a people service, which exposes simply a static area of people, and gives also the possibility to add new people.

[00:17] Now, that people module gets imported here in the app module, which allows us then in that home component to show a list of people, which we can just see here. Then we have that employees link here. That employees link here points to that employees module, which is a routed Angular module.

[00:35] Which in turn, has also an employees component, which uses the app people list component from that people module. Now, obviously, in order to be able to use that component here, we have to import that people module in our employees module as well. Otherwise, it wouldn't recognize that HTML tag here.

[00:53] Now, the employees module here is a routed module. That means we have here a routing configuration, which defines a route path and the according employees component, and exposes here that employees routing module.

[01:05] In turn, that same module is registered here at the app module. We import it inside here as well. Therefore, the router basically recognize that route, and we can switch back and forth between those two views.

[01:18] Now, the home component, as well as the employees component, share the same people list component from the people module. Since both use the same Angular service, and that Angular service is registered to the people module here, it is globally available.

[01:32] We also have only one instance in our application. That means, for instance, if we add here a new value, we switch then the employees component, and we see that we have the same values inside here as well.

[01:45] That's because both basically fetch the data from the same instance of the service, which in memory, has the same array. Now, let's make a change to our application, making that employees routed module here a lazy routed module.

[01:57] First of all, we remove here that employees path. Then we go to our app rooting module, and we have to define here a new route. We say path will be now employees, and we load children, pointing to our employees module.

[02:14] Once you have done that, we have also to go to the app module, and remove every reference here to that employees module, such that it can be lazily loaded. In fact, if we take a look at our compiler, we should now see here a separate employees module chunk. That tells us that it's now lazy loaded.

[02:30] Now, let's take a look what happens if we are in our home component here. We add a record here, Thomas. Then we switch to employees. You can see, we don't find the record here. In fact, if we add Peter here, for instance, and we switch back, we can see that we have now two different instances of our service, apparently.

[02:47] The reason for that behavior is that lazy loaded modules, like our employees module, have their own dependency injector. As a result, if such a lazy loaded module, as in our case, registers here to people module, which in turn, exposes here that people service, that people service will be registered to that lazy loaded dependency injector here.

[03:07] Similarly also, our app module takes here a reference to that people module, and again, always that service gets exposed. That service will now be registered to the global dependency injector of our application.

[03:21] Notice we have two different kind of instances of our people service, one living in that employees dependency injector, and one living in the global app dependency injector. Now, there is a pattern for avoiding such behavior.

[03:32] For instance, let's take our people module. If this a module that is intended to be shared across our applications, as well as across lazy loaded modules, we can open here that module definition, and we can implement here what is called a so-called full root pattern.

[03:47] We create here a static method for root, which exposes a so-called module with providers object. There, we expose the module, which is the people module, and then our providers. Now, providers is nothing else as our providers section above here. You can simply copy and paste it down here.

[04:04] As you can see here, we export our components, which we still have to export, and with our modules, have to import our people module in order to be able to use those components. Then we expose our providers, which will be only available one instance for application in that provider section down here.

[04:20] Now, if we go back to our app module, we should actually invoke here the .fullroot. That will now fetch that module with providers object, and register that service only once globally. In our employees module, however, we can still leave the people module registration as it is.

[04:38] This one won't register another service again, because there is no service registration on the top here. Since we are not calling for root, there is no service being registered. However, we still get the declarations for that people list component, such that in our employees component, we can still use that app people list.

[04:57] Now, if we refresh our application again, If we add here Thomas, and we'll switch down to employees, we can see that we again have one single service registered per our application.

Stephan Maier-Knodt
Stephan Maier-Knodt
~ 6 years ago

Thanks from the north of the Alps! Very short, very clear all-encompassing. Nothing to add.

Juri Strumpflohner
Juri Strumpflohnerinstructor
~ 6 years ago

haha 😀. Thank you, glad you liked it 👍

Adallo
Adallo
~ 6 years ago

Nice one. Thanks

Viktor Soroka
Viktor Soroka
~ 6 years ago

As I understand it would also work fine if the service was imported in app module and forRoot thing is just to keep the service in the related module. Is not it?

Juri Strumpflohner
Juri Strumpflohnerinstructor
~ 6 years ago

@Viktor: Exactly. But usually you want to keep services in the modules where they belong. The forRoot() is a convention that is being used for providers & which you usually do just once in your root module (i.e. the AppModule). This way providers are registered to the top-most dependency injection container & therefore available to all other modules. However, your imported module might not just export providers, but also components, directives and pipes. For those you need to import your "shared module" on all other modules where you plan to use those components. But there you don't call .forRoot() again and thus you won't get the providers again

Viktor Soroka
Viktor Soroka
~ 6 years ago

@Juri, thank you for you quick replies) Have been waiting for you next courses.

Juri Strumpflohner
Juri Strumpflohnerinstructor
~ 6 years ago

@Viktor :) thanks man. I have some I'm currently working on and a couple of cool ones that I'll start very soon. So stay tuned for more stuff to come along 🙂

Levi9
Levi9
~ 6 years ago

Nice and clear. Love it.

ganqqwerty
ganqqwerty
~ 4 years ago

is it the same as providedIn:root?

Markdown supported.
Become a member to join the discussionEnroll Today