3

In the angular docs, it lists the types of feature modules that one can implement. One of those being a service module.

https://angular.io/guide/module-types

In earlier versions of Angular, you would create your service and list it in the providers array of the service NgModule. This NgModule would then be imported by the consuming application where the service will be provided in the application root injector.

Injectable()
export class MyService {}

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

NgModule({
    imports: [MyServicesModule]
})
export class AppModule {}

The newest Angular recommendation is to use the Injectable annotation, where you no longer need to list it in the providers definition.

Injectable({ providedIn: 'root'})
export class MyService {}

Therefore is there any point in creating a service module? Do you just create the services you want provided in the root injector using the above annotation and just import the service class directly and inject accoridngly?

Fergal Rooney
  • 1,330
  • 2
  • 18
  • 31
  • 1
    It may help:- https://stackoverflow.com/questions/50848357/what-is-the-purpose-of-providedin-with-the-injectable-decorator-when-generating – Mahi Jul 31 '19 at 17:23

2 Answers2

13

A service module would be required if you are using lazy loaded modules which have specific services only provided inside that module.

You can't provide a service into the same module where components of that module get the service injected.

So assume you have a MyLazyFeatureModule

@NgModule({
  imports:      [ ],
  providers:    [ ],
  declarations: [ MyFeatureComponent ],
  exports:      [ ],
})
export class MyLazyFeatureModule { }

and you have a service which contains logic only for your feature, then you cannot do the following:

Injectable({ providedIn: MyLazyFeatureModule})
export class MyFeatureService {}

You would get a cycle!

To resolve the issue you need to create a specific service module and import that module into the feature module:

@NgModule({
  imports:      [ ],
  providers:    [ ],
  declarations: [ ],
  exports:      [ ],
})
export class MyLazyFeatureServiceModule { }
Injectable({ providedIn: MyFeatureServiceModule})
export class MyFeatureService {}
@NgModule({
  imports:      [ MyLazyFeatureServiceModule ],
  providers:    [ ],
  declarations: [ MyFeatureComponent ],
  exports:      [ ],
})
export class MyLazyFeatureModule { }

Please have a look at this article (three shakable providers why, how and cycles by Manfred Steyer which explains the details of why there is a cycle quite in depth.

Besides of that use case you probably would not need a service module if you only have services provided in root. You could create one to actually have all services in one place. But maybe a core module would do for that as well. That's up to you.

Erbsenkoenig
  • 1,584
  • 14
  • 18
  • If the service module is not going to be imported, then I think it doesn't make sense to have a module, seems redundant. Can you explain what you meant by core module? – Fergal Rooney Jul 31 '19 at 12:10
  • You have to import that service module into the feature module. I‘ll add a structural overview to my answer. Maxbe then it‘s a bit clearer – Erbsenkoenig Jul 31 '19 at 12:31
  • Updated my answer hope it's a bit clearer now why you would need that service module – Erbsenkoenig Jul 31 '19 at 14:03
  • Thanks for your help on this. I tried to follow the link you provided but I don't quite grasp what it's trying to get across. The MyFeatureModule above is intended to be used in a lazy loaded context? If so, might be worth updating the names of the modules to include "Lazy". Can you explain what the cycle is that you are referring to in the first example? – Fergal Rooney Jul 31 '19 at 14:26
  • Yes that would probably most of the times the use case. You have a lazy loaded feature module and you want to limit the service to that scope so that the service is only bundled inside the lazy loaded module and not inside the main app. And if you want to achieve that, you need to add a service module. – Erbsenkoenig Jul 31 '19 at 14:29
  • Okay great, thank you! I understand where you are coming from with regard to scope. Still confused by the cycle reference though! – Fergal Rooney Jul 31 '19 at 14:31
  • Can we create a service module. I know that we can register a service in 'providers' array in a module @NgModule({}) or inside @Component({}). What does creating a service module MyFeatureServiceModule means? – Mahi Jul 31 '19 at 16:56
  • 1
    @FergalRooney the cycle happens because when the compiler tries to create the module it runs through the components and creates instances of services injected into the components if not already existent. When the compiler comes to a services which is providedIn the same module it is currently trying to create, the compiler runs in a cycle because he needs to create the service in order to create the module but also needs the module to be created to create the service. – Erbsenkoenig Jul 31 '19 at 17:01
  • 1
    @Mahi a service module is a pattern to group services into own feature related modules so that your routed modules were a bit more readable because the providers list was just not so long. The question here is whether with the introduction of tree shakable providers (the providedIn declaration) this pattern became obsolet or whether there is still a use case when those modules come in useful – Erbsenkoenig Jul 31 '19 at 17:05
  • @Erbsenkoenig , if I have a service 'myService.ts' having class 'myServiceClass' and want to inject the instance to my feature Module 'myFeatureModule.module.ts' having class 'myFeatureModule'. Can't I register 'myService.ts' as @Injectable({ providedIn: 'myFeatureModule', }) as we do @Injectable({ providedIn: 'root', }) ? I think we can do like this as well. Basically, 'providedIn' is just a shortcut to register a service. https://stackoverflow.com/questions/50848357/what-is-the-purpose-of-providedin-with-the-injectable-decorator-when-generating – Mahi Jul 31 '19 at 17:18
  • No one said you can‘t just use the providers list or it is necessary to use a service module. The tree shakable notation (providedIn) is not just a shorting but rather there so the tree shaking of unused code parts is even more efficient. There is heaps of explanation in the official angular documentation and in the link I provided in my answer if you would like to learn more about it. So whatever way to go depends on the use case... – Erbsenkoenig Jul 31 '19 at 17:46
  • Excellent information @Erbsenkoenig. Makes so much more sense now. My only recommendation would be to update your example module class names to include the word "Lazy" for clarity to benefit others. – Fergal Rooney Aug 01 '19 at 10:25
0

A singleton service is a service that providedIn: 'root' to take advantage of tree-shaking.

The location of a service file:

  • in a feature module's services folder, you could put a.service.ts file here
  • for a service that doesn't seem to belong to a specific feature module (eg. a logger service used by many modules), you may organize the service into a core folder without a core.module.ts. See angular doc on coding style guide.

However, depending on the case, if you are using ngrx store or similar lib, you may want a CoreModule or some ServiceModule(s) to import the files (ngrx module) away there. See angular v6 doc on coding style guide.

P.S. For sharing things app-wide, a SharedModule is considered for pipes, directives, and components, but not for services. shared.module.ts is likely to have no providers property in its @NgModule decorator. See angular doc on SharedModule.

The underlying reason for this naming convention 1 is that the root injector is there for all singleton services (working well for both eager modules and lazy loaded modules), so that you usually don't want to create another injector (unless you know what you're doing, such as the forRoot pattern 2 eg<- btw please remember that such a hassle is avoided if you have already let all services providedIn: 'root' ).

To sum up, the angular doc recommend making all services singleton and tree-shakable 3 4 ; the services fit into 3 places: a feature module's services folder OR a core module / service module, especially when using ngrx OR a core folder just for holding services organized and tidy.


related docs and references

https://angular.io/guide/singleton-services

https://angular.io/guide/providers

https://angular.io/guide/router

https://angular.io/guide/lazy-loading-ngmodules

https://v6.angular.io/guide/styleguide CoreModule 1 2 3

https://angular.io/guide/styleguide

https://angular.io/guide/module-types

tinystone
  • 369
  • 3
  • 6