¿Qué es un contenedor de Inyección de Dependencias para TypeScript?
La Inyección de Dependencias (DI) es un patrón de diseño en el cual un objeto recibe sus dependencias desde el exterior, en lugar de crearlas por sí mismo. Un contenedor DI es una herramienta que gestiona la creación e inyección de estas dependencias de forma automática. En TypeScript, gracias al tipado estático, los contenedores DI son especialmente efectivos: garantizan la seguridad de tipos y simplifican las pruebas.
La tarea principal del contenedor es vincular interfaces con sus implementaciones concretas y resolver las cadenas de dependencias. Esto permite escribir código débilmente acoplado y modular, que sea fácil de mantener y ampliar.
¿Por qué se necesita un contenedor DI en TypeScript?
- Reducción del acoplamiento del código — las clases no dependen de implementaciones concretas, sino solo de abstracciones (interfaces).
- Simplificación de las pruebas — las dependencias se reemplazan fácilmente con mocks o stubs.
- Gestión del ciclo de vida — el contenedor puede crear singletons, objetos transitorios u objetos con un ámbito específico.
- Resolución automática de dependencias — el contenedor construye el árbol de dependencias por sí mismo, liberando al desarrollador de la construcción manual de objetos.
Contenedores DI populares para TypeScript
Consideremos las dos bibliotecas más comunes: InversifyJS y TSyringe. Ambas son compatibles con TypeScript, pero tienen enfoques diferentes.
1. InversifyJS
InversifyJS es un potente contenedor IoC con decoradores. Requiere la indicación explícita de las dependencias mediante los decoradores @injectable() y @inject(). Es adecuado para proyectos grandes con una arquitectura clara.
Instalación
npm install inversify reflect-metadata --saveEn el archivo tsconfig.json, asegúrese de incluir:
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true }}Ejemplo de uso
import { Container, injectable, inject } from "inversify";import "reflect-metadata";
// Definimos la interfaz y el símbolo para el enlaceinterface ILogger { log(message: string): void;}const TYPES = { ILogger: Symbol.for("ILogger"), IUserService: Symbol.for("IUserService"),};
// Implementación del logger@injectable()class ConsoleLogger implements ILogger { log(message: string): void { console.log(`[LOG]: ${message}`); }}
// Servicio que depende del logger@injectable()class UserService { constructor(@inject(TYPES.ILogger) private logger: ILogger) {}
createUser(name: string): void { this.logger.log(`Usuario ${name} creado`); }}
// Configuración del contenedorconst container = new Container();container.bind<ILogger>(TYPES.ILogger).to(ConsoleLogger).inSingletonScope();container.bind<UserService>(TYPES.IUserService).to(UserService);
// Usoconst userService = container.get<UserService>(TYPES.IUserService);userService.createUser("Alexei");2. TSyringe
TSyringe es un contenedor DI ligero de Microsoft. Utiliza decoradores y resolución automática a través del constructor. Es ideal para proyectos medianos y pequeños.
Instalación
npm install tsyringe reflect-metadata --saveLa configuración de tsconfig.json es similar a la de InversifyJS.
Ejemplo de uso
import { container, injectable, singleton } from "tsyringe";import "reflect-metadata";
// Registramos la clase como singleton@singleton()class DatabaseConnection { connect(): void { console.log("Conexión a la BD establecida"); }}
// Clase con inyección automática de dependencia@injectable()class UserRepository { constructor(private db: DatabaseConnection) {}
save(user