Key feature from Angular even since AngularJS (ie: Angular 1.X), DI is a pure treasure from Angular, but injector hierarchy can be difficult to grasp at first. Add routing and dynamic load of modules and all could go wild... Services get created multiple times and if stateful (yes functional lovers, you sometimes need states) the global states (even worse 😅) is out of sync in some parts of your app.
To get back in control of the singleton instances created for your app singleton, you need to be aware of a few things.
Let's get started...
Tour of hero app
Let's reuse the tour of heroes app that you should be familiar with from when you first started at angular.io. Thansk to LarsKumbier for adapting it to webpack, I've forked the repo and adjust it to my demo's needs. All source code could be find on github.
In this version of Tour of heroes, the app displays a Dashboard page and a Heroes page. I've added a
RecentHeroCompoent
that displays the recently selected heroes in both pages. This component uses the ContextService
to store the recently added heroes.
See AppModule in master branch.
Provider at Component level
Let's go to
HeroSearchComponent
in src/app/hero-search/hero-search.component.ts
file and change the @Component
decorator:
@Component({
selector: 'hero-search',
templateUrl: './hero-search.component.html',
styleUrls: ['./hero-search.component.css'],
providers: [ContextService] // [1]
})
export class HeroSearchComponent implements OnInit {
if you add line [1], you get something like this drawing:
Run the app again.
What do you observe?
The heroes page is working fine listing below the recently visited heroes. However going to Dashboard/SearchHeroComponent, the recently visited heroes list is empty!!
The recently added heroes is empty in
HeroSeachComponent
because you've got a different instance of ServiceContext
. Dependency injection in Angular relies on hierarchical injectors that are linked to the tree of components.
This means that you can configure providers at different levels:
- for the whole application when bootstrapping it in the
AppModule
. All services defined in providers will share the same instance. - for a specific component and its sub components. Same as before but for à specific component. so if you redefine providers at Component level, you got a different instance. You've overriden global AppModule providers.
Tip: don't have app-scoped services defined at component level. Very rare use-cases where you actually want
Provider at Module level
What about providers at module level, if we do something like:
Let's first refactor the code, to introduce a
SharedModule
as defined in angular.io guide.
In your SharedModule
, we put the SpinnerComponent
, the RecentHeroComponent
and the ContextService
. Creating the SharedModule
, you can clean up the imports
for AppModule
which now looks like:
@NgModule({
declarations: [
AppComponent,
HeroDetailComponent,
HeroesComponent,
DashboardComponent,
HeroSearchComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
SharedModule,
InMemoryWebApiModule.forRoot(InMemoryDataService),
AppRoutingModule
],
providers: [
HeroSearchService,
HeroService,
ContextService
],
bootstrap: [
AppComponent
]
})
export class AppModule {}
Full source code in github here. Notice
RecentHeroComponent
and SpinnerComponent
has been removed from declarations. Intentionally the ContextService
appears twice at SharedModule
and AppModule
level. Are we going to have duplicate instances?
Nope.
A Module does not have a specific injector (as opposed to Component which gets their own injector). Therefore when
AppModule
provides a service for token ContextService
and imports a SharedModule
that also provides a service for token ContextService
, then AppModule
's service definition "wins". This is clearly stated in AppModule angular.io FAQ.
Where to go from there
In this blog post you've seen how
providers
on component plays an important role on how singleton get created. Modules are a different story, they do not provide encapsulation as component.
Next blog posts, you will see how DI and dynamically loaded modules plays together. Stay tuned.
Tweet
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.