Dependency Injection In Angular

Dependency Injection In Angular

Angular dependency injection is now an integral component of the framework. It gives us the ability to inject dependencies into Components, Directives, Pipes, and Services. In this article, We will learn what Angular Dependency Injection is and how to inject dependency into a ComponentsDirectivesPipes, or Services using an example.

What is Dependency

In the Angular Services lesson, we created a ProductService. The ProductService provides the AppComponent with a list of Products to show.

In other words, the AppComponent is reliant on ProductService.

What is Angular Dependency Injection

DI is a technique in which a class acquires its dependencies from external sources rather than building them from scratch.

Let's have a look at the ProductService that we built in the Angular Services article.

When the getProduct method is called on our ProductService, it returns the hard-coded products.

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;               
    }
}

As demonstrated below, we directly instantiated the productService in our Component.
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();
   }
 
}

The Component's ProductService Instance is local. The AppComponent is now inextricably linked to the ProductService, which causes a slew of problems.

Our AppComponent has the ProductService hardcoded. What happens if we wish to use BetterProductService instead? We'll need to rename ProductService anywhere it's used to BetterProductService.

What if ProductService is reliant on a third-party service? After that, we decide to switch to a different service. We will have to manually hunt for and replace the code once more.

It's difficult to test this Component because providing a Mock for the ProductService is tricky. For example, suppose we wanted to replace ProductService's implementation with MockProductService during testing.

Our Component Class has now connected one specific ProductService implementation. It will be tough to reuse our components as a result.

We'd like to make our ProductService a singleton so that we may use it throughout our project.

What is the best way to solve all of these issues? As illustrated below, the creation of ProductService should be moved to the AppComponent class's constructor.
export class AppComponent {    
   products:Product[];    
 
   constructor(private productService:ProductService) {    
 
   }    
 
   getProducts() {        
       this.products=this.productService.getProducts();    
   }
}

The ProductService object is not created by our AppComponent. It simply requests it in its Constructor. The author of the AppComponent is responsible for creating the ProductService.

The Dependency Injection Pattern is the pattern described above.

Benefits of Dependency Injection

loosely coupled

The ProductService and our Component are now loosely connected.

The AppComponent has no idea how to make a ProductService. In reality, it has no knowledge of the ProductService. It only works with the ProductService that has been supplied to it. ProductService, BetterProductService, and MockProductService are all valid options. The AppComponent is unconcerned.

Easier to Test

It's now easy to test AppComponent. Our AppComponent is no longer reliant on a specific ProductService implementation. It will function with any ProductService implementation that is supplied to it. Simply construct a mockProductService Class and send it around during testing.

Reusing the Component

It becomes easier to reuse the component. As long as the interface is respected, our Component will now operate with any ProductService.

Our AppComponent is now tested, maintainable, and more thanks to the dependency injection approach.

But does it fix all of our issues? No, we simply transferred the problem from the Component to the Component's Creator.

How do we make a ProductService object and pass it to the AppComponent? Angular Dependency Injection accomplishes this.

Angular Dependency Injection Framework

In Angular, the Angular Dependency Injection framework provides Dependency Injection. It builds, maintains, and injects Dependencies into Components, Directives, and Services.

The Angular Dependency Injection Framework has five key actors.

Consumer

The Consumer is the class that requires the Dependency (Component, Directive, or Service). The Consumer in the preceding example is the AppComponent.

Dependency

The type of service we aim to provide to our customers. The Dependency in the preceding example is ProductService.

Injection Token (DI Token)

A Dependency is identified by its Injection Token (DI Token). When we register a dependency, we use DI Token.

Provider

The Providers keep track of the dependencies and their Injection Tokens. To identify the Dependency, it uses the Injection Token.

Injector

The injector holds the Providers and is in charge of resolving dependencies and injecting the Dependency instance to the Consumer.

The Injector searches the Providers for Dependency using the Injection Token. After that, it generates a dependence instance and injects it into the consumer.

Using Dependency Injection

Registering the Dependency with the Injector

Every component and directive in the application receives an instance of Injector and Provider from Angular ( Consumers). It also creates an instance of the Injector at the module level and at the application's root. It basically builds a Tree of Injectors with a parent-child relationship.

The Provider is notified of the dependencies. This is done in the Injector's Providers metadata.
providers: [ProductService]

