Angular Dependency Injection In Depth- Custom Provider in Angular

Custom Provider in Angular

In Angular, services are an essential part of building applications. They provide a way to encapsulate and share data and functionality across multiple components. By default, Angular’s dependency injection mechanism provides the same instance of a service throughout the application. However, there are cases where we might need to have different instances of a service based on certain conditions or user roles.

In this blog post, we’ll explore how to implement custom providers in Angular to provide different instances of a service based on the user’s role. We’ll use a TodoService as an example service and provide a standard version for regular users and an enhanced version for administrators.

Step 1: Define an Injection Token
To differentiate between the standard and enhanced versions of the TodoService, we need to define an injection token. The injection token serves as a unique identifier for the service.

import { InjectionToken } from '@angular/core';

export const TODO_SERVICE = new InjectionToken<TodoServiceV1>('TodoServiceV1');

Here, we create an InjectionToken called TODO_SERVICE and associate it with the TodoServiceV1 type.

Step 2: Implement Custom Service Versions

Next, we need to create custom implementations of the TodoService for different user roles. For simplicity, let’s assume we have a StandardTodoService and an EnhancedTodoervice.

import { Injectable } from '@angular/core';
import { Todo } from '../models/todo.model';
import { TodoServiceV1 } from './todo.service-v1';


@Injectable({providedIn:'root'})
export class StandardTodoService extends TodoServiceV1 {

  override getAllTodos(): Todo[] {
    return [
      { id: 1, title: 'Standard task', completed: false }
    ]
  }

}

import { TodoServiceV1 } from "./todo.service-v1";
import { Todo } from "../models/todo.model";
import { Injectable } from "@angular/core";
@Injectable({providedIn:'root'})
export class EnhancedTodoService extends TodoServiceV1 {

  override getAllTodos(): Todo[] {
    return [
      { id: 1, title: 'Enhanced Task', completed: false }
    ]
  }
}


Here, we create two classes, StandardTodoService and EnhancedTodoService, which extend the TodoServiceV1 and override the getAllTodos method to provide different todo data based on the user’s role.

Step 3: Create a Custom Injector

To resolve the appropriate version of the TodoServiceV1 based on the user’s role, we’ll create a custom injector. The custom injector implements the Injector interface and provides the logic to return the desired instance of the service.

import { InjectionToken, Injector } from "@angular/core";
import { EnhancedTodoService } from "./enhanced-todo.service";
import { StandardTodoService } from "./standard-todo.service";
import { TodoServiceV1 } from "./todo.service-v1";

export const TODO_SERVICE = new InjectionToken<TodoServiceV1>('TodoServiceV1');
export class CustomTodoServiceInjector implements Injector { private userRole!: string; constructor() {} setUserRole(role: string): void { this.userRole = role; console.log(role); } get<T>(token: any, notFoundValue?: T): T { if (token === TASK_SERVICE) { console.log(this.userRole); // Provide the appropriate instance of the TaskService based on the user's role if (this.userRole === 'admin') { return new EnhancedTodoService() as unknown as T; } else { return new StandardTodoService() as unknown as T; } } return notFoundValue as T; } }

In the CustomTodoServiceInjector, we implement the Injector interface and provide the get method to return the appropriate instance of the TodoServiceV1 based on the user’s role. We use the TODO_SERVICE injection token to identify the service and provide the standard or enhanced version accordingly.

Step 4: Configure the Module

Now, let’s configure the module to use the custom injector and provide the appropriate instances of the TodoServiceV1.

import { Injector, NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { CustomTodoServiceInjector, TODO_SERVICE } from './services/custom-injector';
import { TodoServiceV1 } from './services/todo.service-v1';
import { TodoListComponent } from './todo-list/todo-list.component';


@NgModule({
  declarations: [
    AppComponent,
    TodoListComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [
    CustomTodoServiceInjector,
    {
      provide: TODO_SERVICE,
      useFactory: (injector: Injector) => {
        const customInjector = injector.get(CustomTodoServiceInjector);
        return customInjector.get<TodoServiceV1>(TODO_SERVICE);
      },
      deps: [Injector]
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
  constructor(private customInjector: CustomTodoServiceInjector) {
    const userRole = 'admin'; // Replace 'admin' with the appropriate user role
    this.customInjector.setUserRole(userRole); // Set the user role here
  }
}

In the module configuration, we import the CustomTodoServiceInjector and provide it as a provider. We also provide a factory function for the TODO_SERVICE token. Inside the factory function, we retrieve the CustomTodoServiceInjector instance and set the user role. Then, we use the customInjector.get() method to obtain the appropriate instance of the TodoServiceV1 based on the user’s role.

In the AppModule constructor, we set the user role again using the CustomTodoServiceInjector instance.

Step 5: Use the TodoServiceV1 in Components

Now, we can use the TodoServiceV1 in our components by injecting it using the TODO_SERVICE token.

import { Component, Inject, Injectable } from '@angular/core';
import { Todo } from '../models/todo.model';
import { TODO_SERVICE } from '../services/custom-injector';
import { TodoServiceV1 } from '../services/todo.service-v1';


@Component({
  selector: 'app-todo-list',
  template: `
    <ul>
      <li *ngFor="let todo of todos">
        {{ todo.title }}<button (cllick)="deleteTodo(todo.id)">Delete</button>
      </li>
    </ul>
  `,

})
export class TodoListComponent {
  todos: Todo[] = [];

  constructor(@Inject(TODO_SERVICE) private todoService: TodoServiceV1) {
    this.todos = this.todoService.getAllTodos();
  }
  deleteTodo(id: any) {

  }
}

In the TodoListComponent, we inject the TodoServiceV1 using the TODO_SERVICE token. Angular’s dependency injection mechanism will resolve the appropriate instance of the TodoServiceV1 based on the user’s role.

Conclusion

In this blog post, we explored the concept of custom providers in Angular. We learned how to use custom injectors and injection tokens to provide different instances of a service based on the user’s role. By leveraging custom providers, we can easily accommodate different requirements and provide specialized functionality to different users in our applications.

Remember to configure the custom injector, register the injection token, and use the token to inject the service in the components to make the custom provider setup work seamlessly.

I hope this blog post helps you understand and implement custom providers in Angular effectively. Happy coding!

Next Post Previous Post
No Comment
Add Comment
comment url