registerServices()
What?
Wires classes or values into the @archibald/di Container.
Why?
Services are the heart of business logic. By wiring them via DI, they can be injected into Controllers, Modules, or even other Services using the @Inject() decorator. This ensures a singleton-by-default pattern and makes testing easier via mocks.
How to use
public async initServices() {
await this.registerServices([
TranslationService,
CartService,
{
provide: CustomService,
useClass: ProjectCustomService
}
]);
}
Under the Hood: The DI Lifecycle
The registerServices method is a high-level wrapper around the @archibald/di Container.provide() method. The process follows a strict lifecycle:
- Tokenization: Every service is identified by a unique
Token. This happens in thepackages/di/src/di/token.tsconstructor and is invoked byContainer.set.- Class-based: If you pass a class,
new Token(Service)usesService.nameas the identifier. - Provider-based: If you use
{ provide: 'MY_TOKEN', ... },new Token('MY_TOKEN')uses the string as the identifier. - Normalization:
Container.setensures that anyTokenLikeinput is converted into a formalTokeninstance before storage.
- Class-based: If you pass a class,
- Declaration Storage: The container maintains an internal
Map<string, InjectableDefinition>calleddeclarations. When you callregisterServices, the constructor of your service is stored in this map. At this stage, the service is NOT yet instantiated. - Override Mechanism: If a service with the same token already exists in the
declarationsmap:- By default, the new registration is ignored (first-come, first-served).
- If
{ override: true }is provided, the container replaces the existing declaration and deletes any previous instance, ensuring the project-level implementation takes precedence.
- The
init()Trigger: The actual instantiation (callingnew Service()) typically happens whenContainer.init()is called by theFactory. This ensures that all services are registered before the first one is created, preventing "not found" errors during cross-injection.
Initialization Modes: Lazy vs. Eager
While the Factory typically handles batch instantiation, Archibald supports two distinct modes for service creation:
1. Lazy Initialization (Default)
When using registerServices() in the ShopServer, services are added to the declarations map but are not instantiated until Container.init() is called. This is the recommended approach for most services as it allows for overrides to be defined before any instances are created.
2. Eager Initialization (create: true)
You can force a service to be instantiated immediately upon registration by passing { create: true } to Container.set().
- Direct Usage:
Container.set(MyService, MyService, { create: true }); - Decorator Usage: The
@Service(token, register)decorator uses this under the hood.- Default Token: If used without parameters (e.g.,
@Service()), it uses the Class Name as the unique token (derived fromtarget.name). - Custom Token: If a string is passed (e.g.,
@Service('AuthService')), that string becomes the identifier. - Auto-Registration: To trigger immediate wiring and instantiation, the second parameter must be
true(e.g.,@Service(undefined, true)). If omitted orfalse, the decorator acts only as a metadata marker.
- Default Token: If used without parameters (e.g.,
Why use Eager Initialization?
- Self-Registering Infrastructure: For services that need to listen to global events or initialize background processes (like a log watcher or a metrics collector) without being explicitly requested by a controller.
- Immediate Side Effects: When the mere presence of the class should trigger a setup routine.
Risk Warning: Eagerly initialized services cannot be easily overridden by the ShopServer because they might be instantiated before the ShopServer.init() method even runs. Use this sparingly for core infrastructure only.