| |

Angular 56 🅰️ Hierarchical Injectors

Angular’s dependency injection is not a flat container. It is a tree of injectors, and the tree mirrors the application’s structure — the component tree, the route tree, the lazy-loaded module tree. When a component asks for a dependency, the injector at that component tries to satisfy the request; if it cannot, it delegates to its parent, and the parent to its parent, all the way up to the root. This hierarchy is what makes a service a singleton at one level and a per-instance at another. It is what lets a feature module override a service for its own subtree without affecting the rest of the application. It is what lets a directive require a host component’s service. The two hierarchies — the EnvironmentInjector for the modules and the routes, and the ElementInjector for the components and the directives — are parallel but connected, and the resolution order walks the element tree first, then the environment. This chapter covers the two hierarchies, the resolution order, the NodeInjector, the injector delegation, the @Host, the @Self, the @SkipSelf, the component-level providers, the route-level providers, the lazy module’s injector, and the patterns that make the hierarchy work.

Key point: Angular has two injector hierarchies. The EnvironmentInjector is the module and route injector — created by ApplicationConfig, by the @Injectable({ providedIn: 'root' }), by the lazy-loaded route’s providers array. The ElementInjector is the component and directive injector — created by the @Component({ providers: [...] }) and the @Directive({ providers: [...] }). The resolution walks the element hierarchy first (the component’s own providers, then its parent’s, then the host’s, then the root’s), and then the environment hierarchy. The @Self stops at the current injector, the @SkipSelf skips it, the @Host stops at the host component, and the @Optional returns the null instead of the error. The NodeInjector is the internal representation, and the inject() function resolves from the current context.


The two hierarchies

Angular’s DI is two hierarchies, and the two are parallel. The EnvironmentInjector is the module-level, and the ElementInjector is the component-level.

The EnvironmentInjector. The environment injector is created by the ApplicationConfig‘s providers array, by the @Injectable({ providedIn: 'root' }) on a service, and by the lazy-loaded route’s providers array. It is the module-level, and its tree mirrors the route tree and the lazy module tree.

The ElementInjector. The element injector is created by the @Component({ providers: [...] }) and the @Directive({ providers: [...] }). It is the component-level, and its tree mirrors the DOM’s component tree.

Why the two are separate. The two are separate because the lifetimes are different. The environment injector lives for the application’s lifetime (or the lazy module’s), and the element injector lives for the component’s lifetime. The separation is what makes a service a singleton at one level and a per-component at another.

Why the resolution walks the element first. The inject() and the constructor injection resolve from the current context. The context is the element injector, and the resolution walks the element hierarchy first. The environment injector is the fallback.

Why the two are connected. The element injector’s hierarchy ends at the host component, and the host component’s element injector has the parent — which is the environment injector at the root. The two are connected, and the connection is the resolution’s path.

Why the hierarchy matters for the testing. The test can provide a mock at the component’s level, and the component’s subtree uses the mock. The parent’s provider is not used, and the isolation is the test’s.

Why the hierarchy matters for the lazy modules. A lazy-loaded module has its own environment injector, and the services provided there are the module’s. The services are created when the module is loaded, and the lifetime is the module’s.

Why the hierarchy is the source of the surprise. The same token can resolve to the different instances in the different parts of the tree. The @Injectable({ providedIn: 'root' }) is the singleton, and the @Component({ providers: [...] }) is the per-component. The two are the different, and the different is the surprise.

Why the two hierarchies are the DI’s power. The hierarchy is what makes the DI flexible. The root services are the application-wide, the route services are the feature-scoped, and the component services are the local. The same token can have the different instances, and the different is the override and the isolation.


The resolution’s order

The resolution’s order is the element first, then the environment. The order is the specific, and the specific is the model.

The step 1: the current element injector. The resolution starts at the current element injector, which is the component’s or the directive’s own providers.

@Component({
  selector: 'app-child',
  providers: [{ provide: Logger, useClass: ChildLogger }],
})
export class ChildComponent {
  private readonly logger = inject(Logger);  // ChildLogger
}

The inject(Logger) resolves the ChildLogger, which is the component’s own provider.

