Dependency Injection
Service registration
Services are registered during application initialization through the AppShell.configureServices method. Each service is associated with a constructor key (typically a class) and a factory function that creates the service instance.
Factory functions receive a ReadableGlobalContext as a parameter, allowing services to depend on other services.
Ways to declare a service
You can register services in three ways:
- Using a class constructor and factory function
- Using a
ServiceKeyand factory function - Using an
AsyncServiceKeyand async factory function
export class LoggerService {
public logMessage(message: string) {
// ...
}
}
export class AppConfig implements AppShell {
public configureServices(ctx: WritableGlobalContext) {
// Use the class itself as the key
ctx.registerService(LoggerService, () => new LoggerService());
}
}import {ServiceKey} from "vue-mvvm";
// For values/services that are not classes, create an explicit key
export const ApiBaseUrl = new ServiceKey<string>("ApiBaseUrl");
export class AppConfig implements AppShell {
public configureServices(ctx: WritableGlobalContext) {
ctx.registerService(ApiBaseUrl, () => "https://api.example.com");
}
}import {AsyncServiceKey} from "vue-mvvm";
export interface UserProfile {
id: string;
name: string
}
export const CurrentUserProfile = new AsyncServiceKey<UserProfile>("CurrentUserProfile");
export class AppConfig implements AppShell {
public configureServices(ctx: WritableGlobalContext) {
ctx.registerService(CurrentUserProfile, async () => {
const res = await fetch("/api/me");
if (!res.ok) throw new Error("Failed to load user profile");
return await res.json() as UserProfile;
});
}
}Example registration
export class LoggerService {
public logMessage(message: string) {
// Implementation ...
}
}import {LoggerService} from "@services/logger.service";
export class AppConfig implements AppShell {
public configureServices(ctx: WritableGlobalContext) {
ctx.registerService(LoggerService, () => new LoggerService());
}
}A service can only be registered once with the same key.
Lazy instantiation
Services are retrieved using the getService method, which implements lazy instantiation with singleton semantics.
The getService function follows these steps:
- Check instance cache: First checks for an existing instance.
- Lookup factory: If no instance exists, retrieves the factory function.
- Instantiate: Calls the factory function with a
ReadableGlobalContextto create the instance - Validate: Throws
InvalidServiceInstanceErrorif the factory returns a falsy value - Cache: Stores the instance in the cache
This lazy instantiation pattern ensures services are only created when first requested, improving application startup performance.
Service Mocking
The mockService method enables replacing service implementations with test doubles, facilitating unit testing of ViewModels without real service dependencies.
import {LoggerService} from "@services/logger.service";
import {LoggerServiceMock} from "@mocks/logger.service";
export class AppConfig implements AppShell {
public configureServices(ctx: WritableGlobalContext) {
ctx.registerService(LoggerService, () => new LoggerService());
if (import.meta.env.VITE_TEST) {
ctx.mockService(LoggerService, () => new LoggerServiceMock());
}
}
}