Qué es la inyección de dependencias
La inyección de dependencias es un patrón de diseño fundamental en Angular que permite a los componentes recibir las instancias de servicios que necesitan sin tener que crearlas directamente. Este patrón resuelve un problema común en el desarrollo de aplicaciones: cómo hacer que diferentes partes de nuestro código trabajen juntas de manera eficiente y mantenible.
El problema sin inyección de dependencias
Imagina que tenemos un componente que necesita utilizar un servicio para obtener datos de usuarios. Sin inyección de dependencias, tendríamos que crear manualmente la instancia del servicio dentro del componente:
import { Component } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-user-list',
template: `<h2>Lista de usuarios</h2>`
})
export class UserListComponent {
private userService: UserService;
constructor() {
// Crear manualmente la instancia - problemático
this.userService = new UserService();
}
}
Este enfoque presenta varios problemas importantes:
- Acoplamiento fuerte: El componente está directamente acoplado a la implementación específica del servicio
- Dificultad para testing: Es muy difícil crear tests unitarios porque no podemos sustituir el servicio por un mock
- Gestión manual de dependencias: Si el servicio necesita otros servicios, tendríamos que crearlos todos manualmente
- Reutilización limitada: Cada componente crearía su propia instancia del servicio, perdiendo los beneficios del patrón singleton
La solución: inyección de dependencias
Angular resuelve estos problemas mediante su sistema de inyección de dependencias. En lugar de crear las dependencias manualmente, declaramos qué servicios necesitamos y Angular se encarga de proporcionárnoslos automáticamente.
El framework mantiene un contenedor de dependencias (también llamado inyector) que:
- Registra servicios: Conoce qué servicios están disponibles en la aplicación
- Resuelve dependencias: Determina qué servicios necesita cada componente
- Crea instancias: Instancia los servicios cuando son necesarios
- Gestiona el ciclo de vida: Controla cuándo y cómo se crean y destruyen las instancias
Beneficios de la inyección de dependencias
La inyección de dependencias aporta ventajas significativas al desarrollo:
- Desacoplamiento: Los componentes no necesitan conocer cómo crear sus dependencias
- Testabilidad: Podemos inyectar mocks o stubs fácilmente durante las pruebas
- Mantenibilidad: Los cambios en un servicio no afectan a los componentes que lo utilizan
- Reutilización: Las instancias de servicios se pueden compartir entre múltiples componentes
- Inversión de control: El framework controla la creación y gestión de objetos, no nuestro código
Cómo funciona en Angular
En Angular, la inyección de dependencias funciona mediante metadatos y decoradores. Cuando declaramos que un componente necesita un servicio, Angular utiliza esta información para:
- Identificar la dependencia: A través del tipo TypeScript y decoradores
- Localizar el proveedor: Buscar dónde está registrado el servicio
- Crear o reutilizar: Instanciar el servicio si es necesario o usar una instancia existente
- Inyectar: Pasar la instancia al componente que la solicita
El siguiente ejemplo muestra cómo un componente declara su necesidad de un servicio sin crearlo directamente:
import { Component } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-user-list',
template: `<h2>Lista de usuarios: {{ users.length }}</h2>`
})
export class UserListComponent {
users: any[] = [];
constructor(private userService: UserService) {
// Angular inyecta automáticamente UserService
this.users = this.userService.getUsers();
}
}
En este caso, Angular se encarga automáticamente de:
- Crear una instancia de
UserService
(si no existe ya) - Pasarla al constructor del componente
- Gestionar su ciclo de vida
Este patrón hace que nuestro código sea más limpio, testeable y mantenible, estableciendo las bases para una arquitectura sólida en aplicaciones Angular.
Inyección por constructor
La inyección por constructor es el método tradicional y más ampliamente utilizado para recibir dependencias en Angular. Este enfoque aprovecha el sistema de tipos de TypeScript y los metadatos de los decoradores para que Angular identifique automáticamente qué servicios necesita cada componente.
Sintaxis básica
Para inyectar un servicio por constructor, simplemente lo declaramos como parámetro del constructor del componente, especificando su tipo:
import { Component } from '@angular/core';
import { UserService } from './services/user.service';
@Component({
selector: 'app-user-dashboard',
standalone: true,
template: `
<div>
<h2>Panel de usuarios</h2>
<p>Total de usuarios: {{ totalUsers }}</p>
</div>
`
})
export class UserDashboardComponent {
totalUsers = 0;
constructor(private userService: UserService) {
// Angular inyecta automáticamente UserService
this.totalUsers = this.userService.getTotalUsers();
}
}
En este ejemplo, Angular reconoce automáticamente que el componente necesita UserService
y se lo proporciona cuando crea la instancia del componente.
Múltiples dependencias
Un componente puede recibir múltiples servicios simplemente añadiendo más parámetros al constructor:
import { Component } from '@angular/core';
import { UserService } from './services/user.service';
import { LoggerService } from './services/logger.service';
import { ConfigService } from './services/config.service';
@Component({
selector: 'app-admin-panel',
standalone: true,
template: `
<div>
<h2>Panel de administración</h2>
<p>Modo: {{ mode }}</p>
@if (users.length > 0) {
<ul>
@for (user of users; track user.id) {
<li>{{ user.name }}</li>
}
</ul>
}
</div>
`
})
export class AdminPanelComponent {
users: any[] = [];
mode = '';
constructor(
private userService: UserService,
private logger: LoggerService,
private config: ConfigService
) {
this.mode = this.config.getMode();
this.users = this.userService.getAllUsers();
this.logger.info('Admin panel initialized');
}
}
Angular resuelve automáticamente todas las dependencias y las inyecta en el orden correcto.
Modificadores de acceso
Los parámetros del constructor pueden usar diferentes modificadores de acceso que afectan cómo se pueden utilizar en el componente:
@Component({
selector: 'app-product-list',
standalone: true,
template: `
<div>
@for (product of products; track product.id) {
<div class="product">{{ product.name }}</div>
}
</div>
`
})
export class ProductListComponent {
products: any[] = [];
constructor(
private productService: ProductService, // Privado - solo accesible dentro de la clase
protected logger: LoggerService, // Protegido - accesible en clases heredadas
public config: ConfigService // Público - accesible desde el template
) {
this.loadProducts();
}
private loadProducts() {
this.products = this.productService.getProducts();
this.logger.debug('Products loaded successfully');
}
}
La práctica más recomendada es usar private
para mantener la encapsulación, a menos que necesites acceder al servicio desde el template o clases heredadas.
Inicialización en el constructor
El constructor es el lugar apropiado para realizar la inicialización básica del componente utilizando los servicios inyectados:
@Component({
selector: 'app-dashboard',
standalone: true,
template: `
<div>
<h1>Bienvenido, {{ userName }}</h1>
@if (isLoading) {
<p>Cargando datos...</p>
} @else {
<div class="stats">
<p>Pedidos pendientes: {{ pendingOrders }}</p>
<p>Última conexión: {{ lastLogin }}</p>
</div>
}
</div>
`
})
export class DashboardComponent {
userName = '';
pendingOrders = 0;
lastLogin = '';
isLoading = true;
constructor(
private userService: UserService,
private orderService: OrderService
) {
// Inicialización básica con datos síncronos
this.userName = this.userService.getCurrentUserName();
// Para operaciones asíncronas, usar ngOnInit
this.loadDashboardData();
}
private loadDashboardData() {
this.orderService.getPendingOrders().subscribe(orders => {
this.pendingOrders = orders.length;
this.isLoading = false;
});
this.userService.getLastLogin().subscribe(date => {
this.lastLogin = date;
});
}
}
Ventajas de la inyección por constructor
Este enfoque ofrece varios beneficios importantes:
- Claridad: Las dependencias están explícitamente declaradas y son visibles de inmediato
- Inmutabilidad: Una vez inyectadas, las dependencias no pueden ser reasignadas
- Compatibilidad: Funciona con todas las versiones de Angular y es ampliamente conocido
- Tooling: Los IDEs proporcionan excelente soporte para autocompletado y refactoring
- Testing: Es fácil crear mocks y stubs para las pruebas unitarias
Ejemplo práctico completo
Veamos un ejemplo que integra todo lo aprendido en un componente de lista de tareas:
import { Component } from '@angular/core';
import { TaskService } from './services/task.service';
import { NotificationService } from './services/notification.service';
import { AuthService } from './services/auth.service';
@Component({
selector: 'app-task-list',
standalone: true,
template: `
<div class="task-container">
<h2>Mis tareas</h2>
@if (canCreateTasks) {
<button (click)="addTask()" class="add-btn">Añadir tarea</button>
}
@if (tasks.length === 0) {
<p>No tienes tareas pendientes</p>
} @else {
<ul class="task-list">
@for (task of tasks; track task.id) {
<li [class.completed]="task.completed">
{{ task.title }}
<button (click)="toggleTask(task.id)">
{{ task.completed ? 'Reactivar' : 'Completar' }}
</button>
</li>
}
</ul>
}
</div>
`
})
export class TaskListComponent {
tasks: any[] = [];
canCreateTasks = false;
constructor(
private taskService: TaskService,
private notificationService: NotificationService,
private authService: AuthService
) {
// Inicializar datos básicos
this.canCreateTasks = this.authService.hasPermission('create_tasks');
this.loadTasks();
}
private loadTasks() {
this.tasks = this.taskService.getUserTasks();
}
addTask() {
const newTask = this.taskService.createTask('Nueva tarea');
this.tasks.push(newTask);
this.notificationService.showSuccess('Tarea creada correctamente');
}
toggleTask(taskId: string) {
const task = this.taskService.toggleTaskStatus(taskId);
this.loadTasks(); // Recargar la lista
const message = task.completed ? 'Tarea completada' : 'Tarea reactivada';
this.notificationService.showInfo(message);
}
}
En este ejemplo, el componente recibe tres servicios diferentes a través del constructor y los utiliza para gestionar tareas, mostrar notificaciones y verificar permisos de usuario. Angular se encarga automáticamente de proporcionar las instancias correctas de cada servicio, permitiendo que el componente se centre en su lógica específica sin preocuparse por la creación o gestión de dependencias.

Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Angular es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Angular
Explora más contenido relacionado con Angular y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender qué es la inyección de dependencias y por qué es importante en Angular.
- Identificar los problemas de crear servicios manualmente dentro de componentes.
- Aprender a inyectar servicios en componentes usando el constructor.
- Conocer cómo Angular gestiona automáticamente la creación y ciclo de vida de las dependencias.
- Aplicar la inyección por constructor para múltiples servicios y entender los modificadores de acceso.