The step 2: the parent’s element injector. If the current injector does not have the token, the resolution walks to the parent’s element injector.

The step 3: the host’s element injector. The resolution continues up the element tree, and the @Host stops at the host component.

The step 4: the environment injector. The element hierarchy’s root is the host component, and the next is the environment injector. The environment injector’s tree is the module’s and the route’s.

The step 5: the root’s environment injector. The resolution continues up the environment tree, and the root is the last.

Why the order is the element first. The element first is the specific, and the specific is the close. The component’s own provider is the closest, and the closest is the resolution’s first. The environment is the fallback.

Why the order matters for the override. The element’s provider overrides the environment’s, and the override is the local. The component can override the service for its subtree, and the subtree’s components use the override.

Why the order matters for the @Host. The @Host stops at the host component’s element injector, and the environment injector is not reached. The @Host is the stop, and the stop is the directive’s.

Why the order is the NodeInjector‘s. The NodeInjector is the internal representation of the element injector, and the resolution uses the node’s parent. The NodeInjector is the internal, and the internal is the implementation.

Why the order should be the known. The order is the known, and the known is the fluency. The element first, the environment second is the model, and the model is the resolution.

Why the order can be the confusing. The order is the different from the module’s, and the module’s is the flat. The two are the different, and the different is the confusion. The inject() is the context, and the context is the order.


The @Self, the @SkipSelf, and the @Host

The three modifiers control the resolution’s path. The @Self stops at the current, the @SkipSelf skips the current, and the @Host stops at the host.

The @Self. The @Self requires the token at the current injector.

@Component({
  selector: 'app-child',
  providers: [{ provide: Logger, useClass: ChildLogger }],
})
export class ChildComponent {
  constructor(@Self() private logger: Logger) {}
}

The @Self() requires the Logger at the ChildComponent‘s injector. The provider is the component’s, and the resolution is the current’s.

Why the @Self matters. The @Self is the strict, and the strict is the local. The directive that requires the host’s provider uses the @Self, and the @Self is the requirement.

The @SkipSelf. The @SkipSelf skips the current injector.

@Component({ selector: 'app-child', standalone: true, template: `` })
export class ChildComponent {
  constructor(@SkipSelf() private logger: Logger) {}
}

The @SkipSelf() skips the ChildComponent‘s injector and starts at the parent’s. The parent’s Logger is the resolution, and the current’s is the skipped.

Why the @SkipSelf matters. The @SkipSelf is the parent’s, and the parent’s is the inherited. The component that needs the parent’s service uses the @SkipSelf, and the @SkipSelf is the request.

The @Host. The @Host stops at the host component’s injector.

@Directive({ selector: '[appHighlight]' })
export class HighlightDirective {
  constructor(@Host() private logger: Logger) {}
}

The @Host() stops at the host component’s injector, and the resolution does not go beyond. The directive requires the host’s Logger, and the @Host is the requirement.

Why the @Host matters. The @Host is the directive’s, and the directive’s is the host. The directive that needs the host’s service uses the @Host, and the @Host is the stop.

The @Optional. The @Optional returns the null instead of the error.

@Component({ selector: 'app-child', standalone: true, template: `` })
export class ChildComponent {
  constructor(@Optional() private logger: Logger | null) {}
}

The @Optional() makes the logger the Logger | null, and the missing provider is the null. The pattern is the graceful, and the graceful is the optional.

Why the @Optional matters. The @Optional is the absence’s, and the absence is the missing. The component that may not have the provider uses the @Optional, and the @Optional is the tolerance.

Why the modifiers are the four. The @Self, the @SkipSelf, the @Host, and the @Optional are the four. The four are the vocabulary, and the vocabulary is the fluency.


The inject()‘s options

The inject() function accepts the options that mirror the modifiers.

@Component({ selector: 'app-child', standalone: true, template: `` })
export class ChildComponent {
  private readonly self = inject(Logger, { self: true });
  private readonly skipSelf = inject(Logger, { skipSelf: true });
  private readonly host = inject(Logger, { host: true });
  private readonly optional = inject(Logger, { optional: true });
}

