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!