In Angular, what does giving an InjectionToken a factory do, and how does an explicit provider for the same token interact with it?
answer
- a built-in default provider
- providedIn defaults to root
- zero arguments, inject() inside
- nearest explicit provider wins
basics
~20 sA factory makes an InjectionToken self-providing: it behaves as if registered in the root injector, so it never needs a providers entry and is tree-shakable. Any explicit provider found first on the lookup path overrides that default.
solid answer
~40 s`new InjectionToken<string>('API_BASE_URL', { factory: () => … })` attaches a default provider to the token itself. With a factory, `providedIn` defaults to `'root'`, so the root injector creates the value on first request and caches it, and nobody has to list the token in `providers`. The factory takes no arguments; it gets dependencies by calling `inject()`, for example `inject(DOCUMENT)` to derive a same-origin URL. Because the provider lives on the token, a token nobody injects adds no provider code to the app. An explicit provider still wins: lookup stops at the first injector that has a record for the token, so a route, a component or the bootstrap providers can supply `{ provide: API_BASE_URL, useValue: … }` and the factory is never called there.
code
ts · 28 linesimport { DOCUMENT, Injectable, InjectionToken, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Routes } from '@angular/router';
export const API_BASE_URL = new InjectionToken<string>('API_BASE_URL', {
// providedIn: 'root' is implied because a factory is given
factory: () => `${inject(DOCUMENT).location.origin}/api`,
});
@Injectable({ providedIn: 'root' })
export class ReportsApi {
private readonly http = inject(HttpClient);
private readonly baseUrl = inject(API_BASE_URL);
monthly() {
return this.http.get<unknown[]>(`${this.baseUrl}/reports/monthly`);
}
}
// Under /archive, a route-level ReportsApi instance reads the archive URL.
// (The root ReportsApi was created in the root injector and keeps the default.)
export const routes: Routes = [
{
path: 'archive',
providers: [ReportsApi, { provide: API_BASE_URL, useValue: 'https://archive.example.test/api' }],
loadComponent: () => import('./archive').then((m) => m.Archive),
},
];go deeper
Know that a token with a factory is available everywhere without a providers entry, like a providedIn root service.
Explain the root default, lazy creation, inject() inside the zero-argument factory, and that the nearest explicit provider wins.
Predict which value a root-level service sees when a route overrides the token, and keep factories SSR-safe by injecting DOCUMENT instead of reading globals.
Use factory defaults to make configuration opt-in, with overrides at deliberate levels, so most deployments need no configuration at all.
## Two kinds of InjectionToken An `InjectionToken` can be created in two ways: ```ts // 1. A bare key - someone must provide it export const API_BASE_URL = new InjectionToken<string>('API_BASE_URL'); // 2. A key with a built-in default export const API_BASE_URL = new InjectionToken<string>('API_BASE_URL', { factory: () => `${inject(DOCUMENT).location.origin}/api`, }); ``` The first is only a key. If nothing provides it, injecting it fails with the no-provider error NG0201. The second carries its own **factory**, a zero-argument function that produces a default value. Angular documents the effect as setting the token up "as if it was defined explicitly in the application's root injector". ## What the factory changes - **`providedIn` defaults to `'root'`.** With a factory and no `providedIn`, the token is scoped to the root injector. You may set `providedIn` explicitly; `'platform'` is also accepted, while `'any'` and an NgModule type are deprecated. - **No providers entry is needed.** Any code can `inject(API_BASE_URL)` and get the default. - **It is tree-shakable.** The provider definition is attached to the token object. If nothing references the token, bundlers can drop both the token and its factory; an entry in a `providers` array, by contrast, keeps its code referenced whether or not anyone injects it. - **The factory runs in an injection context.** It receives no parameters, so it obtains other services with `inject()`, such as `inject(DOCUMENT)` above. - **The value is created lazily and cached.** The root injector calls the factory the first time the token is requested and returns the same value afterwards. ## How explicit providers interact with the default Lookup always starts at the injector closest to the requester and stops at the **first injector that has a record** for the token. The factory only comes into play when the root injector is reached and has no explicit record of its own. That gives a clear precedence order: | Where an explicit provider exists | What a consumer below it gets | |---|---| | Nowhere | The factory's default, created in the root injector | | Bootstrap / application providers | The explicit value; the factory never runs | | A lazy route's `providers` | The route's value for anything that injects the token through the route's injector; the default elsewhere | | A component's `providers` | The component's value inside that subtree only | So a staging bootstrap can provide `{ provide: API_BASE_URL, useValue: 'https://staging.example.test/api' }`, one admin route can point at a different backend, and every other part of the app falls back to the same-origin default — all without a single consumer changing. ## Token default vs a root-provided service A token with a factory behaves much like a class marked `providedIn: 'root'`: both are created lazily by the root injector, both are tree-shakable, and both can be overridden by an explicit provider closer to the consumer. The difference is what they can hold. A root-provided class must be an instance of that class, while a token factory can return any `T` — a string, a frozen configuration object, a function, a browser API such as `localStorage`. Use a service when the dependency has behaviour and state of its own; use a token with a factory when it is a value that merely needs a sensible default. ## The API_BASE_URL scenario end to end 1. Declare the token with a factory that derives a sensible default from the current document's origin. 2. Consumers call `inject(API_BASE_URL)` and build request URLs from it. 3. Deployments that need a different backend add one explicit provider at the right level. 4. Tests provide a fixed value explicitly, so they never depend on the test runner's document origin. ## Pitfalls - **Browser globals in the factory.** A factory that reads `window` directly breaks under server-side rendering; injecting `DOCUMENT` keeps it platform-neutral. - **Expecting the factory to re-run.** It runs once per root injector. A value that changes at runtime needs a signal or an object with a getter, not a token default. - **Forgetting that an explicit root provider wins silently.** If a bootstrap provider for the token exists, editing the factory changes nothing; search for explicit providers before debugging the default. - **Using the factory for heavy work.** It runs on the first request, which may be in the middle of rendering; keep it cheap and synchronous.
- When should a token deliberately have no factory?When there is no safe default and a missing value should stop the app early — a tenant identifier, a licence key, a required backend URL in a multi-backend app. A bare token fails loudly with NG0201 the first time it is injected, while a factory default would let a misconfigured deployment run against the wrong backend.
- Can a token with a factory still be used with multi: true providers?A factory default is a single value, while a multi token resolves to an array assembled from explicit `multi: true` providers. Mixing the two ideas is confusing, so extension-point tokens normally have no factory and are typed as arrays; if you want an empty default, the consumer can inject the token optionally and fall back to an empty array.
saying these in an interview costs you the question
- A token with a factory must still be listed in a providers array
- The factory receives its dependencies as parameters
- The factory default beats an explicit provider in the bootstrap providers
- A token factory runs every time the token is injected
- Without providedIn the factory is ignored and the token is unprovided