|
|
|
# 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:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
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
|
|
|
|
|
|
|
|
```ts
|
|
|
|
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
|
|
|
|
|
|
|
|
```json
|
|
|
|
{
|
|
|
|
"apiBase": "http://localhost:8080/panacea",
|
|
|
|
"recursos": "/recursos"
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
```ts
|
|
|
|
import config from '../config.json';
|
|
|
|
```
|
|
|
|
|
|
|
|
### Peticiones GET y POST
|
|
|
|
|
|
|
|
```ts
|
|
|
|
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
|
|
|
|
|
|
|
|
```ts
|
|
|
|
this.recursosService.obtenerRecursos().subscribe({
|
|
|
|
next: recursos => {
|
|
|
|
this.recursos = recursos;
|
|
|
|
},
|
|
|
|
error: err => {
|
|
|
|
console.error(err);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
```
|
|
|
|
|
|
|
|
### Operadores básicos
|
|
|
|
|
|
|
|
```ts
|
|
|
|
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.
|
|
|
|
|
|
|
|
```ts
|
|
|
|
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:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
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
|
|
|
|
|
|
|
|
```ts
|
|
|
|
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
|
|
|
|
|
|
|
|
```ts
|
|
|
|
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
|
|
|
|
|
|
|
|
```ts
|
|
|
|
@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. |