← Back to Articles         
Root-level and Component-level Services

Root-level and Component-level Services

Published on 30 Sep 2026     8 min read Angular
Services & Dependency Injection

1. What is Hierarchical Dependency Injection in Angular?

What is it?

Hierarchical Dependency Injection means Angular organizes its injectors in a parent-child hierarchy.

When Angular needs a dependency, it first looks in the nearest injector. If the dependency is not available there, Angular searches upward through parent injectors until it finds a matching provider.

Conceptually:

Root / Environment Injector
        │
        │ ProductService
        │
        ▼
Parent Component Injector
        │
        ▼
Child Component Injector

If the child requests ProductService:

Child Injector
     ↓ Not found

Parent Injector
     ↓ Not found

Root Injector
     ↓ Found

ProductService instance
     ↓

Child Component

This allows services to be either application-wide or limited to a particular component subtree.


2. Why do we need Hierarchical DI?

Not every service should necessarily be shared throughout the entire application.

For example:

Application
│
├── Header
├── Products
│     ├── ProductList
│     └── ProductDetails
│
└── Orders

You might want:

AuthService
     ↓
Entire Application

but another service might only be relevant to the Products area:

ProductStateService
       ↓
Products
 ├── ProductList
 └── ProductDetails

Hierarchical DI allows Angular to control the scope and sharing of service instances.


3. Root-Level Service

A root-level service is commonly configured using:

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

This makes the service available through the application's root environment injector.

Conceptually:

Root Injector
     │
     └── ProductService instance
             ↑
       ┌─────┼─────┐
       │     │     │
       A     B     C

Components throughout the application can normally receive the same root-level instance.

This is commonly used for:

  • Authentication
  • API services
  • Logging
  • Global state
  • Configuration
  • Caching
  • User information

4. Component-Level Service

A service can instead be provided directly by a component:

@Component({
  selector: 'app-product',
  providers: [ProductService]
})
export class Product {
}

Now Angular creates an instance associated with that component's injector.

Root Injector
     │
     ▼
Product Component
     │
     └── ProductService
              │
              ├── Product Child A
              │
              └── Product Child B

That instance is available to the component and generally its descendants.


5. Full Simple Sample Program

This example clearly demonstrates the difference.

counter.service.ts

import { Injectable } from '@angular/core';

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

  count = 0;

  increment(): void {
    this.count++;
  }
}

component-a.ts

import { Component } from '@angular/core';
import { CounterService } from './counter.service';

@Component({
  selector: 'app-component-a',
  standalone: true,
  template: `
    <h2>Component A</h2>

    <p>Count: {{ counterService.count }}</p>

    <button (click)="increment()">
      Increment
    </button>
  `
})
export class ComponentA {

  constructor(public counterService: CounterService) {
  }

  increment(): void {
    this.counterService.increment();
  }
}

component-b.ts

import { Component } from '@angular/core';
import { CounterService } from './counter.service';

@Component({
  selector: 'app-component-b',
  standalone: true,
  template: `
    <h2>Component B</h2>

    <p>Count: {{ counterService.count }}</p>

    <button (click)="increment()">
      Increment
    </button>
  `
})
export class ComponentB {

  constructor(public counterService: CounterService) {
  }

  increment(): void {
    this.counterService.increment();
  }
}

app.ts

import { Component } from '@angular/core';
import { ComponentA } from './component-a';
import { ComponentB } from './component-b';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    ComponentA,
    ComponentB
  ],
  template: `
    <h1>Hierarchical DI Example</h1>

    <app-component-a></app-component-a>

    <hr>

    <app-component-b></app-component-b>
  `
})
export class App {
}

6. How the Root-Level Example Works

The service contains:

@Injectable({
  providedIn: 'root'
})

Therefore both components resolve CounterService from the root-level provider.

Root Injector
      │
      │ CounterService
      │ count = 0
      │
   ┌──┴──┐
   │     │
   ▼     ▼
   A     B

Suppose Component A increments the value:

count = 1

Component B uses the same service instance, so it also observes:

count = 1

The state is shared.


7. Changing it to Component-Level

Now suppose we change ComponentA:

@Component({
  selector: 'app-component-a',
  standalone: true,

  providers: [CounterService],

  template: `
    <h2>Component A</h2>

    <p>Count: {{ counterService.count }}</p>

    <button (click)="increment()">
      Increment
    </button>
  `
})
export class ComponentA {

  constructor(public counterService: CounterService) {
  }

  increment(): void {
    this.counterService.increment();
  }
}

The important line is:

providers: [CounterService]

Now Component A has a local provider.


8. What Happens Now?

There is already a root provider:

@Injectable({
  providedIn: 'root'
})

But Component A also provides:

providers: [CounterService]

The hierarchy effectively becomes:

Root Injector
     │
     ├── CounterService Instance #1
     │
     │
     └──────────── Component B
     
Component A Injector
     │
     └── CounterService Instance #2

When Component A asks for:

CounterService

