AngularJSInjection Tokens

Injection Tokens

Angular's dependency injection works great with class types — you inject UserService and Angular knows what to create. But what if you want to inject a plain string, number, interface, or configuration object? Classes can't describe those.

Injection Tokens solve this problem. They are special objects that serve as unique keys in the DI system, allowing you to provide and inject any value — not just class instances.

The Problem Without Tokens

TS
// TypeScript interfaces are erased at runtime — you can't use them as DI tokens
export interface ApiConfig {
  baseUrl: string;
  timeout: number;
}

// This will NOT work — interfaces don't exist at runtime
@Injectable({ providedIn: 'root' })
export class ApiService {
  constructor(private config: ApiConfig) {} // ERROR: No provider for ApiConfig
}
Creating an InjectionToken

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

// Generic type parameter makes it fully type-safe
export const API_CONFIG = new InjectionToken<ApiConfig>('ApiConfig');

// The string is a description for debugging — it shows up in error messages
// It does NOT have to be unique, but best practice is to make it descriptive
Common Use Cases

1. Application Configuration

TS
// tokens.ts — define all tokens in one place
import { InjectionToken } from '@angular/core';

export interface AppConfig {
  apiUrl: string;
  appName: string;
  maxUploadSizeMb: number;
  supportedLocales: string[];
}

export const APP_CONFIG = new InjectionToken<AppConfig>('AppConfig');
export const API_URL = new InjectionToken<string>('ApiUrl');
export const MAX_RETRY = new InjectionToken<number>('MaxRetry');

TS
// main.ts — provide values at bootstrap
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { APP_CONFIG, API_URL, MAX_RETRY } from './app/tokens';

bootstrapApplication(AppComponent, {
  providers: [
    {
      provide: APP_CONFIG,
      useValue: {
        apiUrl: 'https://api.myapp.com',
        appName: 'MyApp',
        maxUploadSizeMb: 50,
        supportedLocales: ['en', 'de', 'fr'],
      },
    },
    { provide: API_URL, useValue: 'https://api.myapp.com' },
    { provide: MAX_RETRY, useValue: 3 },
  ],
});

TS
// Inject using inject() function
import { inject, Injectable } from '@angular/core';
import { APP_CONFIG, API_URL } from './tokens';

@Injectable({ providedIn: 'root' })
export class HttpService {
  private apiUrl = inject(API_URL);
  private config = inject(APP_CONFIG);

  uploadFile(file: File): Observable<void> {
    if (file.size > this.config.maxUploadSizeMb * 1024 * 1024) {
      throw new Error(`File exceeds ${this.config.maxUploadSizeMb}MB limit`);
    }
    return this.http.post<void>(`${this.apiUrl}/upload`, file);
  }
}

2. Feature Flags

TS
export interface FeatureFlags {
  darkMode: boolean;
  betaAnalytics: boolean;
  aiAssistant: boolean;
}

export const FEATURE_FLAGS = new InjectionToken<FeatureFlags>('FeatureFlags');

// In app config
providers: [
  {
    provide: FEATURE_FLAGS,
    useValue: {
      darkMode: true,
      betaAnalytics: false,
      aiAssistant: environment.production ? false : true,
    },
  },
]

// In a component
@Component({ selector: 'app-header' })
export class HeaderComponent {
  private flags = inject(FEATURE_FLAGS);

  showAiButton = this.flags.aiAssistant;
}

3. Tokens with Default Values

You can provide a default factory directly on the token itself using the second argument. This means the token works even without an explicit provider:

TS
export const THEME = new InjectionToken<'light' | 'dark'>('Theme', {
  providedIn: 'root',
  factory: () => {
    // Default: detect from OS preference
    return window.matchMedia('(prefers-color-scheme: dark)').matches
      ? 'dark'
      : 'light';
  },
});

// No provider needed — the factory runs automatically
@Component({ selector: 'app-root' })
export class AppComponent {
  theme = inject(THEME); // 'light' or 'dark' from OS preference
}
Tip
Providing a factory in the token definition makes it self-contained. Override it in tests or for specific environments by providing a different value explicitly.

