Skip to the solution
HardFrontend9 min read

How does Angular's dependency resolution search work using @Self(), @SkipSelf(), @Optional(), and @Host()?

250 views

The problem

Question presented to candidate: "A directive inside a panel component needs a THEME value. Sometimes it should only use one declared on its own element, sometimes it must ignore its own and use the parent's, and sometimes it should quietly get nothing. Walk me through how Angular searches for a dependency and how @Self(), @SkipSelf(), @Optional() and @Host() change that search."

What a strong answer should cover:

  • Default resolution: start at the requesting element's element injector, walk up parent element injectors, then the environment injectors (root, platform), and throw NG0201 at the null injector.
  • self: look only at the current element's injector, nothing above it.
  • skipSelf: start at the parent, ignoring a provider on the current element (classic use: a nested instance of the same service).
  • optional: return null instead of throwing when nothing is found; combines with the others.
  • host: stop the search at the host component's boundary; inside a component's template it sees the host's viewProviders but not its plain providers.
  • Both syntaxes are current: parameter decorators on constructors and inject(T, { self, skipSelf, optional, host }); the decorators are still public API in Angular 22.
  • providers vs viewProviders and content projection change what each lookup can see.

Clarifying questions expected:

  • "Is the consumer in the component's own template, or projected into it with ng-content?"
  • "Should a missing value be an error or a silent fallback?"

Code / implementation expected: Yes β€” one token provided at several levels and probed with each modifier.

  • di
  • decorators

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read Β· 10 sections

Read the code

AngularOne token, four lookups, five places in the tree
import { Component, Directive, ElementRef, InjectionToken, inject } from "@angular/core";

// One token, provided at several levels. The string says WHO provided it.
export const THEME = new InjectionToken<string>("THEME", {
  providedIn: "root",
  factory: () => "environment",
});

// Gives an element its own THEME, so @Self has something to find.
@Directive({ selector: "[ownTheme]", providers: [{ provide: THEME, useValue: "own element" }] })
export class OwnTheme {}

// Asks for THEME four different ways and prints the answers into its element.
@Directive({ selector: "[probe]" })
export class Probe {
  private el = inject(ElementRef<HTMLElement>);
  private plain = inject(THEME);
  private self = inject(THEME, { self: true, optional: true });
  private skipSelf = inject(THEME, { skipSelf: true });
  private host = inject(THEME, { host: true, optional: true });
  constructor() {
    this.el.nativeElement.textContent =
      `default=${this.plain} | self=${this.self} | skipSelf=${this.skipSelf} | host=${this.host}`;
  }
}

@Component({
  selector: "app-panel",
  imports: [Probe, OwnTheme],
  providers: [{ provide: THEME, useValue: "panel" }],
  template: `
    <p>A, in panel template: <span probe></span></p>
    <p>B, in panel template, own provider: <span probe ownTheme></span></p>
    <ng-content />
  `,
})
export class Panel {}

@Component({
  selector: "app-root",
  imports: [Panel, Probe],
  providers: [{ provide: THEME, useValue: "app-root" }],
  template: `
    <app-panel>
      <p>C, projected into panel: <span probe></span></p>
    </app-panel>
    <p>D, in app-root template: <span probe></span></p>
    <p>E, app-root itself with skipSelf: {{ rootSkipSelf }}</p>
  `,
})
export class AppComponent {
  rootSkipSelf = inject(THEME, { skipSelf: true });
}
AngularSame tree with viewProviders on the panel: host now finds it, projected content does not
import { Component, Directive, ElementRef, InjectionToken, inject } from "@angular/core";

// One token, provided at several levels. The string says WHO provided it.
export const THEME = new InjectionToken<string>("THEME", {
  providedIn: "root",
  factory: () => "environment",
});

// Gives an element its own THEME, so @Self has something to find.
@Directive({ selector: "[ownTheme]", providers: [{ provide: THEME, useValue: "own element" }] })
export class OwnTheme {}

// Asks for THEME four different ways and prints the answers into its element.
@Directive({ selector: "[probe]" })
export class Probe {
  private el = inject(ElementRef<HTMLElement>);
  private plain = inject(THEME);
  private self = inject(THEME, { self: true, optional: true });
  private skipSelf = inject(THEME, { skipSelf: true });
  private host = inject(THEME, { host: true, optional: true });
  constructor() {
    this.el.nativeElement.textContent =
      `default=${this.plain} | self=${this.self} | skipSelf=${this.skipSelf} | host=${this.host}`;
  }
}

@Component({
  selector: "app-panel",
  imports: [Probe, OwnTheme],
  viewProviders: [{ provide: THEME, useValue: "panel" }], // only the panel view can see this
  template: `
    <p>A, in panel template: <span probe></span></p>
    <p>B, in panel template, own provider: <span probe ownTheme></span></p>
    <ng-content />
  `,
})
export class Panel {}

@Component({
  selector: "app-root",
  imports: [Panel, Probe],
  providers: [{ provide: THEME, useValue: "app-root" }],
  template: `
    <app-panel>
      <p>C, projected into panel: <span probe></span></p>
    </app-panel>
    <p>D, in app-root template: <span probe></span></p>
    <p>E, app-root itself with skipSelf: {{ rootSkipSelf }}</p>
  `,
})
export class AppComponent {
  rootSkipSelf = inject(THEME, { skipSelf: true });
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 115 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextHard

What is NgZone and when do you run outside Angular?

Continue