skip to content

In Angular, what is the difference between { provide: A, useExisting: B } and { provide: A, useClass: B } when B is also provided?

level: middleimportance: should knowfreq 58%

answer

  1. count the instances
  2. alias vs second construction
  3. separate state behind two tokens
  4. resolved from the alias's injector

basics

~20 s

With useExisting, token A is an alias: injecting A resolves B and returns B's instance, so both tokens share one object. With useClass, the injector builds a second, independent B instance for A, so the two tokens hold separate state.

solid answer

~50 s

`useExisting` creates nothing: its recipe is effectively `inject(B)`, so `A` and `B` resolve to the same instance and share state. `useClass: B` tells the injector to construct `B` again for token `A`; the injector keys its cache by token, so the instance already built for `B` is not reused and you end up with two objects that look identical but hold different data. The bug shows up as "I updated the service and the other component didn't see it" — for example a `SandboxPaymentGateway` whose recorded test charges appear under one token and not the other. Use `useExisting` when you want a second name for one service — exposing a concrete class under an abstract token, or keeping an old token alive during a rename — and `useClass` only when you genuinely want a separate instance.

code

ts · 32 lines
ts
import { Component, Injectable, inject } from '@angular/core';

export abstract class PaymentGateway {
  abstract charge(amountCents: number): string;
}

@Injectable()
export class SandboxPaymentGateway extends PaymentGateway {
  readonly recorded: number[] = [];
  charge(amountCents: number): string {
    this.recorded.push(amountCents);
    return `sandbox-${amountCents}`;
  }
}

@Component({
  selector: 'app-checkout-shell',
  providers: [
    SandboxPaymentGateway,
    { provide: PaymentGateway, useExisting: SandboxPaymentGateway },
  ],
  template: `<button (click)="pay()">Pay</button> Recorded: {{ sandbox.recorded.length }}`,
})
export class CheckoutShell {
  private readonly gateway = inject(PaymentGateway);
  protected readonly sandbox = inject(SandboxPaymentGateway);

  pay(): void {
    this.gateway.charge(1999);
    console.log(this.gateway === this.sandbox); // true with useExisting, false with useClass
  }
}

go deeper

for a junior

Remember the one-line rule: useExisting shares one instance under two tokens, useClass builds another instance.

for a middle

Explain that the injector caches by token, so useClass under a new token constructs again, while useExisting's recipe is just a lookup of the other token.

for a senior

Diagnose the lost-state symptom from a provider list, and know that an alias resolves from the injector that holds it, so overrides lower in the tree do not affect it.

for a principal

Use aliases deliberately for migrations and API narrowing, with a deprecation path, rather than letting duplicate instances creep in through copied provider arrays.

## Two recipes that look alike Both recipes mention a class on the right-hand side, which is why they are easily confused: ```ts providers: [ SandboxPaymentGateway, { provide: PaymentGateway, useExisting: SandboxPaymentGateway }, // alias // { provide: PaymentGateway, useClass: SandboxPaymentGateway }, // second instance ] ``` An **injector** caches one value per **token** (the key a consumer asks for). The recipe decides how that value is produced the first time it is requested. - **`useClass: B`** — construct a new `B`, resolving `B`'s dependencies, and cache it under `A`. - **`useExisting: B`** — do not construct anything; ask the injector for token `B` and cache *that* result under `A`. ## What each produces | | `useExisting: B` | `useClass: B` | |---|---|---| | Instances when both `A` and `B` are injected | one | two | | State shared between `inject(A)` and `inject(B)` | yes | no | | Requires a provider for `B` | yes — otherwise NG0201 | no — `B` is instantiated directly | | Typical intent | a second name for one service | a separate object of that class | In Angular's source the difference is visible in how the injector turns a provider into a factory: an existing-provider's factory is a call to inject the other token, while a class provider's factory calls `new` on the class. Nothing links the instance built for `A` to the one built for `B` in the `useClass` case, because the cache is keyed by token, not by class. ## The bug in practice Suppose a QA tool panel injects `SandboxPaymentGateway` directly to list the fake charges it recorded, while checkout injects the abstract `PaymentGateway`. With `useClass`, checkout charges go into one instance's list and the panel reads another's, so the panel stays empty. Nothing throws; the app simply behaves as if the service "lost" its data. Switching the second entry to `useExisting` makes both tokens resolve to the same object and the panel fills up. The same trap appears when a service is aliased for a rename: `{ provide: LegacyCartApi, useClass: CartApi }` quietly gives old callers a second cart, while `useExisting` keeps one cart with two names. ## Where the alias is resolved An alias is resolved from the injector that **holds the alias provider**, walking up from there — not from the component that asked. Two consequences: 1. If `B` is provided nowhere visible from that injector, injecting `A` fails with the no-provider error NG0201, reached through the alias. 2. If a descendant component overrides `B` in its own `providers`, an alias registered higher up still resolves the *higher* `B`; the override is invisible to it. Register the alias next to the `B` it should follow. ## When to use which - Use **`useExisting`** to expose one concrete service under an abstract class or token, to give a service a second name during a migration, or to let a component or directive publish *itself* under a shared token (often written with `forwardRef`). - Use **`useClass`** to substitute an implementation (sandbox for live) or when you genuinely want an independent instance of the same class under a different token. - If you are unsure, ask "should writing through one token be visible through the other?" If yes, it is `useExisting`. ## Narrowing an API with an alias A quieter use of `useExisting` is **narrowing**. A concrete `SandboxPaymentGateway` may expose test-only methods such as `reset()` or `recorded`. Production code should see only the abstract `PaymentGateway` contract. Registering the concrete class once and aliasing the abstract token to it gives both audiences what they need from one object: QA tooling injects the concrete class and sees everything, checkout code injects the abstract token and sees only `charge()`. Because there is one instance, whatever the tooling inspects is exactly what checkout used. With `useClass` the tooling would inspect an object checkout never touched. ## Checklist for a review - Two tokens backed by the same class with `useClass` on both is almost always a bug. - An alias whose target is missing fails only when first injected, so cover it with a test that injects the alias. - `useExisting` pointing at its own token loops; Angular reports a circular dependency (NG0200). - When a service is renamed, an alias from the old token to the new class keeps old callers on the same instance while they migrate; delete the alias once nothing injects the old token.

  • What happens if you write { provide: PaymentGateway, useExisting: PaymentGateway }?
    The alias asks the injector for the very token it is resolving, so resolution re-enters itself. Angular detects the loop and throws its circular-dependency error, NG0200. An alias must always point at a different token that has its own provider.
  • Can the target of a useExisting alias live in a parent injector rather than next to the alias?
    Yes. The forwarded lookup starts at the injector that holds the alias and walks up its ancestors like any other lookup, so an alias in a route or component injector can point at a root-provided service. It resolves the first provider found on that path, not the one the requesting component would find from its own position.

saying these in an interview costs you the question

  • useExisting creates a new instance of the target class for the alias
  • useClass reuses the instance if the class is already provided in the same injector
  • An alias follows whichever B the requesting component sees
  • Two tokens with useClass of the same class always share state
  • A missing useExisting target is reported when the injector is created