🇺🇸 EN
Angular
·5 min read·↗·🇺🇸 EN

Cómo entender y usar la función inject() en Angular

Portada principal del artículo: Cómo entender y usar la función inject() en Angular

⚡ ¿Usas Angular moderno (v17+)? Salta directo a Patrones modernos de inyección: Guards funcionales y utilidades componibles para ver inject() en arquitectura funcional.

Durante años, la inyección de dependencias (DI) en Angular significaba escribir parámetros extensos en el constructor:

// ⚠️ Inyección clásica por constructor
constructor(
  private http: HttpClient,
  private authService: AuthService,
  private router: Router,
  private toast: ToastService
) {}

Con la llegada de la función inject(), Angular modernizó su mecanismo de inyección de dependencias, permitiendo resolver dependencias directamente en inicializadores de propiedades y en funciones componibles independientes.

En esta guía práctica, te mostraré cómo funciona inject(), cómo elimina el código repetitivo del constructor en la herencia de clases y cómo usarlo de forma segura dentro del Injection Context de Angular.


1. Inyección por constructor vs. inicializadores de propiedades con inject()

Aquí tienes una comparación directa de cómo los servicios y componentes declaran sus dependencias:

DI tradicional con constructor (v2 a v14)

// player.service.ts (Tradicional)
@Injectable({ providedIn: 'root' })
export class PlayerService {
  constructor(
    private http: HttpClient,
    private logger: LoggerService
  ) {}
 
  getPlayers() {
    this.logger.log('Fetching players...');
    return this.http.get<Player[]>('/api/players');
  }
}

DI moderna con inject() (v15+ / v19)

// player.service.ts (Moderno)
import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { LoggerService } from './logger.service';
 
@Injectable({ providedIn: 'root' })
export class PlayerService {
  private http = inject(HttpClient);
  private logger = inject(LoggerService);
 
  getPlayers() {
    this.logger.log('Fetching players...');
    return this.http.get<Player[]>('/api/players');
  }
}

2. Cómo resolver el problema del constructor en la herencia de clases

El mayor dolor de cabeza con la inyección por constructor aparece al usar herencia de clases. Cada vez que un servicio o componente base agrega una nueva dependencia, todas las clases hijas derivadas deben actualizar su constructor y su llamada a super():

// ⚠️ Clase base frágil con DI por constructor
export abstract class BaseHttpService {
  constructor(
    protected http: HttpClient,
    protected analytics: AnalyticsService
  ) {}
}
 
export class UserService extends BaseHttpService {
  constructor(http: HttpClient, analytics: AnalyticsService) {
    super(http, analytics); // 😫 Paso innecesario de dependencias
  }
}

La solución con inject():

Como los inicializadores de propiedades se ejecutan durante la instanciación, las clases hijas nunca necesitan declarar ni reenviar las dependencias de la clase base:

// ✅ Clase base limpia con inject()
export abstract class BaseHttpService {
  protected http = inject(HttpClient);
  protected analytics = inject(AnalyticsService);
}
 
export class UserService extends BaseHttpService {
  // ¡Cero código repetitivo en el constructor!
}

Patrones modernos de inyección: Guards funcionales y utilidades componibles

Más allá de las clases, inject() impulsa las APIs funcionales modernas en todo Angular:

1. Guards e interceptores de rutas funcionales

// auth.guard.ts (Guard funcional)
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';
 
export const authGuard: CanActivateFn = () => {
  const authService = inject(AuthService);
  const router = inject(Router);
 
  return authService.isAuthenticated() ? true : router.createUrlTree(['/login']);
};

2. Funciones de utilidad componibles ("Hooks" personalizados)

Puedes crear funciones componibles independientes que encapsulan dependencias y estado reactivo:

// use-window-size.ts
import { inject, signal, NgZone } from '@angular/core';
import { DOCUMENT } from '@angular/common';
 
export function useWindowDimensions() {
  const document = inject(DOCUMENT);
  const window = document.defaultView;
  const width = signal(window ? window.innerWidth : 0);
 
  if (window) {
    window.addEventListener('resize', () => width.set(window.innerWidth));
  }
 
  return { width: width.asReadonly() };
}

Entendiendo el Injection Context (Error NG0203)

inject() solo funciona cuando Angular tiene un Injection Context activo:

  • ✅ Inicializadores de campos (private http = inject(HttpClient))
  • ✅ Constructores
  • ✅ Funciones fábrica de InjectionToken
  • ✅ Guards e interceptores de rutas funcionales

¿Qué causa el error NG0203: inject() must be called from an injection context?

Si intentas llamar a inject() dentro de un ciclo de vida (ngOnInit) o en un callback asíncrono (setTimeout, .subscribe()), Angular lanza un error porque la fase de construcción ya finalizó:

export class UserComponent implements OnInit {
  ngOnInit() {
    // ❌ ERROR NG0203: El contexto de inyección ya se cerró aquí.
    const http = inject(HttpClient);
  }
}

Ejecución manual en contexto con runInInjectionContext

Si necesitas ejecutar código con inject() de forma dinámica, captura el EnvironmentInjector y usa runInInjectionContext:

import { Component, EnvironmentInjector, inject, runInInjectionContext } from '@angular/core';
 
@Component({ ... })
export class DynamicComponent {
  private injector = inject(EnvironmentInjector);
 
  executeAsyncAction() {
    setTimeout(() => {
      runInInjectionContext(this.injector, () => {
        const auth = inject(AuthService); // ✅ Válido dentro de runInInjectionContext
        auth.checkSession();
      });
    }, 1000);
  }
}

Comparativa: DI por constructor vs. inject()

CaracterísticaDI por constructorFunción inject()
Código repetitivo en herenciaAlto (se debe reenviar mediante super())Cero (los campos base se inicializan directamente)
Guards funcionales❌ No soportado (solo clases)✅ Totalmente soportado
Utilidades componibles❌ Difícil de compartir✅ Fácil de exportar como funciones componibles
TipadoTipado estricto en parámetrosInferido directamente desde el Token o Clase

Resumen

La función inject() es el estándar moderno para la inyección de dependencias en Angular:

  1. Usa inject(Service) en inicializadores de propiedades para mantener componentes y servicios limpios.
  2. Aplica inject() en guards de rutas funcionales, interceptores y funciones de utilidad personalizadas.
  3. Respeta el Injection Context y usa runInInjectionContext() cuando necesites resolver dependencias de forma asíncrona.
Parte de la serie Angular

These are my experiences learning and facing my daily challenges working with Angular.

Ver serie completa

Preguntas Frecuentes

¿Qué es la función inject() en Angular y cómo reemplaza la inyección por constructor?

La función inject() resuelve dependencias directamente en inicializadores de propiedades o funciones fábrica sin declarar parámetros en el constructor, lo que reduce el código repetitivo y simplifica la herencia de clases.

¿En qué lugares se puede llamar válidamente a la función inject() en Angular?

La función inject() debe ejecutarse durante un contexto de inyección activo, como inicializadores de propiedades (campos), constructores, funciones fábrica de InjectionToken o guards e interceptores funcionales.

¿Se puede llamar a inject() dentro de ciclos de vida como ngOnInit o en callbacks asíncronos?

No. El contexto de inyección se destruye una vez finalizada la construcción. Llamar a inject() dentro de ngOnInit() o en callbacks asíncronos como setTimeout o suscripciones lanzará el error NG0203, a menos que se envuelva con runInInjectionContext().

¿Cómo simplifica inject() la herencia de clases y las funciones componibles en Angular?

Las clases base que usan inject() no requieren que las clases hijas pasen dependencias a través de super(), eliminando problemas de mantenimiento del constructor en árboles de herencia extensos.

Artículos Relacionados

Compartir este artículo

Si esta guía te resultó útil, compártela con tu equipo o con otros desarrolladores.


Software real. Lecciones reales.

Comparto las lecciones que aprendí por las malas, para que puedas evitarlas o estar preparado cuando ocurran.

User avatar
User avatar
User avatar
User avatar
+13K

Únete a 13,800+ desarrolladores y lectores.

Cero spam. Cancela tu suscripción en cualquier momento.