Skip to the solution
HardFrontend8 min read

What is an InjectionToken?

874 views

The problem

Question presented to candidate: "Our app needs an ApiConfig object (base URL, retry count) available everywhere, and ApiConfig is a TypeScript interface. Why can't we just inject the interface, and what is an InjectionToken?"

What a strong answer should cover:

  • DI looks values up by a runtime key; interfaces, type aliases and primitives are erased by TypeScript, so there is nothing to look up (the compiler reports NG2003 for an interface constructor parameter).
  • An InjectionToken<T> is a unique object used as that key, with a type parameter so inject(TOKEN) is typed as T; the string is only a description for error messages.
  • Provide a value for it with { provide: TOKEN, useValue | useFactory | useClass | useExisting }, or give it a default with new InjectionToken('x', { providedIn: 'root', factory: () => ... }), which is tree-shakable.
  • Token identity is by object, not by description: two tokens with the same text are different keys.
  • Common uses: config objects, feature flags, functions, multi: true extension points (HTTP_INTERCEPTORS, NG_VALUE_ACCESSOR), browser globals such as DOCUMENT.
  • Alternatives: an abstract class works as a token when you also want a contract with methods.

Clarifying questions expected:

  • "Should there be a sensible default, or must every app supply the config?"
  • "Will one part of the app need a different value?"

Code / implementation expected: Yes β€” a typed config token with a default, an override, and a multi token.

  • di
  • injection-token

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

8 min read Β· 10 sections

Read the code

AngularTyped config token with a default factory, a subtree override and a multi token
import { Component, InjectionToken, inject } from "@angular/core";

// An interface has no runtime value, so it cannot be a DI key by itself.
export interface ApiConfig {
  baseUrl: string;
  retries: number;
}

// The token IS the key. The type parameter makes inject() return ApiConfig.
export const API_CONFIG = new InjectionToken<ApiConfig>("API_CONFIG", {
  providedIn: "root",
  factory: () => ({ baseUrl: "https://api.example.com", retries: 3 }), // default if nobody provides one
});

// No factory: something MUST provide it, or injection fails.
export const FEATURE_FLAGS = new InjectionToken<string[]>("FEATURE_FLAGS");

// Same description, different object: a different token.
export const LOOKALIKE = new InjectionToken<string[]>("FEATURE_FLAGS");

@Component({
  selector: "app-admin",
  // Override the default for this subtree only.
  providers: [{ provide: API_CONFIG, useValue: { baseUrl: "https://admin.example.com", retries: 0 } }],
  template: `<p>admin sees {{ config.baseUrl }} (retries {{ config.retries }})</p>`,
})
export class Admin {
  config = inject(API_CONFIG);
}

@Component({
  selector: "app-root",
  imports: [Admin],
  providers: [
    { provide: FEATURE_FLAGS, useValue: "new-checkout", multi: true },
    { provide: FEATURE_FLAGS, useValue: "dark-mode", multi: true },
  ],
  template: `
    <p>root sees {{ config.baseUrl }} (retries {{ config.retries }})</p>
    <app-admin />
    <p>flags: {{ flags.join(", ") }}</p>
    <p>lookalike token: {{ lookalike }}</p>
    <p>token prints as: {{ tokenName }}</p>
  `,
})
export class AppComponent {
  config = inject(API_CONFIG);                        // typed as ApiConfig, no cast
  flags = inject(FEATURE_FLAGS);                      // multi: true collects every provider into an array
  lookalike = inject(LOOKALIKE, { optional: true }) ?? "not found (null)";
  tokenName = String(FEATURE_FLAGS);
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 91 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextHard

How does SSR hydration work in Angular β€” non-destructive hydration and mismatches?

Continue