DEV Community

GaurangDhorda
GaurangDhorda

Posted on • Updated on

How to use DI Token to provide different services instance at runtime for common interface inside angular web app

In this article we will understand use of DI Injection-Token. and We will use This DI token for common tasks and provide services instance at runtime.

You can support me

Alt Text

There are many times comes when we will design angular component, some time component is calling same methods but provide different result for each separate component. For example, Inside our angular application we have two components..

  1. ArtsDepartmentEployeeLists
  2. ScienceDepartmentEmployeeLists

Above, Both component displays lists of employees, and difference is first component displys arts employee while second component displays Science Employee list. Both Component have common interface api to display only list of employee and nothing else.

In this situation, First use-case of app is.. we will create one Container component and let container component decides what to render to user. whether it would be arts or science employee list.

Second Use-Case is let admin decides what to render inside whether it would be arts or science employee list and admin decides at runtime of application and switch between lists of employee. This is just a example to decide when to use this method.

To use Injection-Token based service providers inside angular application,

  1. we will first have to figure out common interface of component.
  2. we do not know when at runtime what will user picks.

Now, we have understood, some of the use-case for this mechanism, now we learn how this method is useful in angular.

InjectionToken(),

  Angular provide us way to extend Dependency Injection mechanism as per our requirement, or we will provide any kind to object or class or factories using InjectionToken inside component, and component injector looks for providers of Token and provide us same instance of InjectionToken. We first Have to create InjectionToken inside our application like this...
Enter fullscreen mode Exit fullscreen mode

employee-type-interface.ts

export type IEmployeeType = "art" | "science";
Enter fullscreen mode Exit fullscreen mode

employee-interface.ts

import { IEmployeeType } from "../Tokens/employee-type-interface";

export interface IEmployeeList {
  employeeType: IEmployeeType;
  list();
}
Enter fullscreen mode Exit fullscreen mode

In above IEmployeeList interface, we declared two members..

  1. employeeType : used to pass type of employee like art or science.
  2. list() : is method where api call for both component takes place.

Both component needs to have implements this IEmployeeList interface.

Now, we create InjectionToken..

list-token.ts

import { InjectionToken } from "@angular/core";
import { IEmployeeList } from "../Tokens/employee-interface";

export const EMPLOYEE_LIST: InjectionToken<IEmployeeList> = new InjectionToken<
  IEmployeeList
>("Token for Employee list");
Enter fullscreen mode Exit fullscreen mode

Above, we created EMPLOYEE_LIST injection token, this injection token has type of IEmployeeList and this token is for Employee list.

Before we provide injection token to app-module, first we will create two separate service for both art and science employee, and this both service implements our IEmployeeList interface and provide implementations.

art-employee-list.ts

import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { IEmployeeList } from "../Tokens/employee-interface";
import { IEmployeeType } from "../Tokens/employee-type-interface";

@Injectable()
export class ArtEmployeeService implements IEmployeeList {
  employeeType: IEmployeeType = "art";
  employeeData$ = of([
      { id: 1, employeeType: "art", employeeName: "Avenger" }
  ]);
  constructor(private http: HttpClient) {}
  list() {
    console.log("art-employee list is calling...");
  }
}
Enter fullscreen mode Exit fullscreen mode

science-employee-list.ts

import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { IEmployeeList } from "../Tokens/employee-interface";
import { IEmployeeType } from "../Tokens/employee-type-interface";

@Injectable()
export class ScienceEmployeeService implements IEmployeeList {
  employeeType: IEmployeeType = "science";
   employeeData$ = of([
     { id: 1, employeeType: "science", employeeName: "Harman" }
   ]);
  constructor(private http: HttpClient) {}
  list() {
    console.log("science-employee list is calling...");
  }
}
Enter fullscreen mode Exit fullscreen mode

Now we need to provide this token inside our app.module.ts

app.module.ts

@NgModule({
  imports: [BrowserModule, FormsModule, HttpClientModule],
  declarations: [AppComponent, HelloComponent],
  bootstrap: [AppComponent],
  providers: [
    { provide: EMPLOYEE_LIST, useClass: ArtEmployeeService, multi: true },
    { provide: EMPLOYEE_LIST, useClass: ScienceEmployeeService, multi: true }
  ]
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

In Above, app.module.ts file we provide our EMPLOYEE_LIST token inside providers array, with different implementation using useClass modifiers and we will tell angular that this token have multiple implementation using multi:true.

Now, all setups are ready to use inside our application, To implement this feature lets create one container-component..

container.component.ts

export class ContainerComponent implements OnInit {
  employeeList$;
  constructor(@Inject(EMPLOYEE_LIST) private employeeList: IEmployeeList[]) {}

  ngOnInit() {}
  onEmployeeList(typeOfEmployee: IEmployeeType) {
    const empList = this.employeeList.find(
      employeeType => employeeType.employeeType === typeOfEmployee
    );
    this.employeeList$ = empList.list();
  }
}
Enter fullscreen mode Exit fullscreen mode

First of all, inside constructor of ContainerComponent we inject our EMPLOYEE_LIST token, and type of this token is Array of IEmployeeList.

This container component has one method onEmployeeList() which decides which list of employee data to load inside component at runtime. lets we call this method from template of container component..

container.component.html

<button (click)="onEmployeeList('art')">Load art-employee data </button>
<hr />
<button (click)="onEmployeeList('science')"> Load science-employee </button>
<hr />
<ng-container *ngIf="employeeList$ | async |json as employeeList">
  {{employeeList}}
</ng-container>
Enter fullscreen mode Exit fullscreen mode

Inside template of html onClick of two button, we pass parameter to onEmployeeList() method, and then this method decides which injector of service to return back at runtime.

First this method finds class of employee list using fine() method, and by this instance we will call list() method respectivly. This list() method will return data based on button click whether its art or science...

At runtime we return instance of service..

There are many benefits of this pattern,

  1. Uniformity in pattern.. when we need another type of employee list to display then we just create own service and provide this service using our custome injection-token with useClass,

  2. we will provide data using angular Dependency Injection, so that our application code is well structured and maintable.

  3. use this pattern when you want to provide instance of service at run time.

You can support me

Alt Text

Top comments (0)