Skip to main content

Overview

The @Injectable decorator marks a class as available to be provided and injected as a dependency. It enables Angular’s dependency injection system to provide instances of the class where needed.

Signature

@Injectable(options?: Injectable): TypeDecorator

Parameters

options
Injectable
Optional configuration object that determines how and where the service is provided.

Injectable Options

providedIn

providedIn
'root' | 'platform' | null
Determines which injector provides the injectable.
  • 'root' - Provided in the root injector (application-level, singleton)
  • 'platform' - Provided in the platform injector (shared across apps)
  • null - Not automatically provided, must be added to a providers array
@Injectable({ providedIn: 'root' })
The providedIn: 'any' option is deprecated. Use 'root' instead.

Basic Service Example

user.service.ts
import { Injectable } from '@angular/core';

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

  addUser(name: string) {
    this.users.push(name);
  }

  getUsers(): string[] {
    return this.users;
  }

  getUserCount(): number {
    return this.users.length;
  }
}

Service with Dependencies

data.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private apiUrl = 'https://api.example.com';

  constructor(private http: HttpClient) {}

  fetchData<T>(endpoint: string): Observable<T> {
    return this.http.get<T>(`${this.apiUrl}/${endpoint}`);
  }

  postData<T>(endpoint: string, data: any): Observable<T> {
    return this.http.post<T>(`${this.apiUrl}/${endpoint}`, data);
  }
}

Using Services in Components

app.component.ts
import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app-root',
  standalone: true,
  template: `
    <div>
      <h2>Users ({{userCount}})</h2>
      <ul>
        <li *ngFor="let user of users">{{user}}</li>
      </ul>
      <button (click)="addUser()">Add User</button>
    </div>
  `
})
export class AppComponent implements OnInit {
  users: string[] = [];
  userCount = 0;

  constructor(private userService: UserService) {}

  ngOnInit() {
    this.users = this.userService.getUsers();
    this.userCount = this.userService.getUserCount();
  }

  addUser() {
    this.userService.addUser(`User ${this.userCount + 1}`);
    this.users = this.userService.getUsers();
    this.userCount = this.userService.getUserCount();
  }
}

Component-Level Providers

notification.service.ts
import { Injectable } from '@angular/core';

@Injectable()
export class NotificationService {
  notifications: string[] = [];

  add(message: string) {
    this.notifications.push(message);
  }

  clear() {
    this.notifications = [];
  }
}
notification.component.ts
import { Component } from '@angular/core';
import { NotificationService } from './notification.service';

@Component({
  selector: 'app-notifications',
  standalone: true,
  providers: [NotificationService], // Component-specific instance
  template: `
    <div>
      <div *ngFor="let msg of notificationService.notifications">
        {{msg}}
      </div>
      <button (click)="addNotification()">Add</button>
      <button (click)="notificationService.clear()">Clear</button>
    </div>
  `
})
export class NotificationComponent {
  constructor(public notificationService: NotificationService) {}

  addNotification() {
    this.notificationService.add(`Notification at ${new Date().toLocaleTimeString()}`);
  }
}
When provided in a component, each instance of the component gets its own instance of the service.

Factory Providers

logger.service.ts
import { Injectable, InjectionToken } from '@angular/core';

export const IS_PRODUCTION = new InjectionToken<boolean>('IS_PRODUCTION');

@Injectable()
export class Logger {
  constructor(private isProduction: boolean) {}

  log(message: string) {
    if (!this.isProduction) {
      console.log(message);
    }
  }

  error(message: string) {
    console.error(message);
  }
}

export function loggerFactory(isProduction: boolean) {
  return new Logger(isProduction);
}
app.config.ts
import { ApplicationConfig } from '@angular/core';
import { Logger, loggerFactory, IS_PRODUCTION } from './logger.service';