The inject(Logger, { self: true }) is the @Self(), and the inject(Logger, { skipSelf: true }) is the @SkipSelf(). The inject(Logger, { host: true }) is the @Host(), and the inject(Logger, { optional: true }) is the @Optional().

Why the options matter. The options are the modern, and the modern is the concise. The decorators are the legacy, and the legacy is the verbose. The two are the equivalent, and the equivalent is the choice.

Why the inject() is the preferred. The inject() is the modern, and the modern is the field initializer. The constructor injection is the legacy, and the legacy is the constructor. The two are the equivalent, and the inject() is the concise.

Why the options can be combined. The options can be combined, and the combination is the specific.

private readonly logger = inject(Logger, { skipSelf: true, optional: true });
// Logger | null, resolved from the parent.

The { skipSelf: true, optional: true } combines the two, and the result is the Logger | null from the parent. The combination is the pattern, and the pattern is the specific.

Why the inject()‘s options matter. The options are the four’s, and the four are the modifiers. The inject() is the modern, and the modern is the preference. The two are the equivalent, and the equivalent is the safety.

Why the inject()‘s options should be the known. The options are the known, and the known is the fluency. The four are the vocabulary, and the vocabulary is the skill. The inject()‘s options are the modern, and the modern is the practice.


The component-level providers

The @Component({ providers: [...] }) creates the element injector, and the providers are the component’s. The providers are the component’s subtree’s, and the subtree’s components use them.

@Component({
  selector: 'app-feature',
  providers: [FeatureService],
  template: `<app-child />`,
})
export class FeatureComponent {}

@Component({ selector: 'app-child', standalone: true, template: `` })
export class ChildComponent {
  private readonly feature = inject(FeatureService);  // the FeatureComponent's
}

The FeatureService is provided at the FeatureComponent‘s injector, and the ChildComponent‘s inject(FeatureService) resolves to the FeatureComponent‘s instance. The provider is the feature’s, and the feature’s is the subtree’s.

Why the component-level matters. The component-level is the local, and the local is the feature’s. The feature’s service is the feature’s subtree’s, and the subtree’s is the scope. The two are the pair, and the pair is the scope.

Why the component-level is the override. The component-level overrides the environment’s, and the override is the local. The component can override the service for its subtree, and the subtree’s components use the override.

Why the component-level’s lifetime matters. The component-level’s lifetime is the component’s, and the component’s is the create and destroy. The service is created with the component, and destroyed with the component. The two are the pair, and the pair is the lifetime.

Why the component-level is the per-instance. The component-level is the per-instance, and the per-instance is the two components’. The two FeatureComponents have the two FeatureServices, and the two are the separate. The per-instance is the scope, and the scope is the component’s.

Why the component-level matters for the state. The component-level’s service can hold the component’s state, and the state is the component’s. The two are the pair, and the pair is the state. The service is the component’s, and the component’s is the scope.

Why the component-level should be the sparing. The component-level is the per-instance, and the per-instance is the memory. The application’s services should be the root’s, and the root’s is the singleton. The component-level is the specific, and the specific is the use.


The route-level providers

The route’s providers array creates the route’s environment injector, and the providers are the route’s. The route’s service is the route’s subtree’s, and the subtree’s is the scope.

const routes: Routes = [
  {
    path: 'admin',
    providers: [AdminService],
    children: [
      { path: '', component: AdminComponent },
    ],
  },
];

The AdminService is provided at the admin route’s injector, and the route’s components use it. The service is the route’s, and the route’s is the scope.

Why the route-level matters. The route-level is the feature’s, and the feature’s is the lazy. The route’s service is the route’s subtree’s, and the subtree’s is the scope. The two are the pair, and the pair is the feature.

Why the route-level is the lazy’s. The route-level is the lazy’s, and the lazy is the module’s. The route’s injector is created when the route is loaded, and the injector’s service is the route’s. The two are the pair, and the pair is the lazy.

Why the route-level is the cleanup’s. The route-level’s injector is destroyed when the route is unloaded, and the service is destroyed with the injector. The two are the pair, and the pair is the cleanup. The route’s service’s lifetime is the route’s, and the route’s is the cleanup.

