Unleashing the Power of InjectionTokens in Angular

Today, I'm thrilled to dive into the fascinating world of InjectionTokens in Angular. If you're not already familiar with them, InjectionTokens are superheroes in the Angular universe, allowing us to leverage the full potential of dependency injection with a hint of TypeScript magic. In this blog post, we'll explore how these powerful tokens will be used to handle small values and tackle complex data structures, unlocking a plethora of possibilities for your app.

Introduction to InjectionTokens

In Angular, dependency injection is the backbone of building scalable and maintainable applications. It enables us to provide the necessary dependencies to our components and services without hardcoding them, promoting flexibility and testability. Enter InjectionTokens, an elegant way to inject dependencies that don't fit the traditional mold of classes or interfaces. These tokens are unique markers used to identify and retrieve specific instances from the Angular injector.

Handling Small Values with InjectionTokens

  1. Primitive Values: Imagine needing to inject simple values like strings, numbers, or booleans into your components. With InjectionTokens, you can elegantly handle these small values. Let's say you have an app that displays a configurable welcome message based on user preferences. By using an InjectionToken like WELCOME_MESSAGE, you can easily inject the desired message wherever needed.
  2. Configurations and Settings: Many apps rely on configuration data, such as API endpoints, feature toggles, or theme settings. Instead of cluttering your code with hard-coded values, use InjectionTokens to centralize and manage these settings efficiently. You'll end up with cleaner and more maintainable code, making your future self (and fellow developers) send you virtual high-fives!

Tackling Complex Data Structures with InjectionTokens

  1. Language Localization: Handling language translations can be daunting in multilingual apps. InjectionTokens can come to the rescue by providing a clean and organized way to inject translation dictionaries into components and services. This approach ensures your app remains scalable as you add support for new languages or update existing translations.
  2. Custom Configurations and Service Options: Some services require various configuration options based on dynamic scenarios. Utilizing InjectionTokens, you can conveniently provide these custom configurations to services while keeping the codebase neat and highly adaptable. For instance, consider a data caching service that can be tuned for various caching durations, cache eviction policies, and storage mechanisms.
  3. Dynamic Themes and Styling: Are you building an app with support for multiple themes or customizable user interfaces? InjectionTokens enable you to inject theme-related data structures into components, giving you the power to modify styles and visual elements at runtime. Your users will love the seamless and delightful experience of personalizing their app's appearance!

Using InjectionTokens To Manage A Theme


Step 1: Define the InjectionToken

First, we'll create an InjectionToken to represent our app's theme configuration.

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

export interface ThemeConfig {
	primaryColor: string;
	secondaryColor: string;
	fontFamily: string;
}

export const THEME_CONFIG = new InjectionToken<ThemeConfig>('app.theme.config');
theme.tokens.ts

Step 2: Provide the Theme Configuration

Next, we must provide the theme configuration using the InjectionToken in the app's module or a feature module.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { THEME_CONFIG, ThemeConfig } from './theme.tokens';

const darkThemeConfig: ThemeConfig = {
	primaryColor: '#212121',
	secondaryColor: '#757575',
	fontFamily: 'Roboto, sans-serif',
};

@NgModule({
	declarations: [AppComponent],
	imports: [BrowserModule],
	providers: [
		{ provide: THEME_CONFIG, useValue: darkThemeConfig },
		// Other providers...
	],
	bootstrap: [AppComponent],
})
export class AppModule {}
app.module.ts

Step 3: Inject and Use the Theme Configuration

Now, we can inject the theme configuration using the InjectionToken in our components and services.

import { Component, Inject } from '@angular/core';
import { ThemeConfig, THEME_CONFIG } from './theme.tokens';

@Component({
	selector: 'app-theme',
	template: `
		<div [style.background]="themeConfig.primaryColor">
			<h1 [style.color]="themeConfig.secondaryColor">Welcome to My 				Awesome App
        	</h1>
		</div>
`,
})
export class ThemeComponent {
	constructor(@Inject(THEME_CONFIG) public themeConfig: ThemeConfig) {}
}
theme.component.ts

In this example, the ThemeComponent uses the injected themeConfig object to dynamically apply the primary and secondary colors to the background and text.

Step 4: Change the Theme Dynamically

The theme can be dynamically changed by updating the provided value of the THEME_CONFIG InjectionToken. For example, you could create a theme switcher component that allows users to select between different predefined themes:

import { Component, Inject } from '@angular/core';
import { ThemeConfig, THEME_CONFIG } from './theme.tokens';

@Component({
    selector: 'app-theme-switcher',
    template: `
    <select (change)="changeTheme($event.target.value)">
    <option value="dark">Dark Theme</option>
    <option value="light">Light Theme</option>
    </select>
    `,
})
export class ThemeSwitcherComponent {
    constructor(@Inject(THEME_CONFIG) private themeConfig: ThemeConfig) {}

    changeTheme(themeType: string) {
        if (themeType === 'dark') {
            this.themeConfig = {
                primaryColor: '#212121',
                secondaryColor: '#757575',
                fontFamily: 'Roboto, sans-serif',
    		};
    	} else if (themeType === 'light') {
            this.themeConfig = {
                primaryColor: '#f5f5f5',
                secondaryColor: '#424242',
                fontFamily: 'Arial, sans-serif',
            };
        }
    }
}
theme-switcher.component.ts


Remember that this approach doesn't change the app's styles directly but only updates the theme configuration dynamically. To apply the changes, you can use Angular's style binding and themeConfig properties as demonstrated in ThemeComponent.

Dependency Injection Trees

Understanding the DI tree is crucial when working with dependency injection in Angular, and InjectionTokens play a significant role in how standalone components and services read from various parts of the DI tree. When an Angular app is bootstrapped, it creates a hierarchical tree of injectors that form the DI tree. This tree represents the relationships between components, services, and other providers in the app.

A possible hierarchy where one child component overrides the parent's theme configuration, and the other child inherits the parent's theme configuration.

When a standalone component or service uses an InjectionToken, it searches for the associated provider starting from its own injector and traverses the DI tree until it finds a matching provider. If the provider is found at the same level or above the component or service's injector, the value is resolved and injected. This process continues until a matching provider is found, or the root injector is reached.

This mechanism allows standalone components and services to access and utilize shared instances of providers defined at higher levels of the DI tree. For example, if we use an InjectionToken to manage the app's theme configuration, as described in a previous section, the theme-related data can be provided at the AppModule level and used by various components and services throughout the app. This ensures consistency in the app's appearance while promoting code reusability and maintainability.

Developers can override dependencies at lower levels of the DI tree by leveraging properties like SkipSelf Host and Optional

  1. SkipSelf: The "SkipSelf" property is a powerful tool that allows developers to instruct Angular to skip the current injector and look for a provider in a higher-level injector. By default, when a component or service requests a dependency, Angular searches for it starting from its own injector and moves up the DI tree. However, there might be cases where you want to override a particular provider and use a different instance provided at a higher level. By setting "SkipSelf" to true, you can direct Angular to skip the current injector and find the desired provider in a parent injector. This enables fine-grained control over the injected dependencies and lets you tailor the behavior of specific components or services without affecting the entire app.
  2. Host: The "Host" property is another valuable aspect of dependency injection that comes into play when working with component hierarchies and content projection (Angular's "ng-content"). When a component has content projection, it can receive projected content from its parent component, and you might need to inject a service from the parent component into the projected content. By default, when you use "Host: false," Angular only looks for the provider in the immediate parent component's injector. However, setting "Host: true" allows Angular to search for the provider in the entire component hierarchy, starting from the current component's injector and moving up to the root injector. This enables seamless sharing of services between parent and projected components.
  3. Optional: The "Optional" property is handy when you have a provider that might not be available in the DI tree, and you don't want to raise an error when the dependency is missing. By setting "Optional: true," Angular will not throw an error if the provider is not found. Instead, it will inject "null" as the value for the dependency. This property can be useful in scenarios where certain services are optional, and the component or service gracefully handles the case when the dependency is not available.

Closing Thoughts

And there you have it! InjectionTokens are not just another feature in Angular; they are potent tools that can elevate your app development to a new level. Using them creatively allows you to easily manage small values, handle complex data structures, and create truly dynamic and flexible applications.

Happy coding! Until next time, keep building and stay curious! 🚀

Subscribe to The Full Stack Engineer

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
[email protected]
Subscribe