Skip to content

Dependency Injection

Dependency Injection (DI) is a design pattern used to implement IoC (Inversion of Control), allowing you to develop loosely coupled code. This guide will help you understand the principles of DI and how to use @wroud/di to manage dependencies in your projects.

What is Dependency Injection?

Dependency Injection is a technique where an object receives its dependencies from an external source rather than creating them itself. This promotes decoupling and enhances testability and maintainability.

Benefits of Dependency Injection

  • Decoupling: Reduces dependencies between components, making your code more modular and easier to manage.
  • Testability: Simplifies unit testing by allowing you to mock dependencies.
  • Flexibility: Makes it easier to switch between different implementations of a service.

Dependency Injection in @wroud/di

In @wroud/di, you use the ServiceContainerBuilder to register services and their dependencies. The IServiceProvider is then used to resolve these services.

Registering and Resolving Services

Services are registered with different lifetimes using methods like addSingleton, addTransient, and addScoped.

Example: Registering and Resolving Services with Decorators

javascript
import { ServiceContainerBuilder, injectable } from '@wroud/di';

@injectable()
class Logger {
    log(message: string) {
        console.log(message);
    }
}

@injectable(() => [Logger])
class UserService {
    constructor(private logger: Logger) {
        this.logger = logger;
    }

    createUser(user: { name: string }) {
        this.logger.log(`Creating user: ${user.name}`);
        // User creation logic
    }
}

const containerBuilder = new ServiceContainerBuilder();
containerBuilder.addSingleton(Logger);
containerBuilder.addTransient(UserService);
const serviceProvider = containerBuilder.build();

const userService = serviceProvider.getService(UserService);
userService.createUser({ name: 'John Doe' });

Injecting Multiple Services

@wroud/di allows you to inject multiple services into a single class.

Example: Injecting Multiple Services

javascript
import { ServiceContainerBuilder, injectable } from '@wroud/di';

@injectable()
class Logger {
    log(message: string) {
        console.log(message);
    }
}

@injectable()
class ConfigService {
    getConfig() {
        return { appName: 'MyApp' };
    }
}

@injectable(() => [Logger, ConfigService])
class UserService {
    constructor(private logger: Logger, private configService: ConfigService) { }

    createUser(user: { name: string }) {
        this.logger.log(`Creating user: ${user.name} in ${this.configService.getConfig().appName}`);
        // User creation logic
    }
}

const containerBuilder = new ServiceContainerBuilder();
containerBuilder.addSingleton(Logger);
containerBuilder.addSingleton(ConfigService);
containerBuilder.addTransient(UserService);
const serviceProvider = containerBuilder.build();

const userService = serviceProvider.getService(UserService);
userService.createUser({ name: 'John Doe' });

Resolving Multiple Implementations of a Service

You can register and resolve multiple implementations of a service type using tokens.

Example: Resolving All Implementations of a Service Type

javascript
import { ServiceContainerBuilder, injectable, createService } from '@wroud/di';

interface ILogger {
    log(message: string): void;
}

const ILogger = createService<ILogger>('ILogger');

@injectable()
class ConsoleLogger implements ILogger {
    log(message: string) {
        console.log(`ConsoleLogger: ${message}`);
    }
}

@injectable()
class FileLogger implements ILogger {
    log(message: string) {
        // Assume logging to a file
        console.log(`FileLogger: ${message}`);
    }
}

@injectable(() => [[ILogger]])
class LoggingService {
    constructor(private loggers: ILogger[]) {}

    logToAll(message: string) {
        this.loggers.forEach(logger => logger.log(message));
    }
}

const containerBuilder = new ServiceContainerBuilder();
containerBuilder.addSingleton(ILogger, ConsoleLogger);
containerBuilder.addSingleton(ILogger, FileLogger);
containerBuilder.addTransient(LoggingService);
const serviceProvider = containerBuilder.build();

const loggingService = serviceProvider.getService(LoggingService);
loggingService.logToAll('This is a test message');

Conclusion

Dependency Injection is a powerful pattern for managing dependencies in your application, promoting decoupling and enhancing testability. By using @wroud/di, you can easily register and resolve services, allowing you to build modular and maintainable applications.

Released under the MIT License.