Why the route-level matters for the singleton. The route-level’s service is the route’s singleton, and the route’s is the feature’s. The route’s components share the service, and the sharing is the scope. The two are the pair, and the pair is the singleton.

Why the route-level should be the feature’s. The route-level is the feature’s, and the feature’s is the scope. The route’s service is the feature’s, and the feature’s is the natural. The two are the pair, and the pair is the design.

Why the route-level’s providers matters. The route-level’s providers is the array, and the array is the declaration. The route’s service is the route’s, and the route’s is the scope. The two are the pair, and the pair is the declaration.


The injector’s EnvironmentInjector

The EnvironmentInjector is the injectable handle to the environment injector, and the runInInjectionContext runs the callback in the context.

@Component({ selector: 'app-child', standalone: true, template: `` })
export class ChildComponent {
  private readonly injector = inject(EnvironmentInjector);

  ngOnInit(): void {
    this.injector.runInInjectionContext(() => {
      const service = inject(FeatureService);
    });
  }
}

The inject(EnvironmentInjector) is the handle, and the runInInjectionContext is the context. The inject(FeatureService) is the valid inside the callback, and the resolution is the context’s.

Why the EnvironmentInjector matters. The EnvironmentInjector is the handle, and the handle is the escape. The runInInjectionContext is the context, and the context is the inject()‘s requirement. The two are the pair, and the pair is the escape.

Why the runInInjectionContext is the escape. The runInInjectionContext is the escape, and the escape is the callback’s. The inject() requires the context, and the runInInjectionContext provides it. The two are the pair, and the pair is the escape.

Why the EnvironmentInjector can be the created. The createEnvironmentInjector creates the child injector, and the providers are the child’s.

const childInjector = createEnvironmentInjector(
  [{ provide: Logger, useClass: FileLogger }],
  parentInjector,
);

The createEnvironmentInjector creates the child, and the parentInjector is the parent. The child’s providers override the parent’s, and the override is the child’s.

Why the createEnvironmentInjector matters. The createEnvironmentInjector is the dynamic, and the dynamic is the runtime. The static is the providers array, and the dynamic is the code’s. The two are the different, and the different is the use.

Why the EnvironmentInjector matters for the testing. The test can create the injector, and the injector’s providers are the test’s. The TestBed uses the EnvironmentInjector, and the injector is the test’s. The two are the pair, and the pair is the test.

Why the EnvironmentInjector is the advanced. The EnvironmentInjector is the advanced, and the advanced is the rare. The inject() is the common, and the common is the daily. The EnvironmentInjector is the escape, and the escape is the specific.


Complete Example Session

import {
  Component, Directive, Injectable, inject, EnvironmentInjector,
  runInInjectionContext, Self, SkipSelf, Host, Optional,
  createEnvironmentInjector,
} from '@angular/core';

// ============================================
// PART 1: THE ROOT SERVICE
// ============================================

@Injectable({ providedIn: 'root' })
export class RootLogger {
  log(message: string): void {
    console.log('[root]', message);
  }
}

// ============================================
// PART 2: THE COMPONENT-LEVEL PROVIDER
// ============================================

@Component({
  selector: 'app-feature',
  standalone: true,
  providers: [{ provide: RootLogger, useClass: FileLogger }],
  template: `<app-child />`,
})
export class FeatureComponent {}

@Component({ selector: 'app-child', standalone: true, template: `` })
export class ChildComponent {
  private readonly logger = inject(RootLogger);
  // The FileLogger, resolved from the FeatureComponent's providers.
}

// ============================================
// PART 3: THE @SELF
// ============================================

@Component({
  selector: 'app-self',
  standalone: true,
  providers: [{ provide: RootLogger, useClass: ChildLogger }],
  template: ``,
})
export class SelfComponent {
  private readonly logger = inject(RootLogger, { self: true });
  // The ChildLogger, from the component's own providers.
}

// ============================================
// PART 4: THE @SKIPSELF
// ============================================

