Por qué usar inject() en lugar de inyección por constructor puede perjudicar tu proyecto Angular
Recientemente, he observado una tendencia creciente en la comunidad Angular: el abandono de la inyección de dependencias por constructor en favor de la función `inject()`. Aunque esta función tiene casos de uso legítimos, su adopción generalizada como reemplazo del patrón clásico introduce problemas sutiles pero significativos en la mantenibilidad y testabilidad del código.
El atractivo de inject()
La función `inject()` fue introducida en Angular 14 como una alternativa para obtener dependencias fuera del contexto del constructor. Su sintaxis es innegablemente concisa:
@Component({...})
export class UserComponent {
private userService = inject(UserService);
private router = inject(Router);
}
Comparado con el enfoque tradicional:
@Component({...})
export class UserComponent {
constructor(
private userService: UserService,
private router: Router
) {}
}
La reducción de código es evidente. Sin embargo, **la brevedad no siempre equivale a claridad**.
El problema fundamental: dependencias ocultas
El constructor de una clase es un contrato explícito. Cuando declaramos dependencias en él, estamos comunicando de manera inequívoca qué necesita nuestra clase para funcionar. Este contrato es visible tanto para el desarrollador que lee el código como para las herramientas de análisis estático.
Con `inject()`, las dependencias se dispersan a través de la clase. Pueden aparecer en propiedades, en métodos, incluso condicionalmente. Esta dispersión rompe un principio fundamental del diseño de software: **hacer las dependencias explícitas y centralizadas**.
// Dependencias difíciles de rastrear
export class OrderComponent {
private cartService = inject(CartService);
processOrder() {
// Una dependencia adicional escondida en la implementación
const paymentService = inject(PaymentService);
// ...
}
}
Testabilidad comprometida
La inyección por constructor facilita enormemente las pruebas unitarias. Al instanciar una clase, simplemente pasamos los mocks necesarios:
const mockUserService = { getUser: () => of(mockUser) };
const component = new UserComponent(mockUserService as any, mockRouter);
Con `inject()`, debemos configurar el TestBed para cada prueba, incluso cuando queremos realizar pruebas unitarias simples. Esto aumenta la complejidad del setup y acopla nuestras pruebas al sistema de inyección de dependencias de Angular.
// Más configuración, más acoplamiento
TestBed.configureTestingModule({
providers: [
{ provide: UserService, useValue: mockUserService },
{ provide: Router, useValue: mockRouter }
]
});
const component = TestBed.createComponent(UserComponent).componentInstance;
La diferencia puede parecer menor en ejemplos aislados, pero en proyectos con cientos de componentes y servicios, esta fricción adicional desincentiva la escritura de pruebas exhaustivas.
El contexto de ejecución importa
`inject()` solo funciona dentro de un **contexto de inyección** activo. Esto significa que debe ejecutarse durante la construcción del componente o servicio, no después. Esta restricción no es obvia para desarrolladores nuevos en Angular y genera errores confusos:
Error: inject() must be called from an injection context
El constructor, por diseño, siempre se ejecuta en un contexto de inyección válido. Al usar exclusivamente inyección por constructor, eliminamos esta categoría de errores por completo.
Cuándo inject() tiene sentido
No estoy argumentando que `inject()` deba evitarse completamente. Tiene casos de uso legítimos:
- Funciones utilitarias que necesitan acceso a servicios (como los functional guards introducidos en Angular 14+)
- Herencia compleja donde pasar dependencias a través de múltiples niveles de constructores resulta engorroso
- Directivas y pipes donde la firma del constructor está restringida
La clave está en reconocer que `inject()` es una herramienta para situaciones específicas, no un reemplazo universal del patrón establecido.
El costo de seguir tendencias
Creo que la evolución actúa como una fuerza natural sobre todo lo que hacemos como desarrolladores. Adoptamos nuevas herramientas y patrones porque resuelven problemas reales o porque la comunidad los promueve. Pero debemos distinguir entre evolución genuina y cambio por novedad.
La inyección por constructor no es un patrón anticuado que necesita reemplazo. Es un enfoque probado que promueve código explícito, testable y mantenible. Antes de migrar un proyecto completo a `inject()`, pregúntate: ¿qué problema concreto estoy resolviendo?
Conclusión
La función `inject()` es una adición valiosa al toolkit de Angular, pero su uso indiscriminado introduce complejidad accidental en nuestros proyectos. Las dependencias se vuelven menos visibles, las pruebas más complicadas, y los errores de contexto de inyección aparecen donde antes no existían.
Mi recomendación: mantén la inyección por constructor como tu patrón predeterminado. Reserva `inject()` para los casos donde genuinamente simplifica código que de otra manera sería innecesariamente complejo. Tu yo futuro—y tu equipo—te lo agradecerán.
Comments
Post a Comment