Skip to main content
The Bitwarden Clients codebase uses dependency injection (DI) extensively to manage service dependencies, enable testing, and maintain loose coupling between components.

DI Patterns Overview

The repository employs two primary DI patterns:
  1. Manual DI via Service Containers (CLI, Desktop background)
  2. Angular DI Framework (Browser, Desktop renderer, Web)
Why Two Patterns?Non-Angular contexts (CLI, Electron main process) use manual service containers. Angular contexts leverage Angular’s built-in DI system for component integration.

Service Abstraction Pattern

All services follow an abstraction-first approach:
Benefits:
  • Testability: Mock abstractions in tests
  • Flexibility: Swap implementations without changing consumers
  • Decoupling: Consumers depend on interfaces, not implementations

Manual Service Container (CLI)

The CLI application uses a manual service container for dependency management:
apps/cli/src/service-container/service-container.ts

Service Container Lifecycle

Accessing Services

Angular Dependency Injection

Angular applications (Browser, Desktop, Web) use Angular’s DI framework:

Service Registration

Services are registered in Angular modules:
apps/browser/src/popup/services/services.module.ts

Injection Tokens

Angular uses injection tokens for non-class dependencies:
libs/angular/src/services/injection-tokens.ts
Usage:

Safe Providers

The codebase uses safeProvider to catch injection errors early:
safeProvider validates that all dependencies are registered and throws clear errors if any are missing.

Constructor Injection

Services receive dependencies via constructor injection:
Key Points:
  • Dependencies are private or protected by convention
  • Use TypeScript’s parameter properties (private keyService: KeyService)
  • Abstract dependencies are injected, not concrete implementations

Service Scopes

Singleton Services (Default)

Most services are singletons - one instance shared across the application:

Injectable Decorator

Angular services can use @Injectable() decorator:
Root vs Module Providers
  • providedIn: 'root' - Singleton across entire app, tree-shakeable
  • Module providers - Singleton within module scope
  • Component providers - New instance per component instance

Testing with DI

Mocking Dependencies

Angular Testing Module

Common DI Patterns

Factory Pattern

Use factories for complex initialization:

Multi Providers

Register multiple values for the same token:

Optional Dependencies

Mark dependencies as optional:

Platform-Specific Services

Different platforms register different implementations:

State Provider Pattern

The StateProvider manages application state with DI:
Usage in services:

Circular Dependencies

Avoid Circular DependenciesCircular dependencies cause initialization errors and make code hard to test.
Bad Example:
Solution 1: Extract Shared Logic
Solution 2: Use Events/Observables

Best Practices

Abstract Over Concrete

Depend on abstractions (interfaces), not concrete implementations

Constructor Injection Only

Never use property injection or method injection

Minimize Dependencies

Services with too many dependencies indicate poor design

Single Responsibility

Each service should have one clear responsibility
1

Define the abstraction

Create abstract class or interface defining the contract
2

Implement the service

Create concrete implementation with constructor dependencies
3

Register in DI container

Add to service container or Angular module providers
4

Inject and use

Inject abstraction in consumers, never the implementation

Debugging DI Issues

Missing Provider Error

Solution: Register the service in the appropriate module:

Circular Dependency Error

Solution: Refactor to remove circular dependency (see patterns above).

Provider Order Matters

In service containers, initialize dependencies before dependents:

Next Steps

Service Architecture

Learn about service layers and patterns

State Management

Understand how state is managed across services