Service Container & Dependency Injection
Relevant source files
The following files were used as context for generating this wiki page:
- src/app/script/app/services/service-container.ts
- src/app/script/app/services/brush-service.ts
- src/app/script/app/services/layer-service.ts
- src/app/script/app/services/tool-service.ts
- src/app/script/app/services/layout-service.ts
- src/app/script/app/services/history-coordinator.ts
- src/app/script/app/services/types.ts
- src/app/script/app/services/index.ts
- src/app/script/app/services/tests/service-container.test.ts
The Service Container in Rocreate serves as a centralized registry and orchestrator for the application's core logic modules. By utilizing Dependency Injection (DI) patterns, the system decouples high-level application components from specific service implementations, allowing for better testability, maintainability, and modular growth.
The architecture is built around a primary ServiceContainer class that implements the IServiceContainer interface, providing a unified access point to services managing brushes, layers, tools, layout, and history. This structure allows the application to pass a single container instance to UI components or controllers rather than injecting multiple individual dependencies.
Sources: src/app/script/app/services/service-container.ts:1-27, src/app/script/app/services/index.ts:1-22
Core Architecture
The service layer is designed with strict interface definitions to ensure that the container can hold any implementation that satisfies the required contract. All services within the container are marked as readonly, ensuring that the service registry remains immutable after initialization.
Service Registry Structure
The following diagram illustrates the relationship between the ServiceContainer and its constituent services:
The diagram shows the aggregation of specific service interfaces into the main container class.
Sources: src/app/script/app/services/service-container.ts:10-27, src/app/script/app/services/types.ts:1-15
Primary Services
The container manages five specialized services that handle different domains of the painting application:
| Service | Interface | Description |
|---|---|---|
| Brush | IBrushService | Manages brush state, including ID, color, size, opacity, and subscriptions to brush changes. |
| Layer | ILayerService | Handles layer stack operations, active layer selection, and synchronization with the canvas. |
| Tool | IToolService | Manages the currently selected tool (e.g., brush, select, fill) and tool transitions. |
| Layout | ILayoutService | Provides UI dimensions, handles layout mode (left/right), and calculates effective canvas sizes. |
| History | IHistoryService | Orchestrates undo/redo operations and tracks the history state via the HistoryCoordinator. |
Sources: src/app/script/app/services/types.ts:1-20, src/app/script/app/services/index.ts:1-22
Dependency Injection Pattern
Services are typically instantiated and then passed into the ServiceContainer constructor as a single object. This pattern is verified in the unit tests, where mock implementations are injected to validate container behavior.
// Example of DI via the container
const container = new ServiceContainer({
brush: new BrushService(params),
layer: new LayerService(params),
tool: new ToolService(params),
layout: new LayoutService(params),
history: new HistoryCoordinator(params),
});
Sources: src/app/script/app/services/tests/service-container.test.ts:104-115, src/app/script/app/services/service-container.ts:21-27
Data Flow & Event Subscriptions
Most services within the container provide subscription mechanisms. This allows UI components to react to state changes (such as a color change in the BrushService or an active layer change in the LayerService) without the services needing direct knowledge of the UI.
The flow shows how UI components interact with services through the container and receive updates via events.
Key Service Functionalities
- BrushService: Supports granular control over brush dynamics, including
increaseSizeanddecreaseSizemethods, and provides access to slider configurations. - LayerService: Facilitates finding the active layer, retrieving the full layer list, and syncing application state with the underlying canvas implementation.
- LayoutService: Acts as the source of truth for UI responsiveness, providing
getIsMobileandshouldUseCompactToolRowbased on window dimensions and current orientation.
Sources: src/app/script/app/services/tests/service-container.test.ts:7-60, src/app/script/app/services/types.ts:1-30
Testing and Mocking
The architecture specifically facilitates unit testing through the use of interfaces. Tests for the ServiceContainer demonstrate that the container correctly maps injected services and maintains their readonly state. By using vi.fn() mocks for each service interface, the test suite verifies that property access on the container correctly triggers the underlying service methods.
Sources: src/app/script/app/services/tests/service-container.test.ts:1-135
Summary
The Service Container and Dependency Injection system in Rocreate provide a robust foundation for managing application state. By encapsulating services for brushes, layers, tools, layout, and history within a unified container, the project ensures that core logic remains decoupled from the presentation layer. This "simple service container" approach provides immediate structure while remaining extensible for future enhancements such as lazy loading.
Sources: src/app/script/app/services/service-container.ts:18-20, src/app/script/app/services/index.ts:1-22