@Component({ selector: 'app-skip', standalone: true, template: `` })
export class SkipComponent {
  private readonly logger = inject(RootLogger, { skipSelf: true });
  // The RootLogger, from the parent.
}

// ============================================
// PART 5: THE @HOST
// ============================================

@Directive({ selector: '[appHighlight]' })
export class HighlightDirective {
  private readonly logger = inject(RootLogger, { host: true });
  // The host component's logger.
}

// ============================================
// PART 6: THE @OPTIONAL
// ============================================

@Component({ selector: 'app-optional', standalone: true, template: `` })
export class OptionalComponent {
  private readonly logger = inject(RootLogger, { optional: true });
  // The RootLogger | null
}

// ============================================
// PART 7: THE ROUTE-LEVEL PROVIDER
// ============================================

const routes: Routes = [
  {
    path: 'admin',
    providers: [AdminService],
    children: [
      { path: '', component: AdminComponent },
    ],
  },
];

// ============================================
// PART 8: THE RUN IN INJECTION CONTEXT
// ============================================

@Component({ selector: 'app-context', standalone: true, template: `` })
export class ContextComponent {
  private readonly injector = inject(EnvironmentInjector);

  ngOnInit(): void {
    this.injector.runInInjectionContext(() => {
      const logger = inject(RootLogger);
    });
  }
}

// ============================================
// PART 9: THE CREATE ENVIRONMENT INJECTOR
// ============================================

const parentInjector = inject(EnvironmentInjector);
const childInjector = createEnvironmentInjector(
  [{ provide: RootLogger, useClass: FileLogger }],
  parentInjector,
);

// ============================================
// PART 10: WHAT NOT TO DO
// ============================================

// Don't expect the component's provider to affect the siblings
// The scope is the component's subtree.

// Don't forget the @Host for the directive's
// The @Host stops at the host.

// Don't use the @Self without the local provider
// The error is the missing.

// Don't use the component-level for the application's singleton
// Use the providedIn: 'root'.

// Don't forget the runInInjectionContext
// The inject() requires the context.

// Don't mix the constructor and the inject()
// The two are the equivalent, but the consistency matters.

The ten parts cover the root service, the component-level provider, the @Self, the @SkipSelf, the @Host, the @Optional, the route-level provider, the runInInjectionContext, the createEnvironmentInjector, and the anti-patterns.


Quick Reference

The Two Hierarchies

The hierarchyThe created by
The EnvironmentInjectorThe ApplicationConfig, the providedIn, the route’s providers
The ElementInjectorThe @Component({ providers }), the @Directive({ providers })

The Resolution’s Order

The stepThe injector
1The current element injector
2The parent’s element injector
3The host’s element injector
4The environment injector
5The root’s environment injector

The Modifiers

The modifierThe effect
The @SelfThe current injector only
The @SkipSelfThe parent, skipping the current
The @HostThe host component’s injector
The @OptionalThe null when absent

The inject()‘s Options

The optionThe modifier
The { self: true }The @Self
The { skipSelf: true }The @SkipSelf
The { host: true }The @Host
The { optional: true }The @Optional

The Provider’s Levels

The levelThe scope
The rootThe application
The routeThe route’s subtree
The componentThe component’s subtree
The directiveThe directive’s subtree

The Injector’s APIs

The APIThe purpose
The EnvironmentInjectorThe handle
The runInInjectionContextThe context
The createEnvironmentInjectorThe child
The inject()The resolution

Best Practices

✅ Do This:

// Use the providedIn: 'root' for the singleton
@Injectable({ providedIn: 'root' })
export class UserService {}                                    // ✅

// Use the component-level for the feature
@Component({ providers: [FeatureService] })
export class FeatureComponent {}                               // ✅

// Use the route-level for the lazy
const routes: Routes = [
  { path: 'admin', providers: [AdminService], children: [...] },
];                                                             // ✅

// Use the @Self for the local
constructor(@Self() private logger: Logger) {}                 // ✅

// Use the @SkipSelf for the parent
constructor(@SkipSelf() private logger: Logger) {}             // ✅

// Use the @Host for the directive
constructor(@Host() private logger: Logger) {}                 // ✅

