← Back to Chapters

Dependency Injection Hierarchy

? Dependency Injection Hierarchy

? Quick Overview

Angular's Dependency Injection (DI) system lets you provide and consume services at different levels of the component tree. The DI hierarchy controls a service's scope and lifetime, deciding whether an instance is shared app-wide or isolated to a specific component subtree.

? Key Concepts

  • Root Injector – Services provided at the root are singletons across the entire app.
  • Module-level Providers – Scoped to that module's injector when configured explicitly.
  • Component-level Providers – New service instance for that component and its children.
  • DI Hierarchy – Angular searches for dependencies starting from the requesting injector and moving up.
  • Scoped State – You control how state is shared or isolated by choosing where you provide the service.

? Syntax & Theory – DI Hierarchy in Angular

In Angular, providers can be registered at multiple levels:

  1. Root level – Using providedIn: 'root' in a service or using providers in the root module.
  2. Feature module level – Using the providers array of a specific NgModule.
  3. Component level – Using the providers array in the @Component decorator.

When a component asks for a service, Angular:

  1. Checks the component's injector for a provider.
  2. If not found, moves up to the parent component's injector.
  3. Continues walking up the tree until it reaches the root injector.

The first provider found in this search path decides which service instance the component receives.

? Providing Services at Different Levels

? View Code Example
// app.module.ts - DataService provided at the root injector
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { DataService } from './data.service';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [DataService], // Single shared instance for the whole application
  bootstrap: [AppComponent]
})
export class AppModule { }

// child.component.ts - DataService provided at the component level
import { Component } from '@angular/core';
import { DataService } from '../data.service';

@Component({
  selector: 'app-child',
  template: '<p>Child Component</p>',
  providers: [DataService] // New instance for this component and its children
})
export class ChildComponent { }

? How Angular Resolves These Services

  • Any component that is not under ChildComponent and injects DataService will receive the root-level singleton instance provided by AppModule.
  • ChildComponent and all its child components will receive a separate instance of DataService created by the component-level provider.
  • When Angular resolves DataService for ChildComponent, it first finds the provider declared in the component's providers array, so it does not use the root one.
  • This enables you to share state globally where needed and isolate state for specific feature areas or component trees.

✅ Tips & Best Practices

  • Use root-level providers for configuration, authentication, and global app state.
  • Use component-level providers when each component tree needs its own isolated state.
  • Be intentional: providing the same service at multiple levels creates multiple instances.
  • Prefer providedIn: 'root' in services for tree-shakable, app-wide singletons unless you need specific scoping.
  • Visualize the component tree and decide where the service's lifetime should begin and end.

? Try It Yourself

  • Create a CounterService and provide it at the root. Inject it into two different components and verify that incrementing the counter in one component updates the value in the other.
  • Provide the same CounterService again in a child component's providers array. Observe that the child component now has its own separate counter.
  • Build a small nested component tree and log the instance IDs (or object references) of a service to see how Angular's DI hierarchy chooses instances at different levels.
  • Refactor a feature that currently uses a root-level service to a component-level service when its state should not be shared with the entire app.