Angular searches the nearest injector first.

Component A Injector
        ↓
CounterService found
        ↓
Use local instance

It doesn't continue to the root injector.

Component B doesn't have a local provider:

Component B Injector
        ↓
Not found
        ↓
Root Injector
        ↓
CounterService found
        ↓
Use root instance

Therefore A and B now use different instances.


9. Angular's Injector Search Process

This is the central idea behind hierarchical DI.

Suppose:

Root
 │
 ▼
Parent
 │
 ▼
Child

The child requests:

ProductService

Angular effectively searches:

Child
  ↓
Is ProductService here?

NO
  ↓
Parent
  ↓
Is ProductService here?

NO
  ↓
Root
  ↓
Found

But if the parent provides it:

Root
 │
 │ ProductService #1
 │
 ▼
Parent
 │ ProductService #2
 │
 ▼
Child

then the child receives:

ProductService #2

because the nearest matching provider wins.


10. Root-Level vs Component-Level Services

Feature Root-Level Component-Level
Configuration providedIn: 'root' providers: [Service]
Scope Application-wide Component subtree
Instance Usually shared Local instance
Available to Entire application Component + descendants
Lifetime Usually application lifetime Related to component instance
Shared state Application-wide Localized
Typical usage Auth, API, logging Local feature/component state

11. Root-Level Example

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

Architecture:

Root Injector
      │
      └── AuthService
            │
     ┌──────┼──────┐
     ▼      ▼      ▼
 Header   Login   Orders

This makes sense because authentication information is often required throughout the application.


12. Component-Level Example

Imagine a shopping-cart editor component that needs temporary local state:

@Component({
  selector: 'app-cart-editor',
  providers: [CartEditorService]
})
export class CartEditor {
}

If two instances of the component exist:

<app-cart-editor></app-cart-editor>

<app-cart-editor></app-cart-editor>

Angular can create separate service instances:

CartEditor #1
      │
CartEditorService #1


CartEditor #2
      │
CartEditorService #2

This is very useful when each component needs independent state.


13. Advantages of Hierarchical DI

Flexible Service Scope

You can decide whether a service should be:

Global
or
Local

Shared Global Services

Root-level services are useful when multiple unrelated components need the same functionality/state.

Isolated State

Component-level providers allow individual component instances to maintain separate state.

Automatic Dependency Resolution

Angular searches the injector hierarchy automatically.

Better Component Encapsulation

A component can own services that exist only for that component subtree.


14. Disadvantages / Things to Watch

Accidental Multiple Instances

This is the most important issue.

Suppose:

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

but you also write:

@Component({
  providers: [UserService]
})

The component can receive a different UserService instance.

If UserService contains shared state, this may cause unexpected behavior.


Injector Hierarchy Can Become Complex

In large applications:

Root
 ↓
Feature
 ↓
Parent
 ↓
Child
 ↓
Grandchild

different levels can provide the same token.

You need to understand which injector is actually providing the dependency.


15. Key Points

  • Angular DI is hierarchical.
  • Angular searches for dependencies starting from the nearest relevant injector and then moves upward.
  • The nearest matching provider is normally used.
  • providedIn: 'root' is commonly used for application-wide services.
  • Root-level services are normally shared across the application.
  • providers: [Service] on a component creates a component-level provider.
  • Component-level services are available to that component and its descendants.
  • Different component instances can get different service instances.
  • Component-level providers are useful for isolated/local state.
  • Be careful not to accidentally create multiple instances of a service that is supposed to hold global state.

16. Interview Questions and Answers

1. What is hierarchical Dependency Injection?

Hierarchical DI means Angular organizes injectors in a hierarchy. When resolving a dependency, Angular searches the nearest injector first and moves upward until it finds a matching provider.

2. What is a root-level service?

A root-level service is typically registered using:

@Injectable({
  providedIn: 'root'
})

It is available throughout the application and normally uses a shared application-level instance.

3. What is a component-level service?

A component-level service is registered using:

providers: [ProductService]

inside @Component(). Its instance is scoped to that component and its descendant hierarchy.

4. What happens if the same service is provided at root and component level?

The component-level provider takes precedence for that component subtree.

Root → ProductService #1

Component
   ↓
ProductService #2

The component uses #2.

5. When should you use root-level services?

Use them when functionality or state needs to be broadly shared, such as:

  • Authentication
  • API access
  • Logging
  • Global state
  • Configuration

6. When should you use component-level services?

Use them when the service/state should belong specifically to a component or component subtree, especially when each component instance needs independent state.


Interview Shortcut

ROOT LEVEL

@Injectable({
  providedIn: 'root'
})

Application
     ↓
One broadly shared service instance


COMPONENT LEVEL

@Component({
  providers: [MyService]
})

Component
     ↓
Local service instance
     ↓
Available to descendants

Most Important Rule

Angular searches from the nearest injector upward. If a service is provided locally at the component level, that local provider normally takes precedence over the root-level provider.

← Back to Articles