InjectionToken
Classes can be injected by their own identity, but interfaces, primitives, and config objects cannot - they have no runtime value to use as a key. InjectionToken<T> gives such a dependency a stable, unique, typed key.
import { InjectionToken } from '@remotex-labs/xinject';
const API_URL = new InjectionToken<string>('API_URL');The type parameter T records what the token resolves to, and the description string is used in error messages such as No provider for InjectionToken(API_URL).
Providing and injecting
Bind the token in providers, then request it with @Inject:
import { inject, Inject, Injectable, InjectionToken } from '@remotex-labs/xinject';
const API_URL = new InjectionToken<string>('API_URL');
@Injectable({ providers: [{ provide: API_URL, useValue: 'https://api.example.com' }] })
class ApiClient {
constructor(@Inject(API_URL) private url: string) {}
}
inject(ApiClient);Any provider form works with a token - useValue for a constant, useFactory to build it, or useClass to resolve it to a class.
Injecting an interface
An interface exists only at compile time, so bind a token to a concrete implementation and inject the token wherever the interface is needed:
interface Clock {
now(): number;
}
const CLOCK = new InjectionToken<Clock>('CLOCK');
@Injectable({ providers: [{ provide: CLOCK, useFactory: (): Clock => ({ now: () => Date.now() }) }] })
class Scheduler {
constructor(@Inject(CLOCK) private clock: Clock) {}
}Swapping the implementation - a fixed clock in tests, for example - is a one-line change to the provider, with no change to Scheduler.
Avoiding the allocation
new InjectionToken(...) is a top-level constructor call - a module-load side effect. When you would rather not allocate a token, two side-effect-free alternatives exist:
- A class is already its own token. Class dependencies need no
InjectionTokenat all - just@Injectthe class. - An
abstract classtokenises an interface. A class declaration executes nothing at import and is tree-shakeable, yet it carries a type - so it serves as both the interface and its key, with nonew:
abstract class Clock {
abstract now(): number;
}
@Injectable()
class SystemClock extends Clock {
now(): number { return Date.now(); }
}
@Injectable({ providers: [{ provide: Clock, useClass: SystemClock }] })
class Scheduler {
constructor(@Inject(Clock) private clock: Clock) {}
}String and symbol tokens
The lightest key of all is a plain string (or symbol) - a string literal allocates nothing and needs no module-level declaration:
@Injectable({ providers: [{ provide: 'API_URL', useValue: 'https://api.example.com' }] })
class ApiClient {
constructor(@Inject('API_URL') private url: string) {}
}
inject(ApiClient); // url === 'https://api.example.com'String and class tokens mix freely in one class - each parameter is resolved independently by its own token:
@Injectable()
class Logger {}
@Injectable({
providers: [
{ provide: 'SQL', useValue: 'SELECT * FROM users' },
{ provide: 'MAX_CONN', useValue: 10 }
]
})
class Database {
constructor(
@Inject(Logger) public logger: Logger, // class token -> self-injects
@Inject('SQL') public sql: string, // string token -> useValue
@Inject('MAX_CONN') public max: number // string token -> useValue
) {}
}A missing string token throws the same way a token does:
@Injectable()
class Service {
constructor(@Inject('MISSING') private value: string) {}
}
inject(Service); // throws: No provider for MISSINGThe trade-off: a string key carries no value type (the useValue type is not tied to the parameter), so name it uniquely. Because each class's providers is its own registry, a key only has to be unique within that one class - collisions stay local. When you want the value type carried along, use an InjectionToken<T> instead.
Reach for InjectionToken when the dependency is a primitive or a plain config object that has no natural class to stand in for it, and you want its value type carried on the token.
Missing providers
Unlike a class token, an InjectionToken cannot self-resolve. Requesting one that has no provider throws:
const MISSING = new InjectionToken<string>('MISSING');
@Injectable()
class Service {
constructor(@Inject(MISSING) private value: string) {}
}
inject(Service); // throws: No provider for InjectionToken(MISSING)