skip to content

In Angular, what do the provider recipes useClass, useValue, useFactory and useExisting each tell the injector to return?

level: juniorimportance: must knowfreq 72%

answer

  1. token on the left, recipe on the right
  2. bare class is a shorthand
  3. instantiate vs hand back as-is
  4. function result, cached
  5. alias forwards to another token

basics

~20 s

An Angular provider pairs a token with a recipe: useClass instantiates a class, useValue returns a fixed value untouched, useFactory returns what a function builds, and useExisting aliases another token. A bare class is shorthand for useClass of itself.

solid answer

~40 s

A provider is `{ provide: TOKEN, use…: … }`: `provide` is what consumers ask for, the `use…` key is how the injector produces it. `providers: [CartService]` is shorthand for `{ provide: CartService, useClass: CartService }`. `useClass` makes the injector instantiate a class, possibly a different one, and resolve that class's own dependencies — the usual way to swap `PaymentGateway` for `SandboxPaymentGateway` without touching a single consumer. `useValue` hands back an existing value by reference, which suits constants and configuration objects. `useFactory` calls a function, which can use `inject()` or a `deps` list, and returns its result. `useExisting` creates nothing: it forwards the request to another token, so both tokens yield the same object. In every case the injector resolves the recipe on the first request and reuses the result within that injector.

code

ts · 37 lines
ts
import { Injectable, inject } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { App } from './app';

export abstract class PaymentGateway {
  abstract charge(amountCents: number, cardToken: string): Promise<string>;
}

@Injectable()
export class CardPaymentGateway extends PaymentGateway {
  async charge(amountCents: number, cardToken: string): Promise<string> {
    return `live-${cardToken}-${amountCents}`; // would call the live processor
  }
}

@Injectable()
export class SandboxPaymentGateway extends PaymentGateway {
  async charge(amountCents: number): Promise<string> {
    return `sandbox-${amountCents}`;
  }
}

const useSandbox = !location.hostname.startsWith('shop.');

bootstrapApplication(App, {
  providers: [
    { provide: PaymentGateway, useClass: useSandbox ? SandboxPaymentGateway : CardPaymentGateway },
  ],
});

@Injectable({ providedIn: 'root' })
export class CheckoutService {
  private readonly gateway = inject(PaymentGateway); // unchanged by the swap
  pay(cents: number, token: string) {
    return this.gateway.charge(cents, token);
  }
}

go deeper

for a junior

Recall the four recipe keys, what each returns, and that providers: [X] is shorthand for useClass X. Be ready to show a useClass swap such as sandbox vs live payments.

for a middle

Explain that the injector resolves a recipe lazily and caches it per injector, that useValue returns a shared reference, and that useClass resolves the new class's own dependencies.

for a senior

Show judgement about which recipe keeps consumers ignorant of the choice, and spot the bugs: shared mutable useValue objects, useClass where an alias was intended, factories expected to re-run.

for a principal

Frame recipes as the seam for environment and tenant variation: one provider line per variation point, consumers depending only on tokens, and configuration kept out of component code.

## What a provider is In Angular's dependency injection (DI), an **injector** is a registry that answers the question "give me the thing registered under this key". The key is called a **token** — usually a class, an abstract class or an `InjectionToken`. A **provider** is the entry that tells the injector *how* to produce the value for a token. Every provider has two halves: - `provide` — the token consumers pass to `inject()` (or declare as a constructor parameter type). - a **recipe** key — `useClass`, `useValue`, `useFactory` or `useExisting` — that says how to build the value. A consumer never sees the recipe. It asks for `PaymentGateway` and receives whatever the nearest provider produces. That separation is what lets you change an implementation in one place. ## The forms at a glance | Form | What the injector does | Typical use | |---|---|---| | `providers: [X]` | Shorthand for `{ provide: X, useClass: X }` | A service without `providedIn` | | `useClass: Y` | Instantiates `Y`, resolving `Y`'s own dependencies | Swapping an implementation | | `useValue: v` | Returns `v` itself, by reference | Constants, configuration objects | | `useFactory: fn` | Calls `fn` and returns its result | Choices made at runtime | | `useExisting: Z` | Resolves token `Z` and returns that same instance | Aliasing, narrowing an API | ## Swapping an implementation: live vs sandbox payments The textbook case is an environment switch. Checkout code depends on an abstract `PaymentGateway`; production registers the live implementation and development or QA registers a sandbox one: ```ts providers: [ { provide: PaymentGateway, useClass: useSandbox ? SandboxPaymentGateway : CardPaymentGateway }, ] ``` Nothing that calls `inject(PaymentGateway)` changes. Because `useClass` instantiates the class through DI, `SandboxPaymentGateway` can itself `inject(HttpClient)` or any other service, and the injector satisfies those dependencies too. ## When each recipe fits 1. **Class shorthand** — the service is its own token and needs no configuration. This is the most common entry in a component's or route's `providers`. 2. **`useClass`** — consumers depend on one type, and you want a different subclass or implementation behind it: sandbox vs live, a decorated logger, a stub in a story or a demo. 3. **`useValue`** — the value already exists and needs no DI: `{ provide: RETRY_LIMIT, useValue: 3 }`, a frozen configuration object, a function. Angular does not instantiate it and does not inject into it; every consumer gets the exact same reference. 4. **`useFactory`** — the value depends on runtime information or on other services, such as picking a gateway from a feature flag. The factory runs in an injection context, so it can call `inject()`; older code lists dependencies positionally in `deps` instead. 5. **`useExisting`** — you want a second name for an instance that is already provided: exposing a concrete service under an abstract token, or keeping an old token working while callers migrate. No new instance is created. ## Caching and lifetime An injector does not build a value when the provider is registered. It records the recipe and resolves it **the first time** the token is requested from that injector, then returns the cached result on every later request. So a `useFactory` function runs once per injector that owns the provider, not once per consumer, and a `useClass` service is a single instance within that injector. A provider listed in a component's `providers` belongs to that component's element injector, so each component instance gets its own value, created on first request and discarded with the component. ## Common mistakes - Expecting `useValue` to copy the object. It does not; mutating it mutates it for everyone who injected it. - Using `useClass` where an alias was meant, which silently produces two instances with separate state (the difference from `useExisting` is its own interview question). - Thinking a swap needs edits at every call site. The point of the recipe is that only the provider changes. - Using `useValue` for something that needs injected collaborators; a hand-built instance gets no DI of its own, so use `useClass` or `useFactory`. The recipes are the vocabulary; where a provider is registered decides who sees it and how long it lives, which is the job of the injector hierarchy.

  • If the class named in useClass has its own dependencies, where does the injector find them?
    It treats that class like any other class it instantiates: the class's `inject()` calls or constructor parameters are resolved from the injector that holds the provider, walking up its ancestors. That is why `useClass: SandboxPaymentGateway` works even if the sandbox injects `HttpClient`, and why a dependency nobody provides surfaces as NG0201 the first time the gateway is requested.
  • Can useValue hold a function or an object you constructed yourself?
    Yes. `useValue` accepts any JavaScript value — a string, an object, a function, a hand-built instance — and returns that exact reference. Angular neither instantiates nor injects into it, so a hand-built instance gets no DI of its own. If the value needs collaborators, use `useClass` or `useFactory` instead.

saying these in an interview costs you the question

  • useValue gives each consumer its own copy of the object
  • useClass and useExisting are interchangeable ways to point at a class
  • A useFactory function runs every time the token is injected
  • Swapping an implementation means editing every inject() call site
  • The class shorthand only works for services marked providedIn root