Skip to main content

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:

  1. Tokenization: Every service is identified by a unique Token. This happens in the packages/di/src/di/token.ts constructor and is invoked by Container.set.
    • Class-based: If you pass a class, new Token(Service) uses Service.name as the identifier.
    • Provider-based: If you use { provide: 'MY_TOKEN', ... }, new Token('MY_TOKEN') uses the string as the identifier.
    • Normalization: Container.set ensures that any TokenLike input is converted into a formal Token instance before storage.
  2. Declaration Storage: The container maintains an internal Map<string, InjectableDefinition> called declarations. When you call registerServices, the constructor of your service is stored in this map. At this stage, the service is NOT yet instantiated.
  3. Override Mechanism: If a service with the same token already exists in the declarations map:
    • 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.
  4. The init() Trigger: The actual instantiation (calling new Service()) typically happens when Container.init() is called by the Factory. 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 from target.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 or false, the decorator acts only as a metadata marker.

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.