Bloque 4 – Servicios y comunicación con backend (Sesiones 15–19)
Este bloque introduce el uso de servicios en Angular como mecanismo principal para encapsular la lógica de negocio y la comunicación con un backend. Se trabaja con peticiones HTTP reales contra una API de gestión de recursos sanitarios y se profundiza en conceptos clave como Observables, manejo de errores y arquitectura escalable de servicios.
La API base utilizada en todos los ejemplos es:
http://localhost:8080/panacea
Las rutas concretas de los endpoints se obtienen desde un archivo de configuración centralizado.
Sesión 15 – Servicios en Angular
Qué es un servicio
Un servicio es una clase cuya responsabilidad principal es encapsular lógica que no pertenece directamente a la vista. Habitualmente se utilizan para acceder a datos remotos, compartir estado entre componentes o implementar reglas de negocio.
El uso de servicios favorece la reutilización de código y mantiene los componentes más simples y enfocados únicamente en la presentación.
Inyección de dependencias
Angular utiliza un sistema de inyección de dependencias que permite que los servicios se proporcionen automáticamente a los componentes u otros servicios que los necesiten. Esto reduce el acoplamiento y facilita el testeo.
Servicios singleton
Cuando un servicio se declara con providedIn: 'root', Angular crea una única instancia compartida en toda la aplicación. Este comportamiento es el más habitual para servicios de acceso a datos.
Creación de servicios
Ejemplo de servicio básico creado con CLI:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class RecursosService {
constructor() {}
}
Sesión 16 – HttpClient
Introducción
HttpClient es el servicio proporcionado por Angular para realizar peticiones HTTP. Está basado en Observables y permite tipar las respuestas, interceptar peticiones y manejar errores de forma centralizada.
En aplicaciones modernas con componentes standalone, se configura directamente en el bootstrap de la aplicación.
Configuración de HttpClient
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient } from '@angular/common/http';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient()
]
});
Archivo de configuración de endpoints
{
"apiBase": "http://localhost:8080/panacea",
"recursos": "/recursos"
}
import config from '../config.json';
Peticiones GET y POST
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import config from '../config.json';
import { Observable } from 'rxjs';
export interface Recurso {
id: number;
nombre: string;
tipo: string;
}
@Injectable({
providedIn: 'root'
})
export class RecursosService {
private baseUrl = config.apiBase + config.recursos;
constructor(private http: HttpClient) {}
obtenerRecursos(): Observable<Recurso[]> {
return this.http.get<Recurso[]>(this.baseUrl);
}
crearRecurso(recurso: Partial<Recurso>): Observable<Recurso> {
return this.http.post<Recurso>(this.baseUrl, recurso);
}
}
Tipado de datos
El tipado de las respuestas permite detectar errores en tiempo de compilación y mejora la experiencia de desarrollo mediante autocompletado y validaciones.
Sesión 17 – Observables y RxJS
Qué es RxJS y por qué Angular lo usa
RxJS es una librería de programación reactiva basada en flujos de datos asíncronos. Angular la utiliza para gestionar eventos, peticiones HTTP y estados cambiantes de forma declarativa.
Observable vs Promise
Un Observable puede emitir múltiples valores a lo largo del tiempo y puede cancelarse. Una Promise solo se resuelve una vez y no es cancelable.
subscribe
this.recursosService.obtenerRecursos().subscribe({
next: recursos => {
this.recursos = recursos;
},
error: err => {
console.error(err);
}
});
Operadores básicos
import { map, tap, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
obtenerRecursos() {
return this.http.get<Recurso[]>(this.baseUrl).pipe(
tap(() => console.log('Petición realizada')),
map(recursos => recursos.filter(r => r.tipo === 'SANITARIO')),
catchError(() => of([]))
);
}
Sesión 18 – Manejo de errores y estados de carga
Introducción
Cuando se trabaja con datos remotos es fundamental gestionar correctamente los errores y los estados de carga para ofrecer una buena experiencia de usuario.
Interceptor de errores HTTP
Un interceptor permite capturar todas las peticiones HTTP y sus respuestas de forma centralizada.
import { HttpInterceptorFn } from '@angular/common/http';
import { catchError, throwError } from 'rxjs';
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
return next(req).pipe(
catchError(error => {
console.error('Error HTTP', error);
return throwError(() => error);
})
);
};
Configuración del interceptor:
import { provideHttpClient, withInterceptors } from '@angular/common/http';
provideHttpClient(
withInterceptors([errorInterceptor])
);
Interceptor de autenticación (visión general)
Un interceptor de autenticación suele encargarse de añadir automáticamente un header Authorization con un token en cada petición saliente. Su lógica se centra en clonar la petición original y añadir el encabezado correspondiente.
Estados de carga
loading = false;
cargarRecursos() {
this.loading = true;
this.recursosService.obtenerRecursos().subscribe({
next: data => this.recursos = data,
error: () => this.loading = false,
complete: () => this.loading = false
});
}
Sesión 19 – Arquitectura de servicios
Separación de responsabilidades
Un servicio debe tener una única responsabilidad clara. La lógica de acceso a datos no debe mezclarse con lógica de presentación ni con reglas complejas de negocio.
Servicio genérico de acceso HTTP
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import config from '../config.json';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ApiService {
private apiBase = config.apiBase;
constructor(private http: HttpClient) {}
private buildUrl(params: Record<string, any>): string {
let url = this.apiBase;
Object.values(params).forEach(value => {
url += `/${value}`;
});
return url;
}
get<T>(params: Record<string, any>): Observable<T> {
return this.http.get<T>(this.buildUrl(params));
}
post<T>(params: Record<string, any>, body: any): Observable<T> {
return this.http.post<T>(this.buildUrl(params), body);
}
patch<T>(params: Record<string, any>, body: any): Observable<T> {
return this.http.patch<T>(this.buildUrl(params), body);
}
delete<T>(params: Record<string, any>): Observable<T> {
return this.http.delete<T>(this.buildUrl(params));
}
}
Uso desde un servicio de dominio
@Injectable({
providedIn: 'root'
})
export class RecursosService {
constructor(private api: ApiService) {}
listar() {
return this.api.get({ recursos: 'recursos' });
}
}
Anti‑patrones comunes
- Servicios excesivamente grandes y con múltiples responsabilidades.
- Lógica HTTP directamente en los componentes.
- Duplicación de llamadas a la API en distintos servicios.
Escalabilidad
Una arquitectura basada en servicios pequeños, bien definidos y apoyados en servicios genéricos facilita el mantenimiento y la evolución de la aplicación a largo plazo.