In Angular, what does inject(Token, {optional: true}) return when no injector provides the token, and how does that differ from a plain inject() call?
answer
- missing provider, two outcomes
- null instead of an error
- return type widens with the option
- search path unchanged
- decorator form on constructors
basics
~20 sWith optional: true, Angular returns null when the whole search finds no provider; a plain inject() throws NG0201 instead. The option changes only the not-found outcome, not where Angular searches, and its constructor equivalent is @Optional().
solid answer
~40 sBy default a lookup that reaches the top of the injector walk without a match throws `NG0201: No provider found for ...`. Passing `{optional: true}` makes that same miss return `null`, and TypeScript types the result as `T | null`, so the caller must handle the absent case (`this.sink?.track(...)`). It does not change **where** Angular searches: element injectors, then environment injectors, as usual — and a provider that exists but whose factory throws still throws. The constructor-parameter equivalent is `@Optional()`. Use it for genuinely optional collaborators — a plugin, an analytics sink, a parent that may not exist — and combine it with `self`, `skipSelf` or `host`, which make misses more likely. Do not use it to silence a required dependency: you trade a clear NG0201 at creation for a null crash later.
code
ts · 21 linesimport {Component, InjectionToken, inject, signal} from '@angular/core';
export interface AnalyticsSink {
track(event: string): void;
}
export const ANALYTICS_SINK = new InjectionToken<AnalyticsSink>('ANALYTICS_SINK');
@Component({
selector: 'app-accordion-panel',
template: `<button (click)="toggle()">{{ open() ? 'Hide' : 'Show' }}</button>`,
})
export class AccordionPanel {
readonly open = signal(false);
// AnalyticsSink | null: null when the app did not provide a sink
private sink = inject(ANALYTICS_SINK, {optional: true});
toggle() {
this.open.update((v) => !v);
this.sink?.track('accordion-toggle');
}
}go deeper
Recall that optional injection returns null instead of throwing NG0201, and that you must handle the null in code.
Explain that optional changes only the not-found outcome, not the search path, and name the @Optional() decorator as its constructor form.
Show judgement about when absence is legitimate, and prefer a defaulted token or a fixed provider placement over silencing a required dependency.
Set library conventions for extension points: which hooks are optional, which have defaults, and how that keeps misconfiguration errors loud across teams.
## The default: a miss is an error When Angular resolves a **token** — a class or an `InjectionToken` — it searches element injectors from the requesting element upward, then environment injectors up to `root` and the platform injector. If nothing matches, the lookup reaches the `NullInjector`, which throws: ```text NG0201: No provider found for `AnalyticsSink`. ``` In Angular 22 the message also carries a `Path:` listing the injection chain. Throwing is the right default: a component that silently runs without a required service fails later and farther from the cause. ## What `optional` changes `inject(token, {optional: true})` changes exactly one thing: **what happens on a miss**. | | `inject(Sink)` | `inject(Sink, {optional: true})` | |---|---|---| | Provider found | the instance | the instance | | No provider anywhere on the path | throws NG0201 | returns `null` | | Provider found but its factory throws | throws | throws | | Static type | `Sink` | `Sink \| null` | | Search order | unchanged | unchanged | Points to be precise about in an interview: - It returns **`null`**, not `undefined` — code that checks `=== undefined` misses it. - The return type widens to `T | null` through `inject()`'s overloads, so strict TypeScript forces you to handle the absent case. - It is **not** a scope modifier: Angular still searches the full path. Whether a provider is *reachable* is decided by where providers sit and by the other options. ## The decorator form Before `inject()` became the recommended style, the same behaviour came from parameter decorators on constructors: ```ts constructor(@Optional() @Inject(ANALYTICS_SINK) sink: AnalyticsSink | null) {} ``` `@Optional()` is still supported and maps to the same lookup flag. The numeric `InjectFlags` enum that used to express these options was removed in Angular 20; `inject()` and `Injector.get()` take the options object instead. ## When `optional` is the right tool 1. **Optional collaborators.** An accordion panel that reports toggles to an analytics sink if the host application configured one, and works without it otherwise. 2. **Plugin hooks.** A library that checks whether the app provided a customisation token and falls back to built-in behaviour. 3. **Parent discovery.** A component looking for an ancestor of its own type — the top-level instance has none — usually as `{optional: true, skipSelf: true}`. 4. **Narrowed lookups.** `self`, `skipSelf` and `host` limit where Angular looks, so a miss becomes a normal outcome; pairing them with `optional` turns that miss into `null` instead of an exception. ## When it is the wrong tool - **Hiding a missing provider.** If the feature cannot work without the service, a `null` check only postpones the failure to a `TypeError` deep in a handler. - **Supplying a default.** If you want a default value rather than `null`, give the token a default instead — an `InjectionToken` with a `factory`, or a root-provided default implementation — so every consumer gets something usable without null checks. - **Masking the wrong scope.** If a provider exists but sits on a sibling branch or a different route, `optional` makes the bug silent; fix where the provider is placed. ## Interaction with the other options `optional` is most often seen next to an option that narrows the search: - `{optional: true, skipSelf: true}` — "my parent's instance, if I have a parent", used by recursive widgets. - `{optional: true, self: true}` — "a collaborator on this same element, if one is there", used by directives that enhance a form control only when one exists. - `{optional: true, host: true}` — "something from the template that declares me, if it provides it". In each case the narrowing option decides **where** Angular looks and `optional` decides **what a miss means**. Keeping those two questions separate is the cleanest way to explain the whole family of options. ## Summary `optional` is a statement about the *domain* — "this dependency may legitimately be absent" — not a workaround for DI errors. Used that way, it keeps NG0201 meaningful for everything else.
- Does optional: true make Angular search fewer or different injectors?No. The search path is exactly the same as without the option: element injectors from the requester upward, then environment injectors. `optional` only replaces the NG0201 thrown on a miss with `null`. To change where the search starts or stops you use `skipSelf`, `self` or `host`, often together with `optional`.
- When would you give a token a default instead of injecting it optionally?When consumers should always get a usable value. An `InjectionToken` declared with a `factory` (and `providedIn: 'root'`) supplies a default that apps can override with their own provider, so no consumer needs a null check. Optional injection fits better when absence itself changes behaviour, such as skipping analytics entirely.
saying these in an interview costs you the question
- optional: true returns undefined when nothing is provided
- optional restricts the search to the current component
- optional also swallows errors thrown by the provider's factory
- Wrapping required services in optional is a safe default
- InjectFlags.Optional is the current way to pass the option