Dependency Injection is a design pattern. Every framework has its own way of implementation. Angular has its own way, they called it DI Framework. In this article I'll walk you through the basics of Dependency injection.
Dependency Injection coined by two words, Dependency and Injection.
Dependency Injection
To understand Dependency lets consider an example I want to eat Swedish meatball. Before eating I have to have the meatball right? What are the ways we can have it
- I can make it myself with raw ingredients.
- I can buy frozen meatball and cook it.
- I can order it from nearby restaurant.
- I can hire a Sheff who'll prepare it either from my kitchen or somewhere else.
Let's consider the first case "I'll make it myself"
class SwedishMeatballMaker {
constructor(private ingredientProvider: IngredientProviderService){}
private collectRawIngredients(){
return this.ingredientProvider.getRawIngredients();
}
private prepareIngredients(){
const rawIngredients = this.collectRawIngredients();
const cookableIngredients = rawIngredients + "prepare to be cookable";
return cookableIngredients;
}
public cook(){
const cookableIngredients = this.prepareIngredients();
const meatball = cookableIngredients + "Put on the oven";
return meatball;
}
}
You see the whole making process is dependent on IngredientProviderService
without having proper provider the meatball will not be Yummy! This is called Dependency
.
The second term is Injection
To understand this, lets consider the previous example. Now we want to have the meatball in our hand right? What are the ways to have it on hand?
- Manage the Ingredient Provider by yourself.
- Look for the good provider in the Internet.
So looking at the internet and contact them with proper information(what type butter, what type onion, what type milk, what type egg and so on... it requires a long list actually :P ); So this process is called Injection
.
No one wants to manage it by themselves, it is a hell lot of pain right? In the same way if you want to build a robust, flexible enough, testable, scalable and maintainable in a long run App then you need a solid DI framework.
In the Injection
process they need to know the service is Injectable
?
Injectable Service
Injectable Service is a class which is annotated by @Injectable()
decorator (https://www.typescriptlang.org/docs/handbook/decorators.html) or it is declared in the provider array in component or module
@Injectable()
class IngredientProviderService {}
//or declared in the component's provider array
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
providers: [IngredientProviderService]
})
export class AppComponent {
//....
}
//or declared in the module's provider array
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [IngredientProviderService],
bootstrap: [AppComponent]
})
export class AppModule {
}
This is very simple hah! Yes it is. With the annotation Angular compiler will generate the necessary metadata
. In the following figure, we can see the overall picture of different types DI provider.
Metadata
The @Injectable()
decorator required some information to provide a good service. So in our example while we're looking at the ingredient provider,they can be different types
- Local provider
- Nationwide provider
- Global provider.
- Universal provider
The different provider has different characteristics.
Local provider
Local provider has less responsibility and can be accessible within the community. If we call them from outside of the community they might refuse to provide the service. In Angular context, we can make the injectable class accessible to another component's or class's life cycle. When that component or class is destroyed, the injectable class instance is also destroyed.
//app.component.ts
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
providers: [IngredientProviderService]
})
export class AppComponent {
constructor(private ingredientProviderService: IngredientProviderService) {
console.log(this.IngredientProviderService.getRawIngredients());
}
}
//ingredient-provider.service.ts
export class IngredientProviderService {
public getRawIngredients(){
return 'raw ingredients';
}
}
In this example we provide IngredientProviderService
in the component provider array. This type of declaration doesn't required any @Injectable()
decorator. The instance of IngredientProviderService
is available only inside the AppComponent
or its children component. Any other component declared in the same module can not access it.
Nationwide provider
Nationwide provider has more responsibility and more accessibility. Different community within the nation can access it and they can make the meatball. In Angular context, a service or class can be accessible within the module.
@NgModule({
declarations: [MyComponent],
imports: [],
providers: [IngredientProviderService]
})
export class MyModule {
}
// or using the @Injectable() annotation
@Injectable({
providedIn: MyModule
})
export class IngredientProviderService {
public getRawIngredients(){
return 'raw ingredients';
}
}
In this example we used @Injectable()
decorator with a parameter providedIn: MyModule
. We say this service will be available within MyModule
. Whenever this module created, it creates a brand new instance of IngredientProviderService
.
Global provider
Global provider has enormous responsibility and can be accessible across the country. In Angular context, a service or class can be accessible across the application.
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [IngredientProviderService],
bootstrap: [AppComponent]
})
export class AppModule {
}
// or using the @Injectable() annotation
@Injectable({
providedIn: 'root'
})
export class IngredientProviderService {
public getRawIngredients(){
return 'raw ingredients';
}
}
In this example, we declare the IngredientProviderService
in the AppModule
's providers
array. We need to declare it in the main module where we import the BrowserModule
in this example AppModule
is the main module. Another way we can say it providedIn: 'root'
. Now IngredientProviderService
is accessible across the modules. We need to handle this type of service carefully otherwise it can cause memory leak.
If we need a service that can be accessible across the modules but each module need a fresh instance then we need to say providedIn: 'any'
@Injectable({
providedIn: 'any'
})
export class IngredientProviderService {
public getRawIngredients(){
return 'raw ingredients';
}
}
Universal provider
Universal provider is the boos of the Universe. If we can make small community in Mars in coming year and someone from there want to eat Swedish meatball then we need a provider that can serve across the planet. In Angular context, if there are multiple application running in the page and we need a service across the application.
@Injectable({
providedIn: 'platform'
})
export class IngredientProviderService {
public getRawIngredients(){
return 'raw ingredients';
}
}
In this example we use providedIn: 'platform'
. platform
is the key to make it available across the applications.
Conclusion
Dependency Injection is very important in Angular. If we want to be good at Angular, there is no escape form DI. So make time to get solid basics.
Thanks for reading and I hope you learn something from this article. Don't forget to leave a comment, how you understand the DI.
Top comments (1)
well explained