Skip to main content
Dependency Injection (DI) is a design pattern and core feature of Angular that allows you to declare dependencies in your classes rather than creating instances yourself. Angular’s DI system handles the creation and lifecycle of service instances.

Why Dependency Injection?

Loose Coupling

Components don’t need to know how to create dependencies

Testability

Easy to provide mock dependencies for testing

Reusability

Services can be shared across components

Maintainability

Change implementations without modifying consumers

The @Injectable Decorator

Mark a class as available for injection:
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private data: string[] = [];

  getData(): string[] {
    return this.data;
  }

  addData(item: string): void {
    this.data.push(item);
  }
}
providedIn: 'root' makes the service available throughout the application as a singleton.

Provider Scopes

Angular supports several scopes for providing services:

Root Scope

Application-wide singleton:
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private currentUser: User | null = null;

  login(user: User): void {
    this.currentUser = user;
  }

  logout(): void {
    this.currentUser = null;
  }

  getCurrentUser(): User | null {
    return this.currentUser;
  }
}

Component Scope

Provide a service at the component level:
import { Component } from '@angular/core';
import { LoggerService } from './logger.service';

@Component({
  selector: 'app-user-profile',
  standalone: true,
  providers: [LoggerService], // New instance for this component
  template: `<div>{{ user.name }}</div>`
})
export class UserProfileComponent {
  constructor(private logger: LoggerService) {
    this.logger.log('Component initialized');
  }
}

Platform Scope

Shared across multiple Angular applications on the same page:
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'platform'
})
export class PlatformConfigService {
  getConfig() {
    return { apiUrl: 'https://api.example.com' };
  }
}

Using inject() Function

The modern way to inject dependencies:
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { UserService } from './user.service';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  template: `
    <h1>Dashboard</h1>
    <div>{{ user?.name }}</div>
  `
})
export class DashboardComponent {
  // Modern inject() function
  private http = inject(HttpClient);
  private userService = inject(UserService);
  
  user = this.userService.getCurrentUser();

  ngOnInit() {
    this.http.get('/api/data').subscribe(data => {
      console.log('Data loaded:', data);
    });
  }
}
inject() can only be called during the construction phase (in constructors, initializers, or factory functions).

Constructor Injection (Legacy)

Traditional approach using constructor parameters:
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { UserService } from './user.service';

@Component({
  selector: 'app-legacy',
  standalone: true,
  template: `<div>Legacy component</div>`
})
export class LegacyComponent {
  constructor(
    private http: HttpClient,
    private userService: UserService
  ) {
    // Dependencies are injected
  }
}

Injection Tokens

Provide non-class dependencies:
import { InjectionToken, inject } from '@angular/core';

// Define a token
export const API_URL = new InjectionToken<string>('API URL');

// Provide the token
import { ApplicationConfig } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    { provide: API_URL, useValue: 'https://api.example.com' }
  ]
};

// Inject the token
import { Component } from '@angular/core';

@Component({
  selector: 'app-api-consumer',
  standalone: true,
  template: `<div>API: {{ apiUrl }}</div>`
})
export class ApiConsumerComponent {
  apiUrl = inject(API_URL);
}

Provider Types

Angular supports several provider configurations:

Class Provider

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

export abstract class Logger {
  abstract log(message: string): void;
}

@Injectable()
export class ConsoleLogger implements Logger {
  log(message: string): void {
    console.log(message);
  }
}

// Provide using useClass
import { ApplicationConfig } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    { provide: Logger, useClass: ConsoleLogger }
  ]
};

Value Provider

interface AppConfig {
  apiEndpoint: string;
  production: boolean;
}

const CONFIG: AppConfig = {
  apiEndpoint: 'https://api.example.com',
  production: false
};

export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');

// Provide using useValue
export const appConfig: ApplicationConfig = {
  providers: [
    { provide: APP_CONFIG, useValue: CONFIG }
  ]
};

Factory Provider

import { inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

export class ApiService {
  constructor(private http: HttpClient, private baseUrl: string) {}
}

function apiServiceFactory(): ApiService {
  const http = inject(HttpClient);
  const config = inject(APP_CONFIG);
  return new ApiService(http, config.apiEndpoint);
}

// Provide using useFactory
export const appConfig: ApplicationConfig = {
  providers: [
    { provide: ApiService, useFactory: apiServiceFactory }
  ]
};

Existing Provider

Alias one token to another:
@Injectable({ providedIn: 'root' })
export class NewService {
  getData() { return ['new', 'data']; }
}

export abstract class OldService {
  abstract getData(): string[];
}

// Alias OldService to NewService
export const appConfig: ApplicationConfig = {
  providers: [
    NewService,
    { provide: OldService, useExisting: NewService }
  ]
};

Optional Dependencies

Handle missing dependencies gracefully:
import { Component, inject, Optional } from '@angular/core';
import { LoggerService } from './logger.service';

@Component({
  selector: 'app-flexible',
  standalone: true,
  template: `<div>Flexible component</div>`
})
export class FlexibleComponent {
  // Using inject() with optional
  private logger = inject(LoggerService, { optional: true });

  doSomething() {
    // Check if logger is available
    this.logger?.log('Action performed');
  }
}

// Using constructor with @Optional
export class LegacyFlexibleComponent {
  constructor(@Optional() private logger?: LoggerService) {}

  doSomething() {
    this.logger?.log('Action performed');
  }
}

Self and SkipSelf

Control dependency resolution:
import { Component, inject, Self, SkipSelf } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-scoped',
  standalone: true,
  providers: [DataService],
  template: `<div>Scoped component</div>`
})
export class ScopedComponent {
  // Inject only from this component's injector
  private localService = inject(DataService, { self: true });
  
  // Skip this component's injector, use parent
  private parentService = inject(DataService, { skipSelf: true });
}

Hierarchical Injection

Angular creates a hierarchy of injectors:
1

Platform Injector

Shared across all applications on the page
2

Root Injector

Application-wide services (providedIn: ‘root’)
3

Module Injectors

Created for lazy-loaded modules (legacy)
4

Component Injectors

Created when components have providers
5

Element Injectors

Created for directives with providers
import { Component, Injectable } from '@angular/core';

@Injectable()
class CounterService {
  count = 0;
  increment() { this.count++; }
}

@Component({
  selector: 'parent-component',
  standalone: true,
  providers: [CounterService], // Parent instance
  template: `
    <h2>Parent: {{ counter.count }}</h2>
    <button (click)="counter.increment()">Increment</button>
    <child-component />
  `
})
export class ParentComponent {
  counter = inject(CounterService);
}

@Component({
  selector: 'child-component',
  standalone: true,
  // No providers - inherits from parent
  template: `
    <h3>Child: {{ counter.count }}</h3>
    <button (click)="counter.increment()">Increment</button>
  `
})
export class ChildComponent {
  counter = inject(CounterService); // Same instance as parent
}

Best Practices

  1. Use inject() over constructor injection for modern applications
  2. Provide in root for application-wide singletons
  3. Use component providers for component-specific state
  4. Leverage injection tokens for configuration and non-class dependencies
  5. Keep services focused on single responsibilities
  6. Avoid circular dependencies between services
  7. Use factory providers for complex initialization logic

Next Steps

Services

Learn to create and use services

Components

Understand component architecture

Build docs developers (and LLMs) love