export const appConfig: ApplicationConfig = {
  providers: [
    { provide: IS_PRODUCTION, useValue: false },
    {
      provide: Logger,
      useFactory: loggerFactory,
      deps: [IS_PRODUCTION]
    }
  ]
};

Class Providers with useClass

config.service.ts
import { Injectable } from '@angular/core';

export abstract class ConfigService {
  abstract get apiUrl(): string;
}

@Injectable()
export class DevelopmentConfigService implements ConfigService {
  get apiUrl() {
    return 'https://dev-api.example.com';
  }
}

@Injectable()
export class ProductionConfigService implements ConfigService {
  get apiUrl() {
    return 'https://api.example.com';
  }
}
app.config.ts
import { ApplicationConfig } from '@angular/core';
import { ConfigService, DevelopmentConfigService } from './config.service';

export const appConfig: ApplicationConfig = {
  providers: [
    {
      provide: ConfigService,
      useClass: DevelopmentConfigService
    }
  ]
};

Value Providers

constants.ts
import { InjectionToken } from '@angular/core';

export const API_CONFIG = new InjectionToken<ApiConfig>('API_CONFIG');

export interface ApiConfig {
  apiUrl: string;
  timeout: number;
  retries: number;
}
app.config.ts
import { ApplicationConfig } from '@angular/core';
import { API_CONFIG, ApiConfig } from './constants';

const apiConfig: ApiConfig = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3
};

export const appConfig: ApplicationConfig = {
  providers: [
    { provide: API_CONFIG, useValue: apiConfig }
  ]
};
Usage:
import { Injectable, inject } from '@angular/core';
import { API_CONFIG, ApiConfig } from './constants';

@Injectable({ providedIn: 'root' })
export class ApiService {
  private config = inject(API_CONFIG);

  constructor() {
    console.log(`API URL: ${this.config.apiUrl}`);
  }
}

Inject Function

modern.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { API_CONFIG } from './constants';

@Injectable({
  providedIn: 'root'
})
export class ModernService {
  private http = inject(HttpClient);
  private config = inject(API_CONFIG);

  fetchData() {
    return this.http.get(`${this.config.apiUrl}/data`);
  }
}
The inject() function is the modern way to inject dependencies. It can be used in constructors, field initializers, and factory functions.

Service Lifecycle

cleanup.service.ts
import { Injectable, OnDestroy } from '@angular/core';
import { interval, Subscription } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class CleanupService implements OnDestroy {
  private subscription: Subscription;

  constructor() {
    this.subscription = interval(1000).subscribe(n => {
      console.log(`Tick: ${n}`);
    });
  }

  ngOnDestroy() {
    // Clean up resources
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }
}
Services provided in 'root' are singletons and live for the entire application lifetime. They won’t be destroyed until the app is closed. Implement OnDestroy for cleanup, but be aware it may not be called for root services.

Testing Services

user.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';

describe('UserService', () => {
  let service: UserService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [UserService]
    });
    service = TestBed.inject(UserService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should add users', () => {
    service.addUser('John');
    expect(service.getUserCount()).toBe(1);
    expect(service.getUsers()).toContain('John');
  });
});

Provider Scopes

ScopeDescriptionLifetime
providedIn: 'root'Application-level singletonEntire app
providedIn: 'platform'Platform-level singletonAll apps on page
Component providersNew instance per componentComponent lifetime
Module providersShared in module scopeModule lifetime

Best Practices

  • Use providedIn: 'root' for application-wide services
  • Use component providers for component-specific state
  • Implement OnDestroy for cleanup in services with subscriptions
  • Use inject() function for modern dependency injection
  • Create abstract classes for service interfaces
  • Avoid circular dependencies between services
  • Don’t store component-specific state in root services
  • Be careful with service constructors - keep them lightweight
  • Always unsubscribe from observables to prevent memory leaks

See Also

Dependency Injection

Learn DI fundamentals

Hierarchical Injectors

Understand injector hierarchy

Creating Services

Service creation guide

Providers

Configure providers