51

I'm trying to provide a resolve service via the new providedIn attribute.

This is a translations resolver which I use in a protected module:

import { Injectable } from '@angular/core';

import { Observable , pipe } from 'rxjs';
import {map} from "rxjs/operators";

//This is causing: "WARNING in Circular dependency detected:"
import {ProtectedModule} from "../../../protected/protected.module";

import { HttpHandlerService } from '../../http/http-handler.service';

@Injectable({
  providedIn: ProtectedModule //Over here (I need the import for this line)
})
export class TranslationsResolverService {
  constructor(private _httpHandlerService : HttpHandlerService) { }
    resolve(): any {
      //Do Something...
    }
}

I declared the translations resolver service in the protected routing module:

import { NgModule }           from '@angular/core';
import {RouterModule, Routes} from '@angular/router';

import {AuthGuard} from "../core/resolvers/auth/auth.guard";
import {TranslationsResolverService} from "./../core/resolvers/translations/translations-resolver.service";

const routes: Routes = [
  {
    path : 'app' ,
    component: ProtectedComponent,
    resolve : {
      translations : TranslationsResolverService // <---- Over here - i can't remove that of course
    },
    canActivate: [AuthGuard],
    ]
  }
];


@NgModule({
  imports : [RouterModule.forChild(routes)],
  exports : [RouterModule]
})
export class ProtectedRoutingModule { }

Because of the fact that I import (typescript import) the protected.module in the translations-resolver.service.ts in order to use it in the providedIn attribute I get a WARNING in Circular dependency detected:

path/to/translations-resolver.service.ts -> 

protected/protected.module.ts ->

protected/protected-routing.module.ts -> 

path to translations-resolver.service.ts

The 2nd path (protected/protected.module.ts) is added due to the providedIn attribute.

I can fix this by just providing the translationsResolver as a NgModule provider (in the providers array) but I prefer it to be an injectable provider.

Any suggestions for solving this?

Rot-man
  • 18,045
  • 12
  • 118
  • 124
  • You should always provide your service in the root injector unless there is a case where you want the service to be available only if the consumer imports a particular @NgModule. Refer: https://angular.io/guide/providers#provider-scope – Krishna Mohan Jan 21 '20 at 05:10
  • As of angular 10, you can also declare providers in components that are scoped only to that component's hierarchy. – S.D. Nov 12 '20 at 06:13
  • in Angular 9+ you can use providerIn: any, checkout my answer – maxisam Jun 07 '21 at 15:49

5 Answers5

28

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.

DarkNeuron
  • 7,981
  • 2
  • 43
  • 48
  • I want to mark this answer but i hope somebody will give a solution for this problem. Each time i try to use a specific service in a module scope (and not root) in throw some circular dependencies warnings. – Rot-man Jul 04 '18 at 10:43
  • 2
    It's a bad answer anyway :-) – DarkNeuron Jul 04 '18 at 15:16
  • 1
    That referenced comment got very badly voted down - plus the comment says "With tree shakeable providers, you now have zero reasons to provide a service from a module (there is a rare case on some lazy loaded services with a component using the service from a separate lazy module)." So that isn't zero reasons - and is in fact my exact scenario. – Simon_Weaver Mar 16 '19 at 20:53
  • Disappointing indeed, although I have to say this hasn't caused me any issues thus far. – RJM Mar 22 '21 at 12:44
  • use providedIn: any, after Angular 9, checkout my answer – maxisam Jun 07 '21 at 15:46
  • I am happy to know i was following good thing lol but I felt it bad lol – minigeek Jul 05 '21 at 11:03
22

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:

The providedIn syntax seems to have 2 main benefits:

  1. It supports tree-shaking of unused services
  2. 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:

  1. 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 all providedIn: 'root', in which case they will take care of themselves).
  2. 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], })

Dan King
  • 3,412
  • 5
  • 24
  • 23
  • 4
    You should put the addendum at the top of your answer as something like `Update Oct 2019` so people read it first before your old answer. (Saves unnecessary reading and confusion) – takanuva15 Jan 23 '20 at 16:03
  • Use providedIn: any, after Angular 9 – maxisam Jun 07 '21 at 15:45
  • 1
    @maxisam any is not meant for that. – minigeek Jul 05 '21 at 11:04
  • @minigeek what does it mean? – maxisam Jul 06 '21 at 18:51
  • @minigeek I think you should check out my answer. I put the all the references about using any. I believe what I said should be correct. – maxisam Jul 06 '21 at 18:56
  • @maxisam what I mean is why do you want to use any to create multiple instances in lazy loaded modules. Its really isn't necessary. Root injects only when it is required, this tree shaking ability already makes it efficient. I didn't see any usecase of any till now – minigeek Jul 06 '21 at 18:57
  • 1
    @maxisam your answer helped me understand any better lol. Thanks for that..check my new commenf – minigeek Jul 06 '21 at 18:57
  • @maxisam but your answer is not wrong either so +1. Its just that one has to remember its not singleton across all modules – minigeek Jul 06 '21 at 18:59
  • @minigeek yes. It actually help in this case. I know because I had the same problem. If you check the code, it actually works just like "providedIn: ProtectedModule", which is exactly what op was trying to do but limited by dependency check. And thx for the upvote :-) – maxisam Jul 06 '21 at 19:04
  • 1
    @maxisam yes, you are right :). I faced this many time specially in resolver. Makes sense, using any will help OP achieve what he wants to. But most answers are talking about what's not OPs requirement but an alternative. – minigeek Jul 06 '21 at 19:07
17

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.

Reactgular
  • 52,335
  • 19
  • 158
  • 208
2

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);
    }
  }
maxisam
  • 21,975
  • 9
  • 75
  • 84
0

Check forwardRef() function from angular/core. It allows to refer to references which are not yet defined.

import {MyService} from './service';

constructor(@Inject(forwardRef(() => MyService)) public myService: MyService) {
}
Ozgur
  • 3,738
  • 17
  • 34
  • Ozgur i'm not sure how this can help in my case to solve the circular dependency. – Rot-man Jun 28 '18 at 07:48
  • Instead of injecting your service in Routes, use constructor. – Ozgur Jun 28 '18 at 08:02
  • I have to "inject" (register is more correct) the resolver in the routes for the resolve phase. and i have want to use the providedIn syntax in order to mark the scope of the resolver service. Which one of them should i remove / change according to your solution? – Rot-man Jun 28 '18 at 09:41
  • This is not good. As long as there is an `import` statement, the problem will remain unsolved. – Avius Dec 21 '19 at 22:16