For instance, in the following code, ProductService is registered with the AppComponent's Injector.
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  providers: [ProductService]
})
export class AppComponent
{

We may also add the Services to the @NgModule's Providers array. They will then be available for use in all of the application's components and services. In this situation, the ProductService was introduced at the module level to the Injector instance.
@NgModule({
  declarations: [...],
  imports: [...],
  providers: [ProductService],
  bootstrap: []
})

Asking for Dependency in the Constructor

In their constructor, Components, Directives, and Services (Consumers) declare the dependencies they require.
constructor(private productService:ProductService) { 
}

The dependencies are read from the Consumer's constructor by the injector. It then searches the supplier for that dependence. The instance and injector are provided by the Provider, who then injects them into the consumer.

If the Dependency's instance already exists, it will be reused. This turns the dependency into a singleton.

Angular Dependency Injection Example

In the last article, we established a simple ProductService. Let's change it to use Dependency Injection now.

First, we must notify the provider of the dependencies. This is done via the @Component decorator's providers metadata array.
providers: [ProductService]

Following that, we must inform Angular that our component requires dependency injection. The @Injectable() decorator is used to do this.

If the class already has other Angular decorators like @Component, @pipe, or @directive, the @Injectable() decorator isn't required. Because they're all Injectable subtypes.

We don't need to use @Injectable because our AppComponent is already decorated with @Component.

Our AppComponent must then request the requirements. This is done in the Component's constructor.
constructor(private productService:ProductService) { 
}

That is all there is to it.

When an AppComponent is created, it is given its own instance of the Injector. By looking at AppComponent's constructor, the Injector can see that it requires ProductService. It then searches the Providers for a match and returns a ProductService instance to the AppComponent.

The following is the complete AppComponent.
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 Another Service

We looked at how to inject a component with ProductService. Let's have a look at how to inject one service into another.

Let us construct loggerService, which records every operation into a console window and inject it into our ProductService.

Logger Service

Add the following code to the logger.service.ts file.
import { Injectable } from '@angular/core';
 
@Injectable()
export class LoggerService {
  log(message:any) {
    console.log(message);
  }
}

The LoggerService only has one method, log, which accepts a message and outputs it to the console.

Our logger class is also decorated with @Injectible metadata. We don't have to do it technically because the logger service has no external dependencies.

Product Service

This is what we want to inject into our ProductService class now.

The loggerService must be injected into the ProductService. As a result, @Injectible metadata is required by the class.
@Injectable()
export class ProductService{}

Then, in the ProductService's constructor, request the loggerService.
constructor(private loggerService: LoggerService) {
    this.loggerService.log("Product Service Constructed");
}

Also, the GetProducts method should be updated to use the Logger Service.
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, LoggerService must be registered with the Providers metadata.

Open the AppComponent and add LoggerService to the providers array.
providers: [ProductService,LoggerService]

That is all there is to it. The Console window will be updated with the Log messages as soon as you press the Get Products button.

Providing Dependency from Angular Module

We registered the dependencies in the Providers array of the component class in the preceding example. Only the component where we register the dependencies, as well as its descendant components, have access to them.

We must register the dependencies in the root module to make them available to the entire program.

Remove the [ProductService,LoggerService] providers from the AppComponent and place them in the AppModule as shown below.
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 { }

When the service is provided in the root module, it creates a single, shared instance of the service that is injected into any class that requests it.

Because Angular constructs a Tree of Injectors with a parent-child connection similar to the Component Tree, the above code works. We'll go over it in more detail in the next article.

ProvidedIn

You can add ProductService to the providedIn metadata with the value root instead of adding it to the AppModule's providers.

In fact, using ProvidedIn to offer a service in a module is the preferred method.
@Injectable({
  providedIn:'root'
})
export class ProductService {

}


Service Scope

We can access the services we provide at the root module from any component or service within the app because they are app-scoped.

Any service provided in the other Modules (aside from the Lazy Loaded Module) is available across the application.

The services supplied by a Lazy Loaded Module are module specific and only available within that module.

Only the Component and its child components have access to the services provided at the Component level.

Conclusion

In this article, We learned what is Angular Dependency Injection and how to inject dependency into a  ComponentsDirectivesPipes, or Services using an example.

I hope this article helps you and you will like it.👍

If you have any doubt or confusion then free to ask in comment section.

Post a Comment

Previous Post Next Post