DI Patterns Overview
The repository employs two primary DI patterns:- Manual DI via Service Containers (CLI, Desktop background)
- 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:- 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
Safe Providers
The codebase usessafeProvider 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:- Dependencies are
privateorprotectedby 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
TheStateProvider manages application state with DI:
Circular Dependencies
Bad Example: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
Circular Dependency Error
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