Angular Injection Token use cases

Angular Injection Token use cases
Photo by Ivan Diaz / Unsplash

In this post we'll explore Angular Injection Tokens, what they are and why you might want to use them.

Understanding Injection Tokens will be useful to help you gain a deeper understanding of Angular Dependency Injection.  That said, you shouldn't use Injection Tokens unless they are solving a real issue in your application that can't be solved with a simpler solution.

What are Injection Tokens?

Injection Tokens are used with Angular Dependency Injection (DI) and hence we must understand a bit about Angular Dependency Injection.  Dependency Injection is a fancy term for a relatively simple concept.  You have some things (dependencies), which are stored somewhere (dependency pool) and you want something to give them to you when you need them (the Injector).  Let's use a simple analogy to try and clarify what Dependency Injection is:

Lets say you have gone to a fancy restaurant with valet parking.  You hand your keys to a valet and they give you a ticket.  When you want your car back you hand your ticket to the valet and they return your car to you.

Your valet ticket is the Token that identifies the dependency you want, your car.

The restaurant car park contains all the cars, it is the dependency pool.

The valet is the Injector.  You give the valet your ticket and the valet gets your car, you don't get multiple cars, a new car or cars belonging to other diners (hopefully).

An Angular Injection Token is no different.  You create an Injection Token by calling new InjectionToken('some description string'), this returns a reference to your token.  When you want to use the token you can use @Inject or the Injector to get it.

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  constructor(
    @Inject(MY_TOKEN) private myToken: string
  ) {
    console.log("Injected myToken", myToken);
  }
}
Use @Inject to get token
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  constructor(
    private injector: Injector
  ) {
    const myToken = this.injector.get(MY_TOKEN);
  }
}
Use Injector.get() to access token

To make Angular aware of the dependency associated with the token you must add it as a provider in your application.  This step is critical as if you don't do it you'll get an error as Angular doesn't know what to do.  It's like the valet giving you a ticket and then forgetting to park your car or taking if for a spin!!

Creating a Simple Injection Token

Create a file called tokens.ts and add the following line to declare your first Injection Token:

export const MY_TOKEN = new InjectionToken<string>('myToken');

We now have MY_TOKEN Injection Token created.  The 'myToken' can be anything it's just there as a description of  the token.  To use this Injection Token, we need to add it to the constructor of a Service or Component.

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  constructor(
    @Inject(MY_TOKEN) private myToken: string
  ) {
    console.log("Injected myToken", myToken);
  }
}

If you run ng serve you should see an error:

This is because we have created an Injection Token, so we have our valet ticket but when the valet tries to find our car it's not there.  We are using it before Angular knows what it is and how to find it.  We need to let Angular know what it needs to provide for this Injection Token.  We can do that in one of three ways:

  • by adding a factory function on the Injection Token definition (Will be available on Root Module Injector).
  • by adding it to the providers array of the module that will use the Injection Token (Will be available on Root Module Injector)
  • by adding it to the providers array of the component that will use the Injection Token (Will be available on Element Injector)

An example of each is shown below.  Which too choose depends on several factors, which are beyond the scope of this post, if in doubt use the factory function on the provider itself.

// tokens.ts
export const MY_TOKEN = new InjectionToken<string>('MyToken', {
  factory: () => 'Token Value'
});
Add factory function to InjectionToken definition

factory is a function that has a return type of unknown, the type will be inferred once you provide a return value.  In the above example the type is inferred as string but we could return an Array, Object or Class.

// app.module.ts
@NgModule({
  ..  
  providers: 
	[{
		provide: MY_TOKEN,
		useValue: 'Token Value'
	}]
})
Add token to list of providers on module.

// app.component.ts
@Component({
  ..  
  providers: 
	[{
		provide: MY_TOKEN,
		useValue: 'Token Value'
	}]
})
Add token to list of providers on component.

Now the error should be gone as we have told Angular about our Token.  Notice we have linked MY_TOKEN with a string value.  We now have our first working token being injected into our application.  However, injecting a string value may not seem very useful, as we could do the same by using an environment file and a lot less code.  We'll explore other options below.  

There is another optional parameter that can be used when creating an Injection Token providedIn, this will default to root if not explicitly added.  The other options are any or platform.
// tokens.ts
export const MY_TOKEN = new InjectionToken<string>('MyToken', {
  providedIn: 'root',
  factory: () => 'Token Value'
});
Add providedIn: 'root'

When adding a token to the module or component providers array there are 4 options:

  • useValue
  • useFactory
  • useClass
  • useExisting

useValue

We have already used useValue to provide a string.  We could also provide an array or an object using useValue.  

useClass

Not much mystery here, we can use our Injection Token to provide a class instance.  As the token is provided on the app.module.ts there will only be one instance of the class created, no matter how many times it is injected.  Also, note that we don't need to specify useClass if it is a class being provided, it will be inferred.

export class Token {
	constructor() {
    	  console.log("Token Class");
        }
}
// app.module.ts
@NgModule({
  ..  
  providers: 
	[{
		provide: MY_TOKEN,
		useClass: Token
	}]
})

// Sugur syntax when using Class
// app.module.ts
@NgModule({
  ..  
  providers: [MY_TOKEN]
})

useFactory

This is where things get interesting.  You can provide a factory function that will determine what needs to be returned based on the context of the request.

export function TokenFactory() {
	// perform config lookup and return based on context
}
// app.module.ts
@NgModule({
  ..  
  providers: 
	[{
		provide: MY_TOKEN,
		useFactory: TokenFactory
	}]
})

You may be wondering how this works with services.  Any services that are annotated with @Injectable are already provided and available for use in your component, directives or pipes so there is no need to create Injection Tokens for them.

useExisting

With useExisting angular returns the item associated with the Aliased Token, MY_TOKEN_ALIAS.  If the aliased token does not exist you will get an error.  

// app.module.ts
@NgModule({
  ..  
  providers: 
	[{
		provide: MY_TOKEN_ALIAS,
		useExisting: MY_TOKEN
	}]
})

useExisting can be used when a token has already been resolved and you want to provide a narrower API surface to the one exposed by the existing provider, which will typically be a service.  For large applications with many shared services this may prove useful where a child component only needs a subset of a services functionality.

Use BehaviorSubject with InjectionToken

An interesting use of Injection Tokens would be to use a BehaviorSubject to communicate between components.

export const MY_SUBJECT_TOKEN = new InjectionToken<BehaviorSubject>('mySubjectToken');
InjectionToken that creates an instance of a Subject.
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [{
    provide: MY_SUBJECT_TOKEN,
  	useValue: new BehaviorSubject([])
  }]
})
export class AppComponent {
  constructor(
    @Inject(MY_TOKEN) private myToken: BehaviorSubject<string>
  ) {
    myToken.next("from AppComponent");
    console.log("Injected myToken", myToken);
  }
}
AppComponent emits on token.
@Component({
  selector: 'app-admin',
  templateUrl: './admin.component.html',
  styleUrls: ['./admin.component.css']
})
export class AdminComponent {
  constructor(
    @Inject(MY_TOKEN) private myToken: BehaviorSubject<string>
  ) {
    myToken.subscribe((data) => {
      console.log("AdminComponent", data);
    });
  }
}
AdminComponent subscirbes on token

A note on types when using InjectionToken  

When using @Inject(SomeToken) the type is not inferred.  So if you define an InjectionToken that returns a number and add an incorrect type in the constructor you won't get an error.  If you use the component Injector to get the token the type will be inferred correctly.

export const MY_TOKEN = new InjectionToken<string>('MyToken', {
  factory: () => 'Some Value'
});

constructor(
    // no type error even though token is a string
    @Inject(MY_TOKEN) token: number
  ) {
    
  }

constructor(
    private injector: Injector,
  ) {
    // is inferred correctly as string
    const token = injector.get(MY_TOKEN);
  }

Wrap Up

Hopefully this has helped demystify Injection Tokens.  They are a useful tool in the tool belt but should be used sparingly and only where they are adding value and not increasing the complexity unnecessarily.

Resources

How to update dependency injection token value
Angular dependency injection let you inject a string, function, or object using a token instead of a service class. I declare it in my module like this: providers: [{ provide: MyValueToken, useV...
useClass vs useExisting
When should we use useExisting provider instead of useClass? providers: [{provide: Class1, useClass: Class1}, {provide: Class2, useExisting: Class2}] REMARK: I have not found an exact question ...

https://angular.io/guide/dependency-injection-providers