Angular 21 de Cero a Experto / HTTP y datos
Interceptores funcionales: el filtro de toda petición
Hay cosas que quieres hacer en TODAS las peticiones: añadir el token de autenticación,
registrar cuánto tardan, manejar los errores 401 de forma global. Repetir eso en cada llamada
sería un desastre de mantenimiento. Un INTERCEPTOR es un filtro por el que pasan todas las
peticiones (y respuestas), donde centralizas esa lógica una vez. En Angular moderno son simples
funciones —gracias a inject()—. Hoy los dominas.
El puesto de control en la carretera
Un interceptor funcional
Un interceptor es una función que recibe la petición y un next (el siguiente paso de la
cadena). Devuelve el Observable de la respuesta, opcionalmente modificando la petición antes:
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from './auth.service';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const auth = inject(AuthService);
const token = auth.token();
// si hay token, CLONA la petición añadiéndole la cabecera (las req son inmutables)
const conAuth = token
? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })
: req;
return next(conAuth); // pasa al siguiente eslabón (otro interceptor o la petición real)
};Detalle clave: las peticiones (req) son INMUTABLES. No las modificas: creas una copia con
req.clone({ ... }) y esa es la que pasas a next. Es el mismo principio de inmutabilidad que
ya conoces. El inject(AuthService) funciona porque el interceptor corre en contexto de
inyección —la recompensa de inject() fuera de las clases—.
Registrarlo
Los interceptores se registran en provideHttpClient con withInterceptors, en orden:
import { provideHttpClient, withInterceptors } from '@angular/common/http';
provideHttpClient(
withInterceptors([authInterceptor, loggingInterceptor, errorInterceptor]),
);
// se ejecutan EN ORDEN al salir; en orden inverso al volver la respuestaInterceptor de logging y de errores
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { catchError, tap, throwError } from 'rxjs';
// mide cuánto tarda cada petición
export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
const inicio = Date.now();
return next(req).pipe(
tap({ complete: () => console.log(`${req.method} ${req.url} — ${Date.now() - inicio}ms`) }),
);
};
// maneja errores globalmente: un 401 manda al login
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
const router = inject(Router);
return next(req).pipe(
catchError((err) => {
if (err.status === 401) router.navigate(['/login']);
return throwError(() => err); // re-lanza para que el llamador también lo vea
}),
);
};El interceptor de errores centraliza lo que sería tedioso repetir: cualquier petición que
reciba un 401 (sesión expirada) redirige al login, sin que cada componente lo maneje. Y como
re-lanza el error con throwError, el catchError local del componente igual puede reaccionar
si lo necesita.
Antes (interceptor de clase) vs Ahora (funcional)
¿Por qué en el interceptor se usa `req.clone({ setHeaders: {...} })` en vez de modificar `req` directamente?
Mini-reto
Diseña tres interceptores funcionales para "Mi Bolsillo": 1) authInterceptor que añada
Authorization: Bearer <token> si hay sesión; 2) baseUrlInterceptor que anteponga la URL
base de la API a las rutas relativas (usando req.clone({ url: ... })); 3) errorInterceptor
que ante un 401 borre la sesión y redirija al login. Escribe el orden en que los registrarías y
por qué.
Qué sigue
Ya centralizas la lógica transversal de tus peticiones. Otra optimización clave: no pedir dos
veces lo mismo. La próxima lección es la CACHÉ —guardar respuestas para reutilizarlas—, con
shareReplay y un servicio de caché propio, para que tu app sea rápida y ahorre peticiones.