In Angular, how does a useFactory provider get its dependencies, and when does the injector actually call the factory?
answer
- positional deps array
- or inject() inside the function
- first request, then cached
- synchronous return value
basics
~20 sA useFactory function receives dependencies either positionally from its deps array or by calling inject() inside its body, since it runs in an injection context. The injector calls it lazily on the first request and caches the result for that injector.
solid answer
~40 sThere are two styles. The older one lists tokens in `deps`, and the injector resolves them and passes them to the factory in the same order: `useFactory: (http, flags) => …, deps: [HttpClient, FeatureFlags]`. The current one leaves `deps` out and calls `inject()` inside the factory, which is legal because provider factories run in an injection context. Either way, the injector calls the factory the first time the token is requested from the injector that owns the provider, caches the return value, and hands the same value to every later consumer of that injector — so a root-level factory runs once per application, while one in a component's `providers` runs once per component instance. The factory must return the value synchronously; if it returns a Promise, consumers receive the Promise.
code
ts · 38 linesimport { Injectable, inject, signal } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { App } from './app';
@Injectable({ providedIn: 'root' })
export class FeatureFlags {
private readonly flags = signal<Record<string, boolean>>({ 'sandbox-payments': true });
isOn(name: string): boolean {
return this.flags()[name] ?? false;
}
}
export abstract class PaymentGateway {
abstract charge(amountCents: number): string;
}
@Injectable({ providedIn: 'root' })
export class CardPaymentGateway extends PaymentGateway {
charge(amountCents: number) { return `live-${amountCents}`; }
}
@Injectable({ providedIn: 'root' })
export class SandboxPaymentGateway extends PaymentGateway {
charge(amountCents: number) { return `sandbox-${amountCents}`; }
}
bootstrapApplication(App, {
providers: [
{
provide: PaymentGateway,
// Runs once, on the first inject(PaymentGateway); the result is cached.
useFactory: () =>
inject(FeatureFlags).isOn('sandbox-payments')
? inject(SandboxPaymentGateway)
: inject(CardPaymentGateway),
},
],
});go deeper
Know that useFactory runs a function and injects its return value, and that inject() can be called inside it.
Explain deps as positional arguments versus inject() in the body, and that the result is cached per owning injector after the first request.
Catch the async-factory and stale-cached-value bugs, and decide whether runtime variation belongs in a factory or in an object that decides per call.
Keep factories pure and cheap; push startup side effects and asynchronous loading into explicit initialization so DI stays predictable across environments.
## What a factory provider is A **factory provider** registers a function instead of a class or a value: ```ts { provide: PaymentGateway, useFactory: pickGateway } ``` When a consumer asks for `PaymentGateway`, the **injector** (Angular's registry of providers) calls `pickGateway` and uses its return value. It is the recipe of choice when the value depends on information only available at runtime — a flag, the current hostname, another service's state — or when construction needs arguments DI cannot supply by itself, such as a plain class with a primitive in its constructor. ## Getting dependencies in: `deps` vs `inject()` | Style | How it looks | Notes | |---|---|---| | `deps` array | `useFactory: (flags, http) => …, deps: [FeatureFlags, HttpClient]` | Tokens are resolved and passed **by position**; parameter names and TypeScript types are irrelevant | | `inject()` in the body | `useFactory: () => inject(FeatureFlags).on('sandbox') ? … : …` | Works because a provider factory runs in an **injection context** | The `deps` style predates the `inject()` function and is still common in older code and libraries. An entry can be a plain token or an array that combines a token with lookup modifiers such as `new Optional()`. The `inject()` style reads more naturally, keeps types inferred, and lets the factory decide *which* dependencies to request. Both are resolved from the injector that holds the provider, walking up its ancestors. ## When the factory runs Registering a provider does not call the factory. The sequence is: 1. The injector records the provider when it is created. 2. The **first** time anything requests the token from that injector, the injector calls the factory and caches the return value. 3. Every later request to the same injector returns the cached value; the factory is not called again. The consequences follow from which injector owns the provider: - In `bootstrapApplication` providers or a route's providers, the factory runs at most once for that environment injector. - In a component's `providers`, each component instance has its own element injector, so the factory runs once per component instance that requests the token. - If nothing ever requests the token, the factory never runs. ## Scenario: choosing a gateway at runtime A checkout should use the sandbox gateway when a `FeatureFlags` service reports sandbox mode: ```ts { provide: PaymentGateway, useFactory: () => inject(FeatureFlags).isOn('sandbox-payments') ? inject(SandboxPaymentGateway) : inject(CardPaymentGateway), } ``` Because the result is cached, flipping the flag later does **not** swap the gateway already handed out. If the choice must follow a flag that changes while the app runs, return an object that consults the flag on every call, instead of expecting the factory to re-run. ## Factory or class provider? A factory is not automatically better than `useClass`. If the only variation is *which class* to instantiate, and that choice is known when the providers array is built, `useClass: cond ? A : B` is shorter and lets each class declare its own dependencies. Reach for `useFactory` when the decision needs injected services, when the value is not an instance of a DI-aware class (a function, a configured third-party object, a plain class with primitive constructor arguments), or when the factory must return an instance that another token already owns, as in the `inject(SandboxPaymentGateway)` example above. ## Pitfalls - **Async factories.** The injector does not await anything. An `async` factory yields a Promise, and that Promise is what consumers get. Worse, an `inject()` call after an `await` inside it runs outside the injection context and throws. Load asynchronous data before or after DI, not inside a provider factory. - **Positional mismatches.** Reordering `deps` without reordering the parameters silently swaps arguments of compatible shapes. - **Side effects.** A factory runs lazily, so side effects in it happen at an unpredictable moment — whenever the first consumer appears. Startup work belongs to the application's initializer hooks, not to a factory. - **Expecting per-consumer values.** Two consumers of the same injector share one result; a factory is not a "new instance per injection" switch. ## Summary Use `useFactory` when the value is computed. Prefer `inject()` inside the factory for new code, fall back to `deps` where you maintain older providers, and remember that the function runs once per owning injector, on first request, synchronously.
- When would you still write a deps array today?When maintaining providers written before `inject()` existed, or when the factory is a plain exported function you also call outside DI and want its dependencies as ordinary parameters. For new code, calling `inject()` inside the factory is simpler, type-safe and lets the factory request dependencies conditionally.
- Your factory reads a flag that can change at runtime, but the gateway never switches. Why, and what do you change?The injector calls the factory once per owning injector and caches the result, so later flag changes never reach it. Return a small delegating object whose methods consult the flag on each call — or expose the choice as a signal — instead of expecting DI to re-run the factory.
saying these in an interview costs you the question
- A useFactory function runs on every inject() call
- deps entries are matched to parameters by name or TypeScript type
- inject() cannot be used inside a provider factory
- The injector awaits a factory that returns a Promise
- Registering a factory provider calls the factory immediately at startup