Skip to main content

useClass

Using useClass in Providers

The useClass option in a provider allows you to register a class as a dependency. When resolving the dependency, the container will instantiate and manage the class based on its lifecycle (singleton by default or transient if specified).

Example: Basic Usage

Here's how you can register a class using useClass:

import { ContainerResolver } from "depinjionary";
import { CalculatorService } from "./calculator.service";

const providers = [
{ provide: CalculatorService, useClass: CalculatorService }
];

const container = ContainerResolver.init(providers);
const calculator = await container.resolve<CalculatorService>(CalculatorService);
console.log(calculator.calculate(5, 10)); // Outputs: 15

Binding Classes to Interfaces

One of the most powerful uses of useClass is binding a class to an interface, allowing for better abstraction and flexibility. Here's an example:

Define an Interface

export interface LoggerInterface {
debug(data: string): void;
log(data: string): void;
}

export const LoggerInterface = Symbol('LoggerInterface');

Create a Class Implementation

export class ConsoleLoggerService implements LoggerInterface {
debug(data: string): void {
console.debug(`DEBUG: ${data}`);
}

log(data: string): void {
console.log(`LOG: ${data}`);
}
}

Register the Class to the Interface

const providers = [
{ provide: LoggerInterface, useClass: ConsoleLoggerService }
];

const container = ContainerResolver.init(providers);

// Resolving the interface token returns the implementation
const logger = await container.resolve<LoggerInterface>(LoggerInterface);
logger.log("This is a log message.");

Why Symbol Tokens?

By using Symbol tokens for interfaces (e.g., LoggerInterface), you ensure that the container can uniquely identify the dependency. It’s also convenient to export a Symbol with the same name as the interface due to TypeScript’s ability to differentiate between types and values.

Benefits of useClass

  1. Abstraction: Decouple implementation details from the interface, making your code more flexible and testable.
  2. Readability: Use descriptive tokens (like the interface name) to make dependencies clear.
  3. Convenience: The natural ability of TypeScript to export both the interface and the symbol under the same name makes it easier to manage dependencies.

Key Notes

  • Use useClass for cases where a class implements an interface or needs to be instantiated dynamically.
  • Always prefer Symbol or string tokens for interfaces to avoid naming conflicts.
  • Resolve dependencies using container.resolve() for type-safe and intuitive access.