Angular's DI is quite powerful. There are however some peculiarities when it comes to the registration of Angular services with the dependency injector, in combination with lazy loaded Angular modules. The way you register the Angular service (e.g. using the providers
array on a NgModule or the providedIn
decorator) might have a direct impact on the instances of the service you have in memory at runtime. Let's explore that in more detail.
You can also read more details in my blog post
Instructor: [0:01] I have prepared a small sample setup here that allows us to experiment a bit with lazy-loaded modules, and how they affect and serve the service of what you have at runtime. The setup is simple. We have the data access module here, which contains the main service, which is our data service.
[0:18] Then, we have feature one module, which has a component where we are going to use the data service, and similarly feature two module. Both of the feature one and feature two modules are lazy-loaded, and so we can see that in here.
[0:31] You can obviously also prove that here when you go and click on feature one, you see that JavaScript is being pulled in, similar to with feature two. Let's first have a look at our data service.
[0:44] Now, you do what you see is this kind of registration. We are going to explore a bit the fact of this registration. This was introduced, I believe, in Angular 8 and onwards. What you often see, however, is something like this. Let's start with this simple injectable decorator and what effect it has.
[1:02] In order to demonstrate that, let's go to feature one component. Let's simply use that data service. I'm going to pull that in here. In my constructor, as you would usually do, let's also use it here, feature one component. Let's do the same with feature two component, which isn't a feature two module.
[1:31] The situation which we have is that both of these use a service from a normal module, which is data access. Refresh here, go to the console. Click on feature one, and what you get is this error. What you will see quite often sometimes which is basically no provider for data service.
[1:49] Meaning, feature one in this case requires that data service which we have is important in the future one component here, but a data series is not known to feature one module where that component is being registered. Similar with feature two, the same will happen.
[2:06] How can we fix that? If we don't use the injectable provided in syntax, then what do you usually do is you register the service providers. Register it in here. However, this basically registers the service with the data access module, but then you also need to establish a connection between the feature one module and the data access module and similarly with the feature two.
[2:31] Let's go here and pull in the data access module, and also in feature two module let's pull into the data access module, save that. Now, if you go here, it should again work. We see here that initializing data service. This is something we have in our data service printed out in the constructor.
[2:55] Observed is locked now. What happens is I navigated from home so the route of the application to feature one. Obviously, since it is the first one to require a data service in this component it will substantiate that, so this is expected. However, if I click on feature two, I see an another lock. Meaning, another data service is being instantiated. Why is that happening?
[3:17] Basically, I have a blog post on this that has further details. I will link it in the description of the video. What happens with dependency injection and modules and services is that if you don't have lazy-loaded modules, the situation will be something like this.
[3:32] Whenever the first module loads the service will be registered with the root dependency injector and will be available to everyone basically. Even if feature two wouldn't import the data access module, it will still work. Because it's registered on the route DI.
[3:46] However, the particularity is when you use lazy-loaded modules, then a situation of this follows. We have the route parent injector, but then, each of the lazy-load modules has its own dependency injector as well.
[4:00] What will happen is whenever feature one module, basically gets loaded and it sees the data service requirement, it will look it up. It doesn't find it here. It didn't find it under root dependency injector yet, so it will instantiate it on its own. The same will happen here. In the end, what you will have is you will have a date of service in memory here, as well as here.
[4:21] How can we avoid that? In some cases, you might even desire that behavior and so that is fine. However, if you want to avoid that the basic way to go with it is to provide the route. This is the most simple setup. In general, 90 percent of services use exactly this setup, so you want to have one single route at one time.
[4:43] Providing the route also has another benefit if you want is that you don't have to register the data service in a data access model anymore. Also, you don't have to have a dependency on that data access module in your feature module. We can go and remove the data access registration in here.
[5:05] What we end up is with that data service that annotation at the very top, and this will outer register it with the root dependency injector In the end, what will happen is when the first module gets loaded, the data service wouldn't be registered with the lazy-loaded module here, like the dependency injector in that laser module, but rather directly in the root dependency injector. Once the second model comes in, it finds the data service up here, so uses that instance.
[5:34] In effect to prove that, let's navigate here to home, refresh. Go to feature one, we'll see initializing data service. Feature two, we'll not see any other log out here, although we navigated to the feature to route. As you can you see, now, we ended up with one-single instance.
[5:54] Any pattern which are often see as people using that data service, and not for instance, with pride and root, and registering it on the providers array here off our modules. This should absolutely be avoided.
[6:10] Every folder usually has its own module and the service that lives in that folder should be registered to the model that is nearest to its location. It shouldn't be exported from here and registered here, as well as here.