Inyección de dependencias en Angular

¿Qué es la inyección de dependencias?

La inyección de dependencias (Dependency Injection) es el mecanismo con el que Angular suministra a una clase las otras clases que necesita para funcionar. En lugar de que cada componente cree sus propias dependencias, el framework se encarga de construirlas y de entregarlas.

El problema que resuelve

Sin inyección, un componente tendría que instanciar manualmente todo lo que usa. Por ejemplo, podría crear su servicio directamente con new dentro de su constructor.

constructor() {
  this.usuariosService = new UsuariosService();
}

Ese enfoque genera problemas. El componente queda acoplado a la implementación concreta, es difícil de probar y cualquier cambio en el constructor del servicio obliga a modificar todos los lugares donde se crea.

Con la inyección de dependencias, Angular crea el servicio y lo entrega donde se pide, sin que la clase tenga que saber cómo se construye.

Cómo se pide una dependencia

La forma clásica es la inyección por constructor.

@Component({
  selector: 'app-usuarios',
  template: '<h2>Lista de usuarios</h2>',
})
export class UsuariosComponent {
  constructor(private usuariosService: UsuariosService) {}
}

Desde Angular 14 existe además la función inject(), que permite obtener la dependencia sin depender del constructor.

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

export class UsuariosComponent {
  private usuariosService = inject(UsuariosService);
}

Cómo se registra un servicio

Un servicio se convierte en inyectable marcándolo con @Injectable. La opción providedIn: 'root' lo registra en el inyector raíz, por lo que estará disponible en toda la aplicación.

@Injectable({
  providedIn: 'root',
})
export class UsuariosService {
  obtenerUsuarios(): string[] {
    return ['María', 'Pedro'];
  }
}

El inyector raíz y el árbol de inyectores

Angular organiza los servicios en un árbol de inyectores. Existe un inyector raíz disponible para toda la aplicación y, además, cada componente puede declarar sus propios proveedores.

Inyector raíz
      ↓
Inyector del componente A
      ↓
Inyector del componente hijo

Cuando una clase pide una dependencia, Angular la busca empezando por el inyector más cercano y subiendo hasta el inyector raíz. Esto significa que un componente puede entregar su propia instancia a sus descendientes sin afectar al resto de la aplicación.

@Component({
  selector: 'app-panel',
  template: '<p>Panel</p>',
  providers: [PanelConfigService],
})
export class PanelComponent {}

Al declarar el proveedor a nivel de componente, cada instancia de PanelComponent recibe su propia configuración.

Otras formas de proveer dependencias

Además de proveer la clase directamente, Angular permite configurar cómo se crea la dependencia.

providers: [
  { provide: LogService, useClass: LogAvanzadoService },
  { provide: API_URL, useValue: 'https://api.ejemplo.com' },
  {
    provide: CacheService,
    useFactory: () => new CacheService({ limite: 100 }),
  },
]
  • useClass: entrega otra implementación de la misma interfaz.
  • useValue: inyecta un valor fijo, útil para constantes o configuración.
  • useFactory: permite construir la dependencia con lógica propia.

Consideraciones

La inyección de dependencias es uno de los pilares de Angular. Mantiene los componentes desacoplados de sus colaboradores, facilita las pruebas y delega en el framework la creación y el ciclo de vida de los servicios. Distinguir entre el inyector raíz y los inyectores locales ayuda a entender cuántas instancias existen realmente y cuál es la configuración que recibe cada pieza.

© Arturo Juárez Monroy