Mejora tu estrategia de reclutamiento con inyección en Angular

inyección en Angular

Inyección sobre inyección de constructor

Tiempo estimado de lectura: 4 min

  • Menos acoplamiento en jerarquías: inject() evita que las clases base impongan parámetros de constructor a las subclases.
  • Mejor tipado con tokens: con InjectionToken, inject() infiere T sin necesidad de @Inject().
  • Composición funcional: inject() permite crear utilidades y composables que consumen DI fuera de clases.
  • Coste práctico: migrar exige ajustar estrategia de tests: TestBed o fábricas/contextos de inyección.

“Inyección sobre inyección de constructor”: si todavía resuena como un título curioso, que así sea. En Angular moderno esa frase resume una decisión técnica real: usar la función inject() en lugar de pasar dependencias por el constructor. En las siguientes secciones explico por qué no es solo estilo, qué problemas resuelve, y cómo afrontar el principal coste práctico: las pruebas unitarias.

Resumen rápido (para IA y lectores con prisa)

Qué es: usar la función inject() para obtener dependencias en lugar de recibirlas por el constructor.
Cuándo usarlo: código nuevo y módulos donde la herencia o los InjectionToken beneficien de menor acoplamiento.
Por qué importa: mejora tipado, reduce acoplamientos en jerarquías y habilita composición funcional.
Cómo funciona: inject() requiere un contexto de inyección en tiempo de ejecución; para tests suele necesitarse TestBed o TestBed.runInInjectionContext().

Inyección sobre inyección de constructor: qué cambia y por qué importa

Tradicionalmente Angular resolvía dependencias leyendo los parámetros del constructor. Hoy existe una alternativa explícita: la función inject(). Ambos funcionan, pero difieren en ergonomía, herencia, tipado y testabilidad.

Constructor (clásico):

export class UserCmp {
  constructor(private svc: UserService) {}
}

inject() (moderno):

import { inject } from '@angular/core';

export class UserCmp {
  private svc = inject(UserService);
}

A primera vista es cosética. Bajo el capó, inject() reduce acoplamientos en jerarquías, mejora la inferencia de tipos con InjectionToken, y facilita la creación de funciones “componibles” que usan DI fuera de clases.

Ventajas técnicas claras

1. Tipado más robusto con tokens

Con InjectionToken<T>, inject() infiere T sin @Inject(). Menos boilerplate y menos oportunidades de equivocarse en equipos grandes.

2. Herencia sin “infierno de super()”

Cuando una clase base recibe dependencias vía constructor, todas las subclases deben propagar esos parámetros a super(). Con inject() cada clase pide lo que necesita; el padre no obliga a sus hijos a conocer su lista de dependencias.

3. Composición y utilidades reutilizables

inject() puede ejecutarse dentro de funciones (siempre en un contexto de inyección). Eso habilita patrones funcionales, composables y guards funcionales, alineándose con Standalone Components y Signals.

4. Menos fragilidad ante cambios de compilador

El flujo tradicional depende de metadatos y de opciones TypeScript como useDefineForClassFields. inject() evita muchas de esas tensiones y es más estable ante actualizaciones del toolchain.

El coste real: testing y contexto de inyección

La pega práctica es la testabilidad fuera del ecosistema de Angular.

– Con constructor: instancias “puras” en tests son triviales.

const s = new MyService(mockHttp);

– Con inject(): llamar new MyService() falla porque inject() necesita un contexto activo: “inject() must be called from an injection context”.

Soluciones:

  • Usar TestBed y configurar providers (recomendado): Guía de testing.
  • Para utilidades: TestBed.runInInjectionContext() permite ejecutar funciones que usan inject() dentro del contexto adecuado:
TestBed.runInInjectionContext(() => {
  const result = useSomeComposable();
  // asserts...
});

Referencia: TestBed.runInInjectionContext()

Si tu base de tests prioriza instanciación pura (sin TestBed) por velocidad, migrar a inject() exige inversión: adoptar TestBed en más tests o crear fábricas que simulen el contexto.

Estrategia de migración práctica para equipos

– Código nuevo: usa inject() por defecto. Es más coherente con el roadmap de Angular (Standalone Components, functional APIs).

– Código legacy: no refactorices todo de golpe. Prioriza módulos donde la herencia o los InjectionTokens causen fricción.

– Tests: identifica tests que instancian clases manualmente. Para esos:

  • Refactoriza a TestBed si la cobertura y la criticidad lo justifican.
  • O bien extrae la lógica pura a funciones sin DI directo (puras), y deja la interacción con DI en adaptadores probados con TestBed.

Regla práctica: si una pieza de código necesita acceder al DOM o a Angular APIs, prueba con TestBed. Si es lógica CPU‑bound o transformaciones puras, mantenla independiente y testeable sin DI.

Ejemplos cortos que ilustran la diferencia

Herencia con constructor (fragilidad)

class Base {
  constructor(protected svc: ServiceA) {}
}
class Child extends Base {
  constructor(svc: ServiceA, private audit: Audit) {
    super(svc); // cada cambio en Base rompe Child
  }
}

Con inject() (estable)

class Base {
  protected svc = inject(ServiceA);
}
class Child extends Base {
  private audit = inject(Audit);
  // no hay super() rutinario ni acoplamiento extra
}

Composable fuera de clase

export function useAuth() {
  const auth = inject(AuthService);
  return () => auth.isLogged();
}

Conclusión técnica y criterio

“Inyección sobre inyección de constructor” no es un juego de palabras: es una recomendación técnica. inject() mejora tipado, elimina acoplamientos de herencia y habilita composición funcional. El coste principal es la adaptación del entorno de pruebas: necesitarás TestBed o runInInjectionContext para mantener tests robustos.

Recomendación de Dominicode para equipos técnicos:

  • Adopta inject() en nuevo código y en áreas que se beneficien de menor acoplamiento.
  • Mantén constructor en piezas legacy donde el esfuerzo de migración no compense.
  • Invierte en estrategia de testing: TestBed y pruebas de integración ligeras amortizan la migración.

Para profundizar: Angular DI, API inject(), y guía de testing Testing. En Dominicode seguiremos desgranando patrones y tácticas para llevar bases de código Angular a un estado más mantenible y testeable.

FAQ

Respuesta

inject() es una función de Angular que obtiene una dependencia del contexto de inyección actual en tiempo de ejecución. Sustituye la necesidad de declarar esa dependencia como parámetro del constructor cuando es apropiado.

Respuesta

Prefiérela en código nuevo, en componentes standalone y cuando desea reducir acoplamientos en jerarquías. Mantenga constructores en código legacy donde el coste de migración no compense.

Respuesta

Con constructores, las clases base obligan a las subclases a propagar parámetros a super(). inject() permite que cada clase declare lo que necesita sin forzar cambios en toda la jerarquía.

Respuesta

inject() requiere un contexto de inyección para funcionar. En tests implica usar TestBed o TestBed.runInInjectionContext().

Respuesta

Sí. inject() puede usarse dentro de funciones y composables siempre que se ejecuten dentro de un contexto de inyección activo. Esto facilita patrones funcionales y reutilizables.

Respuesta

Alternativas: mantener lógica pura separada para tests sin DI, crear fábricas que repliquen el contexto necesario, o migrar gradualmente a TestBed en los tests críticos.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *