Skip to content
All essays
WebMarch 27, 202512 min

Angular Dependency Injection

Master Angular DI system. Learn providers, injectors, tokens, and advanced DI patterns.

Ü
Ümit Uz
Mobile & Full Stack Developer

Angular's dependency injection system is powerful and flexible, enabling loosely coupled and testable code.

Basic Injection

typescript
@Injectable({ providedIn: 'root' })
export class UserService {
  constructor(private http: HttpClient) {}

  getUsers() {
    return this.http.get<User[]>('/api/users');
  }
}

@Component({
  selector: 'app-users',
  template: '...'
})
export class UsersComponent {
  constructor(private userService: UserService) {}
}

Providing Services

Root Level

typescript
@Injectable({ providedIn: 'root' })
export class DataService {
  constructor() {}
}

Component Level

typescript
@Component({
  selector: 'app-user',
  providers: [UserService],
  template: '...'
})
export class UserComponent {
  constructor(private userService: UserService) {}
}

Module Level

typescript
@NgModule({
  providers: [UserService]
})
export class UserModule {}

Injection Tokens

typescript
import { InjectionToken } from '@angular/core';

export const API_URL = new InjectionToken<string>('api-url');

// Provide
providers: [
  { provide: API_URL, useValue: 'https://api.example.com' }
]

// Inject
constructor(@Inject(API_URL) private apiUrl: string) {}

Providers

useValue

typescript
export const CONFIG = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
};

providers: [
  { provide: 'CONFIG', useValue: CONFIG }
]

useClass

typescript
export class LoggerService {
  log(message: string) {
    console.log(message);
  }
}

export class ConsoleLoggerService extends LoggerService {
  log(message: string) {
    console.log('[CONSOLE]', message);
  }
}

providers: [
  { provide: LoggerService, useClass: ConsoleLoggerService }
]

useExisting

typescript
providers: [
  { provide: LoggerService, useClass: LoggerService },
  { provide: 'LOGGER', useExisting: LoggerService }
]

useFactory

typescript
export function loggerFactory(config: Config) {
  return config.production
    ? new ProductionLogger()
    : new DevelopmentLogger();
}

providers: [
  { provide: Config, useValue: CONFIG },
  { provide: LoggerService, useFactory: loggerFactory, deps: [Config] }
]

Optional Dependencies

typescript
constructor(
  @Optional() private logger?: LoggerService
) {}

@Self, @SkipSelf, @Host

typescript
constructor(
  @Self() private selfService: Service,
  @SkipSelf() private parentService: Service,
  @Host() private hostService: Service
) {}

Inject Decorator

typescript
import { Inject } from '@angular/core';

constructor(
  @Inject(API_URL) private apiUrl: string,
  @Inject(LOCALE_ID) private locale: string
) {}

inject() Function

typescript
export class MyComponent {
  private userService = inject(UserService);
  private router = inject(Router);

  constructor() {
    this.userService.getUsers().subscribe();
  }
}

Injection in Functions and Pipes

typescript
export function loadData() {
  const http = inject(HttpClient);
  return http.get('/api/data');
}

@Pipe({ name: 'format', standalone: true })
export class FormatPipe implements PipeTransform {
  private config = inject(Config);

  transform(value: string): string {
    return this.config.prefix + value;
  }
}

Custom Injection Tokens

typescript
import { InjectionToken, inject } from '@angular/core';

interface AppConfig {
  apiUrl: string;
  environment: string;
}

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

// In providers
{
  provide: APP_CONFIG,
  useValue: {
    apiUrl: 'https://api.example.com',
    environment: 'production'
  }
}

// In component
const config = inject(APP_CONFIG);

Tree-Shakeable Providers

typescript
@Injectable({ providedIn: 'root' })
export class FeatureService {
  // Service is tree-shakeable
}

Testing with DI

typescript
describe('UserComponent', () => {
  let component: UserComponent;
  let fixture: ComponentFixture<UserComponent>;
  let mockUserService: jasmine.SpyObj<UserService>;

  beforeEach(async () => {
    mockUserService = jasmine.createSpyObj('UserService', ['getUsers']);

    await TestBed.configureTestingModule({
      declarations: [UserComponent],
      providers: [
        { provide: UserService, useValue: mockUserService }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(UserComponent);
    component = fixture.componentInstance;
  });

  it('should load users', () => {
    mockUserService.getUsers.and.returnValue(of([]));
    fixture.detectChanges();
    expect(mockUserService.getUsers).toHaveBeenCalled();
  });
});

Angular's DI system provides flexible and powerful dependency management.

Enjoyed this essay? Share it.

Related essays

Next essay
Server-Sent Events: Simple Real-Time Updates