The Angular dependency injection is now core part of the Angular and allows dependencies to be injected into the component or class. In this tutorial, we will learn what is Angular Dependency Injection is and how to inject dependency into a Component or class by using an example
Applies to: Angular 2 to the latest edition of i.e. Angular 8. Angular 9, Angular 10, Angular 11
Table of Content
What is Dependency
We built a ProductService
in the Angular Services. The AppComponent
is depends on the ProductService
to provide the list of Products to display.
In short, the AppComponent
has a Dependency on ProductService
.
What is Angular Dependency Injection
Dependency Injection (DI) is a technique in which we provide an instance of an object to another object, which depends on it. This is technique is also known as “Inversion of Control” (IoC)
Let us look at the ProductService
, which we created in our Angular Services tutorial. You can download the source code from GitHub (available under the folder Services).
Our ProductService returns the hard-coded products when getProduct method invoked.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import {Product} from './Product' export class ProductService{ public getProducts() { let products:Product[]; products=[ new Product(1,'Memory Card',500), new Product(1,'Pen Drive',750), new Product(1,'Power Bank',100) ] return products; } } |
We instantiated the productService directly in our component as shown below.
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 | import { Component } from '@angular/core'; import { ProductService } from './product.service'; import { Product } from './product'; @Component({ selector: 'app-root', templateUrl: './app.component.html', }) export class AppComponent { products:Product[]; productService; constructor(){ this.productService=new ProductService(); } getProducts() { this.products=this.productService.getProducts(); } } |
Best Angular Books
The Top 8 Best Angular Books, which helps you to get started with Angular
The ProductService
Instance is local to the Component. The AppComponent
is now tightly coupled to the ProductService
, This tight coupling brings a lot of Issues.
The ProductService
hardcoded in our AppComponent
. What if we want to use BetterProductService
. We need change wherever the ProductService
is used and rename it to BetterProductService
. What if we wanted to use either ProductService
or BetterProductService
based on users preference.
What if ProductService
depends on another Service. And then we decides to change the service to some other service. Again we need to search and replace the code manually
It is hard to test this Component as it is difficult to provide the Mock for the ProductService
. For Instance, what if we wanted to substitute out the implementation of ProductService
with MockProductService
during testing.
Our Component Class has now tied one particular implementation of ProductService
. It will make it difficult to reuse our component.
We would also like to make our ProductService
singleton so that we can use it across our application.
How to solve all these problems. Move the creation of ProductService
to the constructor the AppComponent
class as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 | export class AppComponent { products:Product[]; constructor(private productService:ProductService) { } getProducts() { this.products=this.productService.getProducts(); } } |
Now our AppComponent
does not create the instance of the ProductService
. It just asks for it in its Constructor. The AppComponent
is now decoupled from the ProductService
. The AppComponent
does not know anything about the ProductService
. It just works with the ProductService
passed onto it. You can pass ProductService
, BetterProductService
or MockProductService
. The AppComponent
does not care.
Now the responsibility of Creating the ProductService
falls on the creator of the AppComponent
.
The above pattern is known as Dependency Injection Pattern.
Why use Dependency Injection
Our Component is now loosely coupled to the ProductService
. AppComponent
does not know how to create the ProductService
.
AppComponent
is now easier to Test. Our AppComponent
is not dependent on a Particular implementation of ProductService
anymore. It will work with any implementation of ProductService
that is passed on to it. You can just create a mockProductService
Class and pass it while testing.
Reusing of the component is becomes easier. Our Component will now work with any ProductService
as long as the interface is honored.
Dependency injection pattern made our AppComponent
testable, maintainable etc.
But does it solve all our Problem ?. No, we just moved the Problem out of Component to the Creator of the Component.
How do we create an instance of ProductService
and pass it to the AppComponent
? That is what Angular Dependency Injection
does.
Angular Dependency Injection Framework
Angular Dependency Injection framework implements the Dependency injection Pattern in Angular. It creates & maintains the Dependencies and injects them into the Components or Services which requests for it.
Parts of Angular Dependency Injection Framework
There are five main parts of the Angular Dependency injection Framework.
Consumer
The Component that needs the Dependency. In the above example, the AppComponent
is the Consumer
Dependency
The Service that is being injected. In the above example the ProductService
is the Dependency
DI Token
The DI Token uniquely identifies a Dependency. We use DI Token when we register dependency
Provider
The Providers Maintains the list of Dependencies along with their Tokens. The DI Token is used to identify the Dependency.
Injector
Injector holds the Providers and is responsible for resolving the dependencies and injecting the instance of the Dependency to the Consumer
How Dependency Injection works in Angular
The dependencies are registered with the Provider
. This is done in the Providers
metadata of the Injector
.
Angular Provides an instance of Injector
& Provider
to every Consumer.
Consumer when instantiated, It declares the Dependencies it needs in its constructor.
Injector
reads the Dependencies from the constructor of the Consumer and looks for the dependency in the provider. The Provider provides the instance and injector, then injects it into the consumer. If the instance of the Dependency is already exists, then it is reused making the dependency singleton.
How to Use Dependency Injection
We had created a simple ProductService
in our last tutorial. Let us now update it to use Dependency Injection.
First, we need to register the dependencies with the provider. This is done in the providers
metadata array of @Component
decorator.
1 2 3 | providers: [ProductService] |
Next, we need to tell angular that our component needs dependency injection. This is done by using the @Injectable()
decorator.
@Injectable()
decorator is not needed, if the class already has other Angular decorators like @Component
, @pipe
or @directive
etc. Because all these are a subtype of Injectible
.
Since our AppComponent is already decorated with @Component
, we do not need to decorate with the @Injectable
@Injectible
is also not needed if the class does not have any dependencies to be injected. However it is best practice is to decorate every service class with @Injectable()
, even those that don’t have dependencies.
Next, our AppComponent
needs to ask for the dependencies. This is done in the constructor as shown below
1 2 3 4 | constructor(private productService:ProductService) { } |
That’s it.
The Services are usually not added to Providers
array of the Component, but to the Providers
array of the @NgModule
. Then they will be available to be used in all the components in the application
When AppComponent
is instantiated it gets its own Injector
instance. The Injector
knows that AppComponent
requires ProductService
by looking at its constructor. It then looks at the Providers
for a match and Provides
an instance of ProductService
to the AppComponent
The Complete AppComponent is as follows
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 | import { Component } from '@angular/core'; import { ProductService } from './product.service'; import { Product } from './product'; @Component({ selector: 'app-root', templateUrl: './app.component.html', providers: [ProductService] }) export class AppComponent { products:Product[]; constructor(private productService:ProductService){ } getProducts() { this.products=this.productService.getProducts(); } } |
Injecting Service into Service
We looked at how to inject ProductService
to a component. Now let us look at how to inject service into another service.
Let us build loggerService
, which logs every operation into a console window and inject it into our ProductService
.
Logger Service
Create the logger.service.ts
and add the following code
1 2 3 4 5 6 7 8 9 10 | import { Injectable } from '@angular/core'; @Injectable() export class LoggerService { log(message:any) { console.log(message); } } |
The LoggerService
has just one method log, which takes a message
and writes it to the console.
We are also using @Injectible
metadata to decorate our logger class. Technically, this is not required here as the logger class does not have any external dependencies.
Make it a practice to add @Injectible
metadata for the following reasons
Future proofing: No need to remember @Injectable()
when we add a dependency later.
Consistency: All services follow the same rules, and we don’t have to wonder why a decorator is missing.
Product Service
Now we want to inject this into our ProductService class
The ProductService
needs loggerService
to be injected. Hence the class requires @Injectible
metadata
1 2 3 4 | @Injectable() export class ProductService{} |
Next, In the constructor of the ProductService
ask for the loggerService
.
1 2 3 4 5 | constructor(private loggerService: LoggerService) { this.loggerService.log("Product Service Constructed"); } |
And update the GetProducts
method to use the Logger Service.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public getProducts() { this.loggerService.log("getProducts called"); let products:Product[]; products=[ new Product(1,'Memory Card',500), new Product(1,'Pen Drive',750), new Product(1,'Power Bank',100) ] this.loggerService.log(products); return products; } |
Finally, we need to register LoggerService
with the Providers
metadata.
Angular does not have any options add providers
in the Service Class. The Providers
must be added to the Component/Directive/Pipe or to the Module.
Open the AppComponent
Update the Providers array to include LoggerService
1 2 3 | providers: [ProductService,LoggerService] |
That’s it. As you click on the Get Products button, you will see the Console window updated with the Log messages
Using NgModule to Provide Dependencies
In the above example, we registered the dependencies in the Providers
array of the component class. The dependencies are only available to the component where it is registered and to its child components.
To Make the dependencies available to the entire application, we need to register it in the root module.
Remove the providers: [ProductService,LoggerService],
from the AppComponent
and move it to the AppModule
as shown below
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 | import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { HttpModule } from '@angular/http'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { ProductService } from './product.service'; import { LoggerService } from './logger.service'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, HttpModule, FormsModule ], providers: [ProductService,LoggerService], bootstrap: [AppComponent] }) export class AppModule { } |
Providing the service in the root module will create a single, shared instance of service and injects into any class that asks for it.
The above code works because, we Angular creates the Tree of Injectors with parent child relationship similar to the Component Tree. We will cover this next tutorials.
The services injected at the module level are app-scoped, which means that they can be accessed from every component/service within the app.Any service provided in the Child Module is available in the entire application.
The services is provided in a lazy module are module scoped and available only to the lazy loaded module.
The services provided in the Component level are available only to the Component & and to the child components.
Suggested Reading
Angular Services
Angular Injector
Angular Providers
Angular Hierarchical Dependency Injection
Summary
We learned what is Angular Dependency Injection. We created two services and registered it in the Providers array of Component. We then injected one of the service into Component and the other one into another service.
Later, we removed the Providers from the Component and moved it to the Root Module, with the same result.
You can download the source code from Github. In the next tutorial’s we look at the Providers and injector in more detail.
there is a little error , @Injectable not @Injectible 🙂 🙂
For those who are generating their service via VSCode “Generate Service” or terminal’s
ng generate service
, and are wondering why the service works without having to indicateproviders
in the component:The generator creates the service with the lines:
@Injectable({
providedIn: 'root'
})
Which seems equivalent to adding the service in app.module.ts > providers.
If you remove this providedIn key in both services, you’ll get an error that goes away when you add the providers manually in the component.
Good article
Thanks a lot for the article! I agree with Sai Kiran: very clearly and logically structured.
Thanks, Chris
Excellent article. Understood clearly about dependency injections in angular. Couldn’t ask for more. Thank you so much
Thanks Sai Kiran