In Angular, why would a provider reference a class through forwardRef(() => SomeClass), and what problem does it not solve?
answer
- declaration order in one file
- wrap the reference in a closure
- resolved when the provider is processed
- not a cure for injection cycles
basics
~20 sforwardRef wraps a class reference in a closure so DI metadata can mention a class not yet defined where that metadata is evaluated; Angular calls the closure later. It fixes declaration order, not a cycle where two services inject each other.
solid answer
~40 sA JavaScript class binding cannot be read before its definition runs, and Angular metadata such as `providers` is evaluated when the decorated class is defined. A provider that names a class declared further down the same file therefore reads a binding that does not exist yet. The documented second case is a component registering itself under a token, as in `{ provide: PAYMENT_FIELD, useExisting: forwardRef(() => CardNumberField) }`. `forwardRef(() => X)` defers the read: Angular tags the closure and calls `resolveForwardRef` when it processes the provider's token, recipe or `deps`, by which time the class exists. It does not help when service A injects B and B injects A; that is an instantiation cycle, and Angular still reports NG0200. Break those by restructuring, not by wrapping references.
code
ts · 30 linesimport { Component, Directive, InjectionToken, forwardRef, inject } from '@angular/core';
export interface PaymentField {
isComplete(): boolean;
}
export const PAYMENT_FIELD = new InjectionToken<PaymentField>('PAYMENT_FIELD');
@Component({
selector: 'app-card-number-field',
// The class is referenced inside its own decorator, so the reference is deferred.
providers: [{ provide: PAYMENT_FIELD, useExisting: forwardRef(() => CardNumberField) }],
template: `<input autocomplete="cc-number" (input)="value = $any($event.target).value" />`,
})
export class CardNumberField implements PaymentField {
value = '';
isComplete(): boolean {
return this.value.replace(/\s/g, '').length >= 12;
}
}
@Directive({
selector: '[appFieldStatus]',
host: { '[attr.data-complete]': 'field.isComplete()' },
})
export class FieldStatus {
// Used as <app-card-number-field appFieldStatus />: the component's providers are
// visible to directives on the same element, so this resolves the field instance.
protected readonly field = inject(PAYMENT_FIELD);
}go deeper
Recognise forwardRef as a wrapper that lets DI metadata mention a class defined later, and spot it in self-registering components.
Explain that the closure is resolved when Angular processes the provider, and why a self-reference inside a decorator needs deferring.
Distinguish declaration-order problems from instantiation cycles, and refuse forwardRef as a fix for NG0200.
Treat frequent forwardRef use as a smell of file layout or cyclic design, and steer the codebase toward one class per file and acyclic service graphs.
## The problem: reading a class before it exists A JavaScript `class` declaration creates its binding only when that line runs. Code evaluated earlier in the same module cannot use it: depending on how the code is emitted you get a `ReferenceError` or `undefined`. Angular metadata — the object passed to `@Component`, `@Directive` or `@Injectable`, including `providers` — is evaluated when the decorated class is defined. Two situations call for a deferred reference: 1. **Two classes in one file.** A class declared first lists a provider for a class declared later in the same file. When the first class's metadata is evaluated, the second class does not exist yet. 2. **Self-reference.** A component or directive that registers *itself* under a token names its own class inside its own decorator. The Angular guide notes that the decorator appears before the class definition and prescribes `forwardRef` for this case; wrapping the reference is safe however the compiler emits the metadata. The self-reference is the one you meet in real code: a form-field component that publishes itself under a shared token, a menu item that registers itself as the parent for nested items, or a component that plugs itself into a forms integration point. ## What `forwardRef` does `forwardRef(fn)` takes a zero-argument function that returns the class, tags it as a forward reference, and returns it. Angular's `resolveForwardRef(value)` checks for that tag: if present it calls the function and uses the result; otherwise it returns the value unchanged. The injector calls `resolveForwardRef` when it processes a provider — on the provider itself, on its `provide` token, on `useClass`, `useExisting` and `useValue`, and on each entry of a `deps` array. By then module evaluation has finished and the class binding exists. ```ts @Component({ selector: 'app-card-number-field', providers: [{ provide: PAYMENT_FIELD, useExisting: forwardRef(() => CardNumberField) }], template: `<input autocomplete="cc-number" />`, }) export class CardNumberField implements PaymentField { /* ... */ } ``` A parent payment form injects or queries `PAYMENT_FIELD` and gets the field component instance, without referencing the concrete class. ## What it does not do | Situation | Does `forwardRef` help? | What to do | |---|---|---| | Provider names a class defined later in the file | Yes | Wrap the reference | | Component registers itself under a token | Yes | Wrap the self-reference | | Service A injects B and B injects A | No — still NG0200 | Extract the shared part, or invert one dependency | | Classes in separate files | Normally not needed | An imported module has already run, unless the two files import each other circularly | A circular **instantiation** is a different problem. To build A the injector must first build B, which needs A, which is still being built. Deferring the *reference* changes nothing about that order, so Angular's circular dependency error (NG0200) remains. Fixes are structural: move the shared logic into a third service, pass data through a method call instead of a constructor-time dependency, or have one side request the other lazily at the moment it is needed. ## Practical guidance - Reach for `forwardRef` when the compiler or the runtime tells you a class is used before it is defined in DI metadata, and in the self-registration pattern shown above. - Do not sprinkle it everywhere "just in case"; it hides nothing when classes live in separate files, and it makes the metadata harder to read. - Keep one class per file where you can; most need for `forwardRef` disappears. - If you are wrapping references to silence a circular dependency error, stop: the design has a cycle that `forwardRef` cannot remove. ## Reading the error that sends you to it The symptom that usually leads here is a runtime failure while the module is being evaluated — a `ReferenceError` saying a class cannot be accessed before initialization, or a provider whose token or class is `undefined` so the injector reports an invalid provider. Both point at declaration order in one file. Moving the later class above its first use, or into its own file, is often cleaner than wrapping the reference. ## Where else it appears The same helper is accepted in other places where Angular reads a class lazily, such as standalone component `imports` when two components render each other. The mechanism is identical — a tagged closure resolved later — and so is the limit: it defers a read, it does not change what depends on what.
- Besides a provider's useExisting or useClass, where does Angular accept a forwardRef?Anywhere it reads the reference lazily: a provider's `provide` token, `useClass`, `useExisting`, `useValue`, each entry of a `deps` array, query tokens, and standalone component `imports` when two components render each other. In each place Angular calls `resolveForwardRef` at the moment it needs the value.
- How do you break a genuine cycle where two services inject each other?Restructure. Move the logic both need into a third service they each depend on, or pass the data one side needs as a method argument instead of a constructor-time dependency. If one side only needs the other occasionally, it can obtain it lazily at call time rather than during construction.
saying these in an interview costs you the question
- forwardRef fixes a circular dependency between two services
- forwardRef is required whenever a provider uses useExisting
- forwardRef is needed for any class imported from another file
- forwardRef creates a lazy proxy object instead of the real instance