CodeForge

Angular 21 de Cero a Experto / HTTP y datos

Interceptores funcionales: el filtro de toda petición

Teoría22 min20 XP

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:

auth.interceptor.ts
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:

app.config.ts
import { provideHttpClient, withInterceptors } from '@angular/common/http';

provideHttpClient(
withInterceptors([authInterceptor, loggingInterceptor, errorInterceptor]),
);
// se ejecutan EN ORDEN al salir; en orden inverso al volver la respuesta

Interceptor de logging y de errores

otros.interceptor.ts
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.