Can lazy-loaded modules share the same instance of a service provided by their parent?
Asked Answered
O

3

45

I've just run into a problem with a lazy-loaded module where parent and child module both require the same service but create an instance each. The declaration is identical for both, that is

import { MyService } from './my.service';
...
@NgModule({
   ...
   providers: [
      MyService,
      ...
   ]
});

and here's the routing setup

export parentRoutes: Routes = [
   { path: ':id', component: ParentComponent, children: [
      { path: '', component: ParentDetailsComponent },
      { path: 'child', loadChildren: 'app/child.module#ChildModule' },
      ...
   ]}
];

which, of course, is then imported in the parent module as

RouterModule.forChild(parentRoutes)

How do I go about this if I wanted to share the same service instance?

Occlusion answered 24/9, 2016 at 5:18 Comment(4)
You want single instance for whole application or for a particular featureModule?Ferrosilicon
For the whole application. Actually, the parent module mentioned above is the root module (AppModule).Occlusion
Okay Then whats the problem with this?Ferrosilicon
It's instantiated twice, once from the root module and once from the child module. I put a console.log in the service's constructor and I got 2 lines, tracked those calls down and one is from AppComponent and one from the 'root' component of the child module.Occlusion
H
60

Using a forRoot, as mentioned here, is what you need probably. The problem it's meant to solve, is directly related to the problem you are experiencing with lazy loaded modules getting their own service.

It's explained here in Configure core services with forRoot, but that section doesn't explain about the lazy-loading issue. That is explained with a little warning at the end of Shared Modules

Do not specify app-wide singleton providers in a shared module. A lazy loaded module that imports that shared module will make its own copy of the service.

@NgModule({})
class SharedModule {
  static forRoot() {
    return {
      ngModule: SharedModule,
      providers: [ MyService ]
    };
  }
}

@NgModule({
  import: [ SharedModule.forRoot() ]
})
class AppModule {}

@NgModule({
  imports: [ SharedModule ]
})
class LazyLoadedModule {}

This makes sure that the lazy loaded module doesn't get the service. But whether or not the module is lazy loaded or not, this is the pattern that is recommended for app-wide services. Though it should be noted that if you don't have any lazy loaded module, not using the forRoot patter, and just importing SharedModule, it will only be one instance of the service. But this pattern should still recommended to be followed.


UPDATE

I guess I jumped to quick on answering without fully looking at the question. In the question, there is no mention of any shared module. It seems the OP is simply trying to add the service to the @NgModule.providers in both the app module and the lazy loaded child module.

In this case, simply remove the service from the child module providers. It is not needed. The one added in the app module is enough for the child to be used.

Just remember that providers are app wide (except in the problem case this post is about), while declarations are not.

Heulandite answered 24/9, 2016 at 5:37 Comment(4)
Thing is, this service provides the theme (which colors to use) for the whole application, and the current settings are stored in a cookie, which is loaded at application start. If I go to settings in my app, the new color settings are applied by notifying the ThemeManager and picked up by any component that listens to the theme observable. Having multiple instances of that service would defy its purpose.Occlusion
I think you are misunderstanding my answer. The solution to the problem is to not add the service to @NgModule.providers, but to add it in a forRoot. In the AppModule, import it by calling TheModule.forRoot(), and when you import to other modules, just import TheModuleHeulandite
Looking at your example again, it doesn't appear you had any mention of a shared module. In this case simply don't include the service in the child module. It will still be able to use the one provided by the app module.Heulandite
This doesn’t work, each module still creates its own instance.Aara
F
17

This should work but still I would suggest you to go with SharedModule concept which contains common services,pipes,directives and components.

Shared/SharedModule

import { NgModule,ModuleWithProviders } from '@angular/core';
import { CommonModule }        from '@angular/common';

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

@NgModule({
  imports:      [ CommonModule ],
  declarations: [],
  exports:      [ CommonModule ]
})
export class SharedModule {
  static forRoot(): ModuleWithProviders {
    return {
      ngModule: SharedModule,
      providers: [ MyService ]                       //<<<====here
    };
  }
}

AppModule

import {SharedModule} from './shared/shared.module';
...
@NgModule({
   imports:[ BrowserModule,SharedModule.forRoot()],  //<<<====here
   providers: []
});
Ferrosilicon answered 24/9, 2016 at 5:38 Comment(4)
A shared module contains only common directives, components and pipes, but NOT services. Services are related to features and in most cases not to be placed in a shared module. If you have to share a service, use the forRoot() approach, described in the angular docs.Bereave
This worked for me, I fully moved files in barrel and then started getting error, but after reordering export most dependency injection resolved and last one was remain, that was in shared module , inside directive not able to resolve injection but doing above way, it solved my problem, so basically it loaded shared module lazily. I am using Angular CLI 1.2.6 and Angular 4.3.2. Thanks @FerrosiliconMayce
You first said This should work but still I would suggest you to go with SharedModule. But the code you wrote after that seems to be the sharedModule does not it?Agitation
What about shared pipes, and directive?Agitation
R
0

I have a separate layout module with a service, this service needed to work on other feature modules, using lazy load

I was able to solve the problem by exporting the service, straight from the layout module

@NgModule({
    declarations: [...],
    imports: [...],
    exports: [...],
})
export class LayoutModule {
    ...
}

export { LayoutService }
Ras answered 4/12, 2019 at 19:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.