// Use the @Optional for the graceful
constructor(@Optional() private logger: Logger | null) {}      // ✅

// Use the runInInjectionContext for the escape
this.injector.runInInjectionContext(() => inject(Service));    // ✅

❌ Don’t Do This:

// Don't expect the sibling's provider
// The scope is the subtree.                                 // ⚠️

// Don't forget the @Host for the directive
// The resolution goes beyond the host.                      // ⚠️

// Don't use the @Self without the local
@Self() service: Service  // the error if not provided        // ⚠️

// Don't use the component-level for the singleton
// The component-level is the per-instance.                  // ⚠️

// Don't forget the runInInjectionContext
inject(Service);  // outside the context                      // ⚠️

// Don't mix the constructor and the inject()
constructor(private a: A) { const b = inject(B); }            // ⚠️

Common Pitfalls

PitfallProblemSolution
The sibling’s providerThe wrong scopeThe subtree’s
The missing @HostThe resolution’s overrunThe @Host
The @Self without the localThe errorThe provider
The component-level for the singletonThe per-instanceThe providedIn: 'root'
The missing contextThe errorThe runInInjectionContext
The mixed stylesThe inconsistentThe inject()
The lazy’s providerThe missingThe route’s providers
The NodeInjector‘s assumptionThe internalThe public’s API

Real-World Examples

1. The root service

@Injectable({ providedIn: 'root' })
export class UserService {}

2. The component-level provider

@Component({ providers: [FeatureService] })
export class FeatureComponent {}

3. The route-level provider

{ path: 'admin', providers: [AdminService], children: [...] }

4. The @Self

constructor(@Self() private logger: Logger) {}

5. The @SkipSelf

constructor(@SkipSelf() private logger: Logger) {}

6. The @Host

constructor(@Host() private logger: Logger) {}

7. The @Optional

constructor(@Optional() private logger: Logger | null) {}

8. The inject()‘s options

const logger = inject(Logger, { skipSelf: true, optional: true });

9. The runInInjectionContext

this.injector.runInInjectionContext(() => inject(Service));

10. The createEnvironmentInjector

const child = createEnvironmentInjector([...], parent);

Visual: The Two Hierarchies

┌──────────────────────────────────────────────────────────┐
│  THE ENVIRONMENT HIERARCHY                               │
│    The ApplicationConfig                                 │
│      │                                                   │
│      ├── The lazy route's injector                       │
│      │     └── The route's providers                     │
│      │                                                   │
│      └── The root's environment injector                 │
│            └── The providedIn: 'root'                    │
│                                                          │
├──────────────────────────────────────────────────────────┤
│  THE ELEMENT HIERARCHY                                   │
│    The FeatureComponent's injector                       │
│      │                                                   │
│      └── The ChildComponent's injector                   │
│            └── The GrandChildComponent's injector        │
│                                                          │
│  The two are parallel, and the resolution is the element │
│  first, then the environment.                            │
│                                                          │
└──────────────────────────────────────────────────────────┘

Visual: The Resolution’s Order

┌──────────────────────────────────────────────────────────┐
│  inject(Logger)                                          │
│       │                                                  │
│       ▼                                                  │
│  1. The current component's providers                    │
│       │                                                  │
│       ├── The found? The return.                         │
│       │                                                  │
│       ▼                                                  │
│  2. The parent component's providers                     │
│       │                                                  │
│       ├── The found? The return.                         │
│       │                                                  │
│       ▼                                                  │
│  3. The host's providers                                 │
│       │                                                  │
│       ├── The found? The return.                         │
│       │                                                  │
│       ▼                                                  │
│  4. The environment injector                             │
│       │                                                  │
│       ├── The found? The return.                         │
│       │                                                  │
│       ▼                                                  │
│  5. The root's environment injector                      │
│       │                                                  │
│       ├── The found? The return.                         │
│       │                                                  │
│       ▼                                                  │
│  The error (the NullInjectorError).                      │
│                                                          │
└──────────────────────────────────────────────────────────┘

Visual: The Modifiers

