angular: "{ providedIn: 'root' }" does not instantiate services as documented
I’m submitting a…
[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report
[ ] Performance issue
[x] Feature request
[x] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
[ ] Other... Please describe:
Current behavior
If you provide a service by setting @Injectable({providedIn: ‘root’}) the service is not created if it is never asked to be injected.
Expected behavior
If you provide a service by setting {providedIn: ‘root’} the service should be created according to the docs.
When you provide the service at the root level, Angular creates a single, shared instance of HeroService and injects into any class that asks for it.
If this issue is actually the expected behavior, (edit: it is) the docs should explicitly point out that the service is only instantiated if injected at least once. Also, I suggest adding a property for the @Injectable annotation to toggle this feature.
// Example flag
@Injectable({
providedIn: 'root',
eager: true // default: false
})
Minimal reproduction of the problem with instructions
Simply check your browser console and comment in/out the constructor in the app.module.ts file.
https://stackblitz.com/edit/angular-nsy3vj?file=src%2Fapp%2Fapp.component.ts
Environment
Angular version: 6.0.0
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 23
- Comments: 24 (12 by maintainers)
@mlc-mlapis That is a valid reason; it should still be explicitly pointed out in the documentation. As is, the “Tour of Heroes”-doc is misleading.
That’s why I suggested adding a flag to instantiate it anyway; something like:
@ericmartinezr, it’s not the case that you haven’t injected it anywhere. The main issue is that maybe you want it to be started along with the application to grab useful information that you will need in the future when you instantiate some component that injects it.
A classic example is when you want to keep track of the last route the user visited on the app (or maybe the user navigation history during the last hour). To do that, one way is to start a service that observes the navigation and store the last navigated url or something like that. And you just want to know about this navigation information, for example, in components where you have “BACK” buttons available to the user (not the browser ones, but your app provided buttons). You could build a service with some code like this:
But if you don’t inject the service in the
app.module.ts
, for example, it will not be instantiated until it’s injected in some component that needs it… too late for it to be useful to that component. At that moment, if you need your navigation history to go back to the last visited page, you won’t have it because the service would have just been started at that time.[UPDATE]: I found a blog describing in more details the navigation example: https://blog.hackages.io/our-solution-to-get-a-previous-route-with-angular-5-601c16621cf0
In that article the author suggests doing something like (in you
AppComponent
):Here we see the calling to the service
loadRouting()
method could be avoided by putting it’s logic inside the service’s constructor. But how strange it would be injectingroutingStateService: RoutingStateService
in theAppComponent
and not using it anywhere (maybe tslint would complain about the not used variable…)@ericmartinezr The documentation is still not correct:
The service instance is not created if it isn’t asked for (the first part of the sentence is completely independent from the second). And that’s what I would do if
eager: true
: create an instance of the service. Because if you simply putconstructor(private service: Service) {}
into the root module and never usethis.service
, the code in the service is still executed. But IDEs markservice
as unused.Anyway, the main reason for me opening this issue is the fact that the documentation is misleading and should be more specific.
[Edit] I adjusted the OP (removed “Bug Report” since the behavior is obviously intended) and simplified the stackblitz code example.
I’m pretty sure this has been the behavior since like forever. The difference is that today the service is not part of your final bundle (tree-shaking), but services don’t work by themselves, they need to be injected somewhere. It has always been like that.
If you add the
eager
property, what will you do with the service if it hasn’t been injected anywhere?As a long-time user of various DI-containers, I also vouch for the idea of adding the eager: true flag for the @Injectable() decorator. I don’t know if there are many other use-cases for eager instantiation but if the service only subscribes to events and is never injected, this feature would come in handy.
I just found myself cleaning up the root level app.component.ts which is the place where devs (including me) put all the global event handling logic such as subscribing to the Angular router events. So being your average object-oriented Joe, I obviously ended up creating RouterEventHandlerService and isolating the event handling logic there. This approach both endorses single-responsibility-principle and solves the issue of polluting AppComponent with all sorts of event listeners.
Now this approach alone doesn’t work of course since the service is not injected anywhere, but is supposed to just live as a singleton instance, instantiated at app start. While the workaround proposed at Stack Overflow works, I find it sub-optimal compared to the solution presented by OP.
@julianobrasil If it’s not injected, then likely no one has reference to that class, and it will be dropped away by tree-shaking without any trace at runtime. If you want additional bootstrap initialization logic, then
APP_BOOTSTRAP_LISTENER
is designed for that.There might be use cases where you just need to add independent pluggable functionality that will not be used in any component or directive or service but you want a class which can use other services by injecting them and and do some calculations.
@dariobraun It’s already documented, but there could be some docs change to make it more clear.
Just came across this issue when migrating my app to standalone components. In my previous version, using a module, I injected a directive that will be used across the app for testing. The components were not aware of this directive. Now that they are standalone, I don’t want to “litter” all my components with this directive, so I’m trying to create a service that will do a similar job to all the components (or something, I’m still not sure how to solve this TBH). So I need this service initialized when the app starts, but I don’t need it in any component or service so it’s getting tree shaked…
Bottom line, I think standalone components might be a reason to reconsider adding this parameter.
based on the comment by @kemsky above, here’s what I came up with:
In the AppModule:
Well, certainly there are many cases when you want service to be instantiated immediately (eager):
One could use
APP_INITIALIZER
for that, but in this case you have to export your services all the way to the top module, it’s just not convenient, verbose and more importantly such code it is not self-evident:oh, I found it. the capital ‘L’ was the problem! Thanks. i’m silly 😃