Angular lazy loading and providers

While lazy loading feature modules is an easy thing in angular we often come up with situations where we need to use a shared service in two or more lazy loaded modules which leads to the creation of different instances of the same service.

To start with, let’s say we have the following set of modules:

  • AppModule
  • SharedModule
  • FirstModule
  • SecondModule

Now under the shared module we’d like to have a shared service, called SharedService, which is to be used by both FirstModule and SecondModule.

The problem

Let’s take a look at the initial approach of our files. AppModule lazy loads our two feature modules.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//AppModule

@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
RouterModule.forRoot([
{
path: '',
children: [
{
path: '',
redirectTo: 'first',
pathMatch: 'full'
},
{
path: 'first',
loadChildren: './first/first.module#FirstModule'
},
{
path: 'second',
loadChildren: './second/second.module#SecondModule'
}
]
}
])
],
bootstrap: [AppComponent]
})
export class AppModule {}

SharedModule includes in its providers array the SharedService in order to be used by both of our two feature modules.

1
2
3
4
5
6
7
8
9
//SharedModule

@NgModule({
imports: [CommonModule, RouterModule],
declarations: [NavComponent],
providers: [SharedService],
exports: [NavComponent]
})
export class SharedModule {}

FirstModule and SecondModule import SharedModule in order to be able to use the SharedService.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//FirstModule

@NgModule({
imports: [CommonModule, FirstRouter, SharedModule],
providers: [],
declarations: [FirstComponent]
})
export class FirstModule {}

//SecondModule

@NgModule({
imports: [CommonModule, SharedModule, SecondRouter],
declarations: [SecondComponent],
providers: []
})
export class SecondModule {}

Finally SharedService.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Injectable } from '@angular/core';

@Injectable()
export class SharedService {
private numbers: number[] = [];

constructor() {}

add(num: number) {
this.numbers.push(num);
}

getNumbers(): number[] {
return this.numbers;
}
}

We notice that each of our feature modules create a new instance of SharedService that’s why they display a different list of numbers.

The solution

In order to get this working we should import our SharedModule forRoot() in our AppModule.

Firstly we import SharedModule in our AppModule.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
imports: [
BrowserModule,
RouterModule.forRoot([
{
path: '',
children: [
{
path: 'first',
loadChildren: './first/first.module#FirstModule'
},
{
path: 'second',
loadChildren: './second/second.module#SecondModule'
}
]
}
]),
SharedModule.forRoot() // Here's the trick
];

Then we set a forRoot() method in our SharedModule and include SharedService in providers array.

1
2
3
4
5
6
7
8
9
10
11
12
@NgModule({
imports: [CommonModule, RouterModule],
declarations: [NavComponent],
exports: [NavComponent]
})
export class SharedModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: SharedModule,
providers: [SharedService]
};
}

Now all of our components lazy loaded or not will receive the exact same instance of our SharedService when injected.

If you would like to dig in here’s the snippet.