In Angular, how do the self, skipSelf and host options of inject() change where a dependency lookup starts and stops?
answer
- one moves the start, two move the end
- current element only
- begin at the parent
- boundary of the declaring template
- no environment fallback for two of them
basics
~20 sskipSelf starts the search at the parent instead of the current element; self checks only the current element's injector; host stops at the host of the template the requester is declared in. self and host never fall back to environment injectors.
solid answer
~40 s`skipSelf` moves the **start**: the requesting element's own injector is skipped, and the walk continues through ancestors and then environment injectors as usual. `self` and `host` move the **end**. `self` checks only the current element's injector and never consults environment injectors — `inject(HttpClient, {self: true})` in a component throws NG0201. `host` lets the walk climb element injectors only up to the host element of the component whose template declares the requester; at that boundary only that component and its `viewProviders` are visible, not its `providers`, and environment injectors are not consulted. `self` cannot be combined with `skipSelf` or `host`; the others combine, typically with `optional`. Constructor injection uses `@Self()`, `@SkipSelf()` and `@Host()`.
code
ts · 15 linesimport {Directive, inject} from '@angular/core';
import {NgControl} from '@angular/forms';
@Directive({selector: 'input[appTrimOnBlur]', host: {'(blur)': 'trim()'}})
export class TrimOnBlur {
// Only the NgControl on this same <input>; null if the input has no formControl/ngModel.
private control = inject(NgControl, {self: true, optional: true});
trim() {
const value = this.control?.value;
if (typeof value === 'string') {
this.control?.control?.setValue(value.trim());
}
}
}go deeper
Remember that skipSelf starts at the parent, self checks only the current element, and host stops at the component boundary.
Explain which options disable the environment-injector fallback, why self plus a root service throws NG0201, and which combinations are illegal.
Reason about host precisely: the declaring template's boundary, projected content, and why only viewProviders are visible at the boundary, as the forms ControlContainer pattern shows.
Decide when a library should rely on narrowed lookups for encapsulation versus explicit inputs, since host and self rules are subtle for consumers to reason about.
## The default walk the options modify Without options, a lookup from a component or directive: 1. checks the requesting element's injector; 2. climbs ancestor element injectors; 3. then walks environment injectors — route, `root`, platform — up to the `NullInjector`, which throws NG0201. The Angular guide groups the options by what they change: **`optional`** changes what happens on a miss, **`skipSelf`** changes where the search **starts**, and **`self`** and **`host`** change where it **stops**. | Option | Starts at | Stops at | Environment injectors searched? | Constructor form | |---|---|---|---|---| | (none) | current element | `NullInjector` | yes | — | | `skipSelf` | parent element | `NullInjector` | yes | `@SkipSelf()` | | `self` | current element | current element | **no** | `@Self()` | | `host` | current element | host of the declaring template | **no** | `@Host()` | ## `skipSelf`: begin at the parent `skipSelf` ignores providers on the requesting element and starts with its parent. The rest of the walk is unchanged, including environment injectors. Typical uses: - a component that **provides** a service and also needs the ancestor's instance of the same token, for example to delegate to it; - a component looking for an **ancestor of its own type** — a nested accordion finding its parent accordion. Without `skipSelf` it would find itself and fail with NG0200 (circular dependency). The outermost instance has no ancestor, so this is paired with `optional`. ## `self`: this element only `self` checks only the requesting element's injector — the `providers` of components and directives on that element, including the other directives themselves — and then stops. It does **not** fall back to environment injectors, so a root-provided service injected with `self: true` from a component throws NG0201. Uses: - a directive that wants a collaborator **on the same element only**, such as the `NgControl` from a `formControlName` or `ngModel` on the same input: `inject(NgControl, {self: true, optional: true})`; - a component that must use its own instance and never an ancestor's. ## `host`: stop at the template boundary `host` is the least intuitive option. The walk climbs element injectors from the requester, but only within the **template in which the requester is declared**. The last stop is the host element of the component that owns that template, and at that boundary Angular exposes **only that component instance and its `viewProviders`** — not its `providers`, and not other directives on the host element. It never falls through to environment injectors. The option documentation also notes it is a no-op for environment injectors. Consequences worth knowing: - Content **projected** into a component is declared in the outer template, so its `host` boundary is the outer component, and the component it is projected into is just an intermediate element with all its `providers` visible. - A component's own `viewProviders` are reachable with `host` from its template; its `providers` are not. Angular's forms package relies on this: a child component re-exposes the parent form with `viewProviders: [{provide: ControlContainer, useExisting: NgForm}]`, and its inner controls — which look up their `ControlContainer` with host-limited lookups — find it at the boundary. ## Combinations - `self` cannot be combined with `skipSelf` (contradictory start) or with `host` (both define the end). - `skipSelf` + `host`: begin at the parent, stop at the boundary — the forms directives' pattern for finding their parent group. - Any of them + `optional`: narrowed lookups miss more often, and `optional` turns that miss into `null`. ## A worked example Take `<app-shell>` with `providers: [Theme]` rendering `<app-card>` with its own `providers: [Theme]`, and a `ThemeBadge` directive on `<app-card>`'s element, while `root` also provides a `Theme`: 1. `inject(Theme)` in `ThemeBadge` — found on its own element: the card's `Theme`. 2. `inject(Theme, {skipSelf: true})` — starts at `<app-shell>`: the shell's `Theme`. 3. `inject(Theme, {self: true})` — the card element's `Theme`; had the card not provided one, NG0201, never root's. 4. `inject(Theme, {host: true})` from the directive, which is declared in the shell's template — the card element is intermediate, so the card's `Theme` is found; without it, the walk would stop at `<app-shell>`, where only the shell's `viewProviders` count, so the shell's `providers` entry would be missed. ## Decorators versus options The constructor decorators `@Self()`, `@SkipSelf()`, `@Host()` and `@Optional()` express the same flags on parameters; `inject()` takes them as an options object. The old numeric `InjectFlags` enum was removed in Angular 20.
- Why does inject(HttpClient, {self: true}) in a component throw even though provideHttpClient() is in ApplicationConfig?`self` limits the lookup to the requesting element's own injector and disables the fallback to environment injectors. `HttpClient` lives in the root environment injector, so it is never consulted and the lookup throws NG0201. Drop `self` for environment-level services; it only makes sense for element-level collaborators.
- With host: true, why can a child in a component's template see that component's viewProviders but not its providers?At the host boundary Angular only exposes the component instance and its `viewProviders`, which by definition belong to the component's own view. `providers` are the host element's public, content-facing providers and are excluded at that boundary. This is why forms re-expose a parent form with `viewProviders` and `useExisting`.
- Can self and skipSelf be combined?No. `self` means check only the current element, `skipSelf` means do not check the current element, so together they would search nothing. The Angular guide lists self+skipSelf and self+host as the two combinations you cannot use; `skipSelf`+`host` and any option with `optional` are fine.
A courier looking for a signature: skipSelf means start with the neighbour's door; self means only this door; host means knock only on doors inside this building, including the building manager's office, but never go out to city hall.
saying these in an interview costs you the question
- self still falls back to root if the element lacks a provider
- skipSelf skips all element injectors and goes straight to root
- host stops at the nearest parent component in the DOM
- host sees the boundary component's providers array
- self and skipSelf can be combined to check only the parent