┌──────────────────────────────────────────────────────────┐
│  THE TREE                                                │
│                                                          │
│  The RootInjector                                        │
│    └── The ParentInjector                                │
│          └── The ChildInjector                           │
│                                                          │
│  THE MODIFIERS                                           │
│    @Self()      → the ChildInjector only                 │
│    @SkipSelf()  → the ParentInjector                     │
│    @Host()      → the host component's injector          │
│    @Optional()  → the null when absent                   │
│                                                          │
│  The default: the walk up the hierarchy.                 │
│                                                          │
└──────────────────────────────────────────────────────────┘

Visual: The Provider’s Levels

┌──────────────────────────────────────────────────────────┐
│  THE ROOT                                                │
│    @Injectable({ providedIn: 'root' })                   │
│    The application's singleton.                          │
│                                                          │
├──────────────────────────────────────────────────────────┤
│  THE ROUTE                                               │
│    { path: 'admin', providers: [AdminService] }          │
│    The route's subtree.                                  │
│                                                          │
├──────────────────────────────────────────────────────────┤
│  THE COMPONENT                                           │
│    @Component({ providers: [FeatureService] })           │
│    The component's subtree.                              │
│                                                          │
├──────────────────────────────────────────────────────────┤
│  THE DIRECTIVE                                           │
│    @Directive({ providers: [HighlightService] })         │
│    The directive's subtree.                              │
│                                                          │
│  The level determines the scope, and the scope is the    │
│  lifetime.                                               │
│                                                          │
└──────────────────────────────────────────────────────────┘

Visual: The Element vs the Environment

┌──────────────────────────────────────────────────────────┐
│  THE ELEMENT HIERARCHY                                   │
│    The component's providers                             │
│    The parent's providers                                │
│    The host's providers                                  │
│                                                          │
│  THE ENVIRONMENT HIERARCHY                               │
│    The route's providers                                 │
│    The root's providers                                  │
│                                                          │
│  THE RESOLUTION                                          │
│    The element first, then the environment.              │
│                                                          │
│  The element's provider overrides the environment's.     │
│  The override is the local.                              │
│                                                          │
└──────────────────────────────────────────────────────────┘

Summary

ItemValue
The EnvironmentInjectorThe module and route
The ElementInjectorThe component and directive
The resolution’s orderThe element first, then the environment
The @SelfThe current injector only
The @SkipSelfThe parent, skipping the current
The @HostThe host component’s
The @OptionalThe null when absent
The component-levelThe component’s subtree
The route-levelThe route’s subtree
The EnvironmentInjectorThe handle
The runInInjectionContextThe context

Key takeaways:

  • Angular has two injector hierarchies — the EnvironmentInjector for the modules and the routes, and the ElementInjector for the components and the directives
  • The resolution walks the element hierarchy first, then the environment — the current component, the parent, the host, the environment, the root
  • The @Self stops at the current injector — the @SkipSelf skips it, and the @Host stops at the host component
  • The @Optional returns the null instead of the error — the graceful’s tool
  • The inject()‘s options mirror the modifiers — the { self: true }, the { skipSelf: true }, the { host: true }, the { optional: true }
  • The component-level providers create the element injector — the providers are the component’s subtree’s, and the subtree’s is the scope
  • The route-level providers create the environment injector — the route’s service is the route’s subtree’s, and the lazy’s is the cleanup
  • The EnvironmentInjector is the injectable handle — the runInInjectionContext is the escape for the inject() outside the construction
  • The createEnvironmentInjector creates the child injector — the dynamic’s tool, and the providers override the parent’s
  • The hierarchy is the DI’s power — the same token can have the different instances in the different scopes, and the different is the override and the isolation

Remember: Angular’s DI is two hierarchies — the EnvironmentInjector for the modules and the routes, and the ElementInjector for the components and the directives. The resolution walks the element first, then the environment. The @Self, the @SkipSelf, the @Host, and the @Optional control the path, and the inject()‘s options are the modern’s. The component-level and the route-level providers scope the services, and the EnvironmentInjector is the handle. The hierarchy is the DI’s power, and the power is the flexibility.


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!