Understanding Angular Injection Tokens

Introduction

In Angular, dependency injection plays a pivotal role in managing dependencies and promoting modular, scalable code. One essential concept in Angular's dependency injection system is Injection Tokens. These tokens enable the developer to define and resolve dependencies in a flexible and decoupled manner. In this article, we will explore what Injection Tokens are, their significance, and their various use case.

Dependency Injection in Angular

Before diving into Injection Tokens, let's briefly understand the concept of dependency injection in Angular. Dependency injection is a design pattern that allows us to provide dependent objects to a class rather than having the class create them itself. It helps in writing more modular and testable code by promoting loose coupling between components.

In Angular, the dependency injection system is responsible for creating and managing instances of services, components, and other objects throughout the application. It automatically resolves and provides dependencies to the requesting components or services.

Injection Tokens in Angular

In Angular, an Injection Token is a unique identifier used by the dependency injection system to resolve dependencies. Unlike regular class dependencies, which are resolved by the type, Injection Tokens offer more flexibility and control over dependency resolution.

An Injection Token can represent any value or object, not just a class. It is a key that maps to a provider responsible for creating and providing the desired Dependency.

Use Cases of Injection Tokens


1. Custom Configuration

Injection Tokens are often used to provide custom configuration options for services or components. For example, consider a service that requires some application-specific settings. Rather than hard-coding these settings within the service, we can create an Injection Token and use it to provide the configuration values. This allows us to easily swap configurations based on different environments or user preferences.

// Define an Injection Token for the configuration
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');

// Provide the configuration using the Injection Token
@NgModule({
  providers: [
    { provide: APP_CONFIG, useValue: { apiUrl: 'https://api.example.com' } }
  ]
})
export class AppModule { }

2. External Libraries Integration

When integrating external libraries or services into an Angular application, Injection Tokens provide a way to abstract and decouple the dependencies. This allows for easier integration and the ability to switch between different library implementations without modifying the consuming code.

// Define an Injection Token for the external library
export const EXTERNAL_LIBRARY = new InjectionToken<ExternalLibrary>('external.library');

// Provide the library implementation using the Injection Token
@NgModule({
  providers: [
    { provide: EXTERNAL_LIBRARY, useClass: ThirdPartyLibrary }
  ]
})
export class AppModule { }

3. Multiple Implementations

Injection Tokens are particularly useful when dealing with multiple implementations of the same interface or abstract class. By using different Injection Tokens, you can ensure that the correct implementation is injected based on the context or configuration.

// Define Injection Tokens for different implementations
export const LOGGER_TOKEN = new InjectionToken<Logger>('logger');
export const FILE_LOGGER_TOKEN = new InjectionToken<Logger>('file.logger');
export const CONSOLE_LOGGER_TOKEN = new InjectionToken<Logger>('console.logger');

// Provide different implementations based on the Injection Token
@NgModule({
  providers: [
    { provide: LOGGER_TOKEN, useClass: FileLogger, multi: true },
    { provide: LOGGER_TOKEN, useClass: ConsoleLogger, multi: true },
    { provide: LOGGER_TOKEN, useClass: DefaultLogger, multi: true },
  ]
})
export class AppModule { }

Implementing Injection Tokens in Angular


1. Defining an Injection Token

To define an Injection Token in Angular, you can create an instance of the InjectionToken class, specifying the type of Dependency it represents.

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

// Define an Injection Token
export const MY_TOKEN = new InjectionToken<MyDependency>('my.token');

2. Providing a Dependency using an Injection Token

Once you have defined an Injection Token, you need to provide the corresponding Dependency using the token. This is typically done within the providers array of an Angular module.

import { NgModule } from '@angular/core';
import { MY_TOKEN } from './path/to/token';
import { MyDependency } from './path/to/dependency';

@NgModule({
  providers: [
    { provide: MY_TOKEN, useClass: MyDependency }
  ]
})
export class AppModule { }

In the above example, the MY_TOKEN Injection Token is provided with the MyDependency class as its implementation. Whenever a component or service requests MY_TOKEN, Angular will create an instance of MyDependency to fulfill the Dependency.

3. Injecting Dependency using an Injection Token

To inject a dependency using an Injection Token, you can specify the token as a parameter in the constructor of the component or service that requires the Dependency.

import { Component, Inject } from '@angular/core';
import { MY_TOKEN } from './path/to/token';
import { MyDependency } from './path/to/dependency';

@Component({
  selector: 'app-my-component',
  template: '<p>{{ data }}</p>'
})
export class MyComponent {
  constructor(@Inject(MY_TOKEN) private myDependency: MyDependency) {
    // Use myDependency in the component
    this.data = this.myDependency.getData();
  }
}

In the above example, the MyComponent class requests the MyDependency dependency using the MY_TOKEN Injection Token. Angular will automatically resolve the Dependency and inject an instance of MyDependency into the myDependency parameter.

With the new inject function available from 14 onwards, the Dependency can be injected outside the constructor.

import { Component, inject } from '@angular/core';
import { MY_TOKEN } from './path/to/token';
import { MyDependency } from './path/to/dependency';

@Component({
  selector: 'app-my-component',
  template: '<p>{{ data }}</p>'
})
export class MyComponent {
  myDependency = inject(MyDependency);
  constructor() {
    // Use myDependency in the component
    this.data = this.myDependency.getData();
  }
}

Conclusion

Injection Tokens are a powerful tool in Angular's dependency injection system. They provide flexibility and decoupling when defining and resolving dependencies. With Injection Tokens, you can handle custom configurations, integrate external libraries, and manage multiple implementations of interfaces or abstract classes with ease.


Similar Articles