4. Injecting Functions

TS
// Inject a logging function rather than a whole service
type LogFn = (message: string, level?: 'info' | 'warn' | 'error') => void;

export const LOGGER = new InjectionToken<LogFn>('Logger', {
  providedIn: 'root',
  factory: () => (message: string, level = 'info') => {
    console[level](`[${level.toUpperCase()}] ${message}`);
  },
});

// Usage
@Injectable({ providedIn: 'root' })
export class OrderService {
  private log = inject(LOGGER);

  placeOrder(order: Order) {
    this.log(`Placing order ${order.id}`, 'info');
  }
}

5. Platform and Environment Tokens

TS
import { DOCUMENT, PLATFORM_ID, isPlatformBrowser } from '@angular/common';
import { inject } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class BrowserService {
  private platformId = inject(PLATFORM_ID);
  private document = inject(DOCUMENT);

  isBrowser(): boolean {
    return isPlatformBrowser(this.platformId);
  }

  getTitle(): string {
    return this.isBrowser() ? this.document.title : '';
  }
}

Built-in Angular tokens you can inject:

Token

Package

Provides

DOCUMENT

@angular/common

The DOM document object

PLATFORM_ID

@angular/core

String: 'browser', 'server', or 'worker'

APP_ID

@angular/core

Application ID string

LOCALE_ID

@angular/core

Current locale string (e.g. "en-US")

ENVIRONMENT_INITIALIZER

@angular/core

Runs initialization code

APP_INITIALIZER

@angular/core

Runs before app renders

BASE_HREF

@angular/common

The base href from index.html

Using @Inject Decorator

When using constructor injection (rather than inject()), use the @Inject decorator to specify the token:

TS
import { Inject, Injectable } from '@angular/core';
import { API_URL, MAX_RETRY } from './tokens';

@Injectable({ providedIn: 'root' })
export class RetryHttpService {
  constructor(
    @Inject(API_URL) private apiUrl: string,
    @Inject(MAX_RETRY) private maxRetry: number,
    private http: HttpClient,
  ) {}
}
Tokens in Testing

Injection tokens make testing straightforward — override any configuration value without touching production code:

TS
describe('HttpService', () => {
  let service: HttpService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        HttpService,
        // Override the API URL for tests
        { provide: API_URL, useValue: 'http://localhost:3000' },
        { provide: MAX_RETRY, useValue: 1 },
        {
          provide: FEATURE_FLAGS,
          useValue: { darkMode: false, betaAnalytics: true, aiAssistant: false },
        },
      ],
    });

    service = TestBed.inject(HttpService);
  });

  it('should use the test API URL', () => {
    // service uses localhost:3000 in tests
  });
});
Best Practices
  • Always use InjectionToken for non-class values (strings, numbers, interfaces, objects)

  • Define all tokens in a single tokens.ts file at the feature or app level for discoverability

  • Give tokens descriptive string descriptions — they appear in error messages and DevTools

  • Use the generic type parameter to get compile-time type safety: new InjectionToken<MyInterface>(...)

  • Prefer token factories (second argument) for tokens with sensible defaults — avoids mandatory providers

  • Use @Optional with inject() when a token might not be provided: inject(TOKEN, { optional: true })

Warning
Never use plain strings as DI tokens (the pre-Angular 2 pattern). Strings can collide across libraries. InjectionToken objects are unique by reference and completely safe.
Summary

InjectionToken unlocks the full power of Angular's DI system for non-class values:

  • Create with new InjectionToken<T>('description')
  • Provide with { provide: MY_TOKEN, useValue: ... } or factory
  • Inject with inject(MY_TOKEN) or @Inject(MY_TOKEN) in constructors
  • Supports default factory, optional injection, and multi-providers
  • Perfect for config objects, feature flags, environment values, and platform APIs

With tokens mastered, you have the full DI toolbox at your disposal.