Introduction
In Angular, you can use services
for several purposes. You can share state between components, share common functionalities, create plus one abstraction layer for some problem and so on. Services are just basic classes with the @Injectable() decorator. But how does this decorator actually works, what are the resolution rules that describes which service to use in a component?
Let's dive into these topic. This will be a series with 4-6 parts, and this is the first one.
What is DI anyway?
Dependency Injection is a design pattern, where the initiated class request its dependencies from external sources instead of creating them by itself. With this, you can increase modularity, flexibility and reusability of your codebase.
Starting point
Let's create a demo service for our starting point. It does not matter what this service can do, because we will discover how Angular handles dependency injection.
import { Injectable } from "@angular/core";
@Injectable()
export class DemoService {
constructor() {}
}
Provide possibilities of a service
So if you would like to use this service, what are your options to tell Angular
I need this service, so inject this for me?
There are two popular ways - as of my current experiences.
- Providing it on root level with
providedIn: 'root'
- Providing it on module level
These two uses almost the same technique with a slight difference. But are these two is used because these are the best solutions, or there are some convenient reasons behind this?
Angular modules never got destroy, so neither the services provided on them 1.
It not obviously a problem, but good to know this. :)
Dependency lookup mechanism
When you are providing a service, you are requesting that resource from the DI. Dependency injection is solved in the background for you by Angular, but it is worth to have a bit of knowledge how it works.
First of all, there are three different hierarchical injectors (that you can configure):
- ModuleInjector:
@NgModule
and@Injectable
withprovidedIn
- module based - ElementInjector: empty by default, but sticked to a component
- root ModuleInjector: created when bootstrapping the application
and there are two more that you can not configure:
- NullInjector(): this is the top/lowest one
- PlatformModule's ModuleInjector() - provide special core things
Visualised, it looks like the following - taken from the official docs:
ModuleInjector
ModuleInjector is created with providers
and import
properties in any of the @NgModule
s. At the end, this is a flattening of all of your providers
of all the modules that can be reached by any of the imports
, recursively.
Think of it as a huge basket of providers - configured by you!
Child ModuleInjectors are created when you lazy load a new module.
ElementInjector
Angular creates an ElementInjector for every DOM node implicitly. You can configure this injector with the providers
or viewProviders
property of a component decorator.
NullInjector
This is the parent of every Injector in the hierarchy.
If you reach this point - without any modifier - an error will be thrown for you. Yes, that well know error:
NullInjectorError: No provider for XYZService
Why is it important?
So you may have asked yourself now: why is it important for us at all? I can tell you the reason: with understanding the base mechanism of injectors, we can dive a bit deeper to understand how Dependency Injector traverse up the hierarchical graph of this Injectors and how can we influence this lookup mechanism.
With this, you can easily understand why you have a NullInjectorError
, why your components have a different instance of your service.
This may help you to debug faster and find the solution for your problem easier.
Stay tuned
This was the first part of this series. In the following one, we will check the resolution rules of dependencies as well as the first resolution modifier.
Have a nice day! :)
References:
1
Top comments (0)