Los servicios en Angular son clases diseñadas para encapsular lógica de negocio y datos reutilizables. El sistema de Inyección de Dependencias (DI) es el mecanismo central para proporcionar estas dependencias, desacoplando los componentes de su implementación concreta. Esta arquitectura promueve un código modular, mantenible y testeable.
Definición de un Servicio con @Injectable
Un servicio se marca comúnmente con el decorador @Injectable, lo que indica que puede ser gestionado por el inyector de Angular. La propiedad providedIn define su alcance de disponibilidad.
// auth.service.ts
import { Injectable } from '@angular/core';
// Interfaz simple para un usuario
interface Usuario {
id: number;
nombre: string;
email: string;
}
@Injectable({ providedIn: 'root' })
export class AuthService {
private usuarioActivo: Usuario | null = null;
async autenticar(nombre: string, clave: string): Promise<Usuario> {
// Simulación de una petición de autenticación
return new Promise((resuelve) => {
setTimeout(() => {
this.usuarioActivo = {
id: Math.floor(Math.random() * 1000),
nombre: nombre,
email: `${nombre}@ejemplo.com`
};
resuelve(this.usuarioActivo);
}, 800);
});
}
obtenerUsuarioActual(): Usuario | null {
return this.usuarioActivo;
}
cerrarSesion(): void {
this.usuarioActivo = null;
}
}
Opciones de providedIn:
'root': Crea una única instancia del servicio para toda la aplicación.'platform': Instancia única compartida entre múltiples aplicaciones en la misma plataforma.- Un módulo específico (ej.
providedIn: MiModulo): El servciio solo está disponible dentro de ese módulo.
Configuración de Proveedores (Providers)
Además de providedIn, se pueden declarar proveedores en arrays providers de módulos o componentes para mayor flexibilidad.
Proveedor de Clase
La forma más directa, donde se indica la clase que se va a instanciar.
import { NgModule } from '@angular/core';
import { AuthService } from './auth.service';
@NgModule({
providers: [AuthService] // Shorthand para { provide: AuthService, useClass: AuthService }
})
export class AuthFeatureModule {}
Proveedor de Valor
Ideal para constantes o configuraciones inyectables.
import { NgModule, Component, Inject } from '@angular/core';
const CONFIGURACION_APP = {
reintentosConexion: 3,
idioma: 'es'
};
@NgModule({
providers: [
{ provide: 'APP_CONFIG', useValue: CONFIGURACION_APP }
]
})
export class CoreModule {}
// Inyección en un componente
@Component({
selector: 'app-config-display',
template: `<p>Idioma configurado: {{ config.idioma }}</p>`
})
export class ConfigDisplayComponent {
constructor(@Inject('APP_CONFIG') public config: typeof CONFIGURACION_APP) {}
}
Proveedor de Fábrica
Utiliza una función para crear la instancia, útil para lógica de creación condicional.
import { NgModule, Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
interface ServicioRegistro {
registrar(mensaje: string): void;
}
@Injectable()
class RegistroConsola implements ServicioRegistro {
registrar(mensaje: string) {
console.debug(`[REGISTRO] ${mensaje}`);
}
}
@Injectable()
class RegistroRemoto implements ServicioRegistro {
private http = inject(HttpClient);
registrar(mensaje: string) {
this.http.post('/api/registros', { mensaje }).subscribe();
}
}
function crearServicioRegistro(http: HttpClient, entorno: { produccion: boolean }): ServicioRegistro {
return entorno.produccion ? new RegistroRemoto() : new RegistroConsola();
}
@NgModule({
providers: [
{ provide: 'SERVICIO_REGISTRO', useFactory: crearServicioRegistro, deps: [HttpClient, 'ENTORNO'] },
{ provide: 'ENTORNO', useValue: { produccion: false } }
]
})
export class LoggerModule {}
Escenarios Prácticos de Inyección de Dependencias
Alcance de Componente
Declarar un servicio en el array providers de un componente crea una instancia nueva por cada instancia de ese componente. Las dependencias se heredan y pueden ser sobrescritas en componentes hijos.
import { Component, inject } from '@angular/core';
import { AuthService } from './auth.service';
@Component({
selector: 'app-panel-usuario',
standalone: true,
providers: [AuthService], // Proveedor exclusivo para este componente
template: `
<button (click)="iniciarSesion()">Iniciar Sesión</button>
@if (usuario) {
<p>Bienvenido, {{ usuario.nombre }}</p>
}
`
})
export class PanelUsuarioComponent {
private authService = inject(AuthService);
usuario: any = null;
async iniciarSesion() {
this.usuario = await this.authService.autenticar('usuario_prueba', 'secreto123');
}
}
Alcance de Aplicación y Funciones de Configuración
Servicios globales como el cliente HTTP o el router se configuran mediante funciones dedicadas como provideHttpClient() y provideRouter().
// main.ts (Punto de entrada con Angular Standalone)
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient } from '@angular/common/http';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app/app.component';
import { rutas } from './app.routes';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(),
provideRouter(rutas)
]
});
Optimizaciones en Angular: Tree Shaking y Resolución
Servicios "Tree-Shakeable"
Servicios marcados con providedIn: 'root' pueden ser eliminados del paquete final por el empaquetador si no se encuentran referencias a ellos. Esto reduce el tamaño de la aplicación. Este comportamiento no aplica para servicios declarados en arrays providers de módulos.
La Función inject()
Esta función, disponible dentro del contexto de inyección (constructores, funciones fábrica, hooks de ciclo de vida), permite inyectar dependencias de forma más explícita y flexible.
import { Component, inject, OnInit } from '@angular/core';
import { AuthService } from './auth.service';
@Component({
selector: 'app-guard-inicio',
standalone: true,
template: `<p>Estado: Verificado</p>`
})
export class GuardInicioComponent implements OnInit {
private auth = inject(AuthService);
ngOnInit() {
// Uso de la dependencia inyectada
if (!this.auth.obtenerUsuarioActual()) {
this.auth.autenticar('invitado', 'temporal');
}
}
}
Componentes Standalone y DI
Los componentes standalone gestionan sus propias dependencias a través de su array imports, lo que resulta en árboles de dependencias más explícitos y a menudo un mejor rendimiento en la resolución.
Pruebas Unitarias y Simulación de Dependencias
El sistema DI facilita enormemente las pruebas al permitir reemplazar dependencias reales con mocks o spies mediante TestBed.
import { TestBed } from '@angular/core/testing';
import { AuthService } from './auth.service';
import { HttpClient } from '@angular/common/http';
describe('AuthService', () => {
let servicio: AuthService;
let httpSimulado: jasmine.SpyObj<HttpClient>;
beforeEach(() => {
httpSimulado = jasmine.createSpyObj('HttpClient', ['post']);
TestBed.configureTestingModule({
providers: [
AuthService,
{ provide: HttpClient, useValue: httpSimulado }
]
});
servicio = TestBed.inject(AuthService);
});
it('debería autenticar y devolver un usuario', async () => {
httpSimulado.post.and.resolveTo({ id: 1, nombre: 'Test' });
const usuario = await servicio.autenticar('Test', 'pass');
expect(usuario.nombre).toBe('Test');
});
});