Angular 6+ :ProvidedIn a non root module is causing a circular dependency
I ran into the same problem. Turns out the solution is "don't do it", as explained in this thread by one of the Angular guys: https://github.com/angular/angular-cli/issues/10170#issuecomment-380673276
It boils down to services being easier to tree shake when they are provided by the root module, as I gather.
I'm as disappointed as you are.
Update - Oct 2019
I've received 5 up-votes now for this answer, so I feel as though I ought to come clean and say that I'm no longer actually following my own advice on this (below)!
Since official (and widely followed) Angular policy is to use providedIn: 'root'
, I decided that on the whole it would be less confusing for other developers if I just stuck with this. So far it hasn't caused me any problems, but the caveats below still remain and I believe it's important to remain aware of this.
Original Post
I think Angular have made a bit of a mess of the providedIn
syntax. It seems to have confused a lot of people. E.g. see these two github threads:
- https://github.com/angular/angular-cli/issues/10170
- https://github.com/angular/angular/issues/25784
The providedIn
syntax seems to have 2 main benefits:
- It supports tree-shaking of unused services
providedIn: 'root'
ensures that you only ever get one instance of the service
But you only really need (1) if you're writing a library rather than an application (because why would you include a service that you didn't need in your application), and you can avoid multiple service instances (2) just by making sure you don't import the service module more than once.
The problems with the providedIn
syntax are:
providedIn: 'root'
breaks the link between the service and the module it "lives in" (or "with") - because the service doesn't know about the module and the module doesn't know about the service. This means the service no longer really "belongs" to that module, and will just get bundled with whatever references it. This in turn means that it is now up to the service consumer to make sure the service's injectable dependencies (if it has any) are available before it is used, which is confusing and quite counter-intuitive (unless of course the dependencies - and their dependencies etc. - are also allprovidedIn: 'root'
, in which case they will take care of themselves).- The circular reference problem described above. It is actually not possible - via this syntax - for the link between the service and its module to be preserved, if the service is actually used by any components within the same module.
This is contrary to official Angular guidance, but my advice would be: Don't use providedIn
, unless you are writing a third party library which requires tree-shaking - use the old (not deprecated) providers
syntax on the module instead, i.e.:
@NgModule({ providers: [MyService], })
In Angular9+
you can use providerIn: Any
Basically it works just like module, but you don't use module directly so no more circular dependency
Doc: https://angular.io/api/core/Injectable#options
'any' : Provides a unique instance in each lazy loaded module while all eagerly loaded modules share one instance.
In other words, it is in a different injection tree. It is not the same instance you used in other module.
More refs
https://dev.to/christiankohler/improved-dependeny-injection-with-the-new-providedin-scopes-any-and-platform-30bb
'Any' is very helpful to make sure a service is a singleton within module boundaries. It's a robust alternative to 'root' to make sure the individual modules don't have a side effect on each other.
https://indepth.dev/posts/1151/a-deep-dive-into-injectable-and-providedin-in-ivy
Code for providerIn
private injectableDefInScope(def: ɵɵInjectableDef<any>): boolean {
if (!def.providedIn) {
return false;
} else if (typeof def.providedIn === 'string') {
return def.providedIn === 'any' || (def.providedIn === this.scope);
} else {
return this.injectorDefTypes.has(def.providedIn);
}
}
This isn't an Angular dependencies problem.
The circular reference is generated by the TypeScript compiler when it tries to resolve the circular imports.
First Solution
Create a new module named ProtectedResolversModule
and use providedIn: ProtectedResolversModule
and move the resolvers there.
Now you can import that module into ProtectedModule
and you won't get a circular dependency error when loading ProtectedRoutingModule
.
Second Solution
Use the providers
array of ProtectedModule
.