2

I'm on Angular 8, simple app, with a Lazy loaded module and a service providedIn: 'root': everything's working.

Now I'd like to provide the service in the LazyLoadedModule, so that the service is provided only when the lazy loaded module is downloaded on the respective route. So I've updated my service as the following:

@Injectable({
  providedIn: LazyModule
})
export class MyService {... }

This service is injected in a component declared in the LazyModule. Now I got the following error:

WARNING in Circular dependency detected: src\app\lazy-module\lazy.service.ts -> src\app\lazy-module\lazy.module.ts -> src\app\lazy-module\lazy-routing.module.ts -> src\app\lazy-module\lazy-component\lazy.component.ts-> src\app\lazy-module\lazy.service.ts

So basically:

  • the lazy.service has a dependency on the lazy.module (caused by the providedIn I guess)
  • the lazy-module has a dependency on lazy-routing module (cause the routing file is imported in its module)
  • the lazy-routing module has a dependency on some components since i'm eagerly loaded some component if route is '' for example
  • the component has a dependency of lazy.service since it's injected in the constructor...

I'm trying to replicate on Stackblitz but I got another error: No provider for LazyService!

Lazy.Service.ts

@Injectable({
  providedIn: LazyModule
})
export class LazyService { ... }

Lazy-routing.module

const ROUTES: Routes = [
  {
    path: '',
    component: LazyComponent
  }
 ];

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

Lazy-component.ts

export class LazyComponent implements OnInit {
     constructor(private lazyService: LazyService) {  }
   }

Any ideas how I can solve this?

Thanks!

user2010955
  • 3,871
  • 7
  • 34
  • 53
  • 1
    Have a look at this article from Manfred Steyer. This explains the reason quite well and what to do. Basically you need to create a separate module for all the services of your lazy loaded module and import that into your lazy loaded module. https://www.softwarearchitekt.at/post/2018/05/06/the-new-treeshakable-providers-api-in-angular-why-how-and-cycles.aspx – Erbsenkoenig Jul 11 '19 at 10:34
  • I see... I've updated the stackblitz and it seems to work... but I think it's awful don't you? – user2010955 Jul 11 '19 at 10:43
  • Mmh not too sure whether I would see that as aweful, it is a little bit of overhead. We used to have the convention to have a service module for every lazy loaded module and as a bonus all services were at one place which are global to that module. but if you don't like that way you can always use the providers array inside your lazy loaded module for those services. – Erbsenkoenig Jul 11 '19 at 10:50
  • yeah ok... it's not so terrible to have another module to collect all the services... – user2010955 Jul 11 '19 at 10:54
  • As far as I remember the angular team is working on making modules optional. So maybe sometime in the future you won't need that in any case :) – Erbsenkoenig Jul 11 '19 at 10:55
  • Possible duplicate of [Angular 6+ :ProvidedIn a non root module is causing a circular dependency](https://stackoverflow.com/questions/51062235/angular-6-providedin-a-non-root-module-is-causing-a-circular-dependency) – user2010955 Jul 11 '19 at 11:03
  • One further problem is that you cannot put the additional module in the same folder of the "main" module, otherwise if you execute ng g c main-module/my-component it raises an exception saying "More than one module matches" – user2010955 Jul 12 '19 at 07:26

1 Answers1

1

Circular dependency errors have nothing to do with Angular. It just means that you are importing in an infinite loop using TypeScript.

For example;

a-service.ts

import {B} from "./b-service';
export class A { b: B; };

b-service.ts

import {A} from "./a-service';
export class B { a:A; };

You can achieve the same declaration of a provider by just adding it to the module.

my-service.ts

@Injectable()
export class MyService {}

lazy-module.ts

import {MyService} from './my-service';

@NgModule({
   providers: [MyService]
})
export class LazyModule {}

The above has the same effect as @Injectable({provideIn: LazyModule}). This should break the chain looping back to LazyModule, but there are some cases where this does not fix the problem.

Use Interfaces Instead

If you get stuck then you have to implement an interface for the service and use that in the component or other breaking references.

my-service-interface.ts

export const MY_SERVICE: InjectionToken<IMyService> = new InjectionToken<IMyService>('MY_SERVICE');

export interface IMyService {
   // methods
}

my-service.ts

@Injectable()
export class MyService implements IMyService {
}

lazy-module.ts

@NgModule({
    providers: [
       {provide: MY_SERVICE, useClass: MyService}
    ]
})
export class LazyModule {}

my-component.ts

@Component({...})
export class MyComponent {
    constructor(@Inject(MY_SERVICE) service: IMyService) {
         // no circular references using just an interface
    }
}
Reactgular
  • 52,335
  • 19
  • 158
  • 208
  • 3
    thank you, I think the best solution is to add an additional module to be used in my LazyService in the providedIn attribute, as described in the Manfred blog link (or you here: https://stackoverflow.com/questions/51062235/angular-6-providedin-a-non-root-module-is-causing-a-circular-dependency?rq=1) – user2010955 Jul 11 '19 at 11:01