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ística | DI por constructor | Función inject() |
|---|---|---|
| Código repetitivo en herencia | Alto (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 |
| Tipado | Tipado estricto en parámetros | Inferido directamente desde el Token o Clase |
Resumen
La función inject() es el estándar moderno para la inyección de dependencias en Angular:
- Usa
inject(Service)en inicializadores de propiedades para mantener componentes y servicios limpios. - Aplica
inject()en guards de rutas funcionales, interceptores y funciones de utilidad personalizadas. - Respeta el Injection Context y usa
runInInjectionContext()cuando necesites resolver dependencias de forma asíncrona.
These are my experiences learning and facing my daily challenges working with Angular.
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
Entendiendo composición vs. herencia en Angular
Por qué la herencia de clases suele generar 'constructor hell' en Angular, y cómo usar la Directive Composition API (hostDirectives), inject() y Signals para escribir código limpio.
Cómo empezar con Standalone Components en Angular (Guía moderna completa)
Aprende a crear aplicaciones en Angular sin módulos usando Standalone Components. Revisa cómo usar bootstrapApplication, app.config.ts, lazy loading con loadComponent y pruebas unitarias con Vitest.
Cómo probar componentes en Angular usando TestBed
Aprende a probar componentes en Angular con TestBed, manejar componentes hijos y mocks de servicios, y migrar de Karma/Jasmine al moderno Vitest con Standalone y Signals.
Preguntas esenciales de entrevista en Angular para desarrolladores Junior y Mid-Level
Una guía práctica de preguntas de entrevista sobre Angular moderno para roles junior y mid-level, cubriendo Signals, Standalone APIs, Control Flow, Vitest y Zoneless Angular.
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.
Únete a 13,800+ desarrolladores y lectores.
Cero spam. Cancela tu suscripción en cualquier momento.