Mover Angular HttpClient al backend de Fetch con withFetch()
Descubra por qué HttpClient de Angular está pasando de XMLHttpRequest a fetch, cómo fetch() permite el SSR en los bordes y la transmisión por streaming, y qué implica esto para sus interceptores.
Fuentes: Stop Using XMLHttpRequest: Why Angular 22 Shifted to the Native Fetch API
Durante la mayor parte de su existencia, HttpClient de Angular ha sido una capa amigable basada en RxJS que se encima de XMLHttpRequest, una API del navegador que precede a casi todo lo demás en la plataforma web moderna. Esa elección se convierte cada vez más en un problema cuando su aplicación se renderiza en el servidor, funciona en entornos de ejecución periféricos o consume respuestas en streaming. Esta guía explica por qué es importante el backend basado en fetch, cómo cambiar a él con una sola opción de proveedor y qué cambia y qué no cambia en sus interceptores existentes.
Por qué XMLHttpRequest se convirtió en un problema
XHR data de 1999, cuando Microsoft lo creó para impulsar Outlook Web Access. Su diseño refleja su antigüedad: es con estado, está basado en eventos y resulta complicado de utilizar, además de que el ecosistema más amplio de JavaScript se ha ido alejando de él durante casi una década. La mayoría de las bibliotecas y entornos de ejecución optaron hace tiempo por el fetch() basado en promesas. Angular mantuvo XHR en su interior principalmente debido a su firme compromiso con la compatibilidad hacia atrás.
Ese equilibrio cambió cuando Angular adoptó la detección de cambios sin zonas y el renderizado del lado servidor. En ese escenario, XHR ya no es solo código antiguo al que nadie quiere tocar; limita activamente dónde y cómo puede ejecutarse una aplicación Angular. Un backend basado en fetch permite al framework modernizar su stack de red, manejar datos en flujo correctamente y ejecutarse en plataformas edge.
En el momento de redactar este texto, las últimas versiones de Angular están adoptando a fetch como el motor preferido para HttpClient. Los valores predeterminados y las funciones obsoletas varían entre versiones principales, por lo que es necesario consultar las notas de lanzamiento de la versión que se está utilizando en lugar de asumir cuál backend está activo.
Los entornos de ejecución de Edge no incluyen XMLHttpRequest
El alojamiento de backends ha ido pasando de procesos completos de Node.js en contenedores Docker a aislamientos ligeros de V8 como Cloudflare Workers, Deno y Fastly Compute. Si desea conocer más detalles sobre ese modelo de entorno de ejecución, consulte nuestra comparación de aislamientos de Edge, WebAssembly y entornos de ejecución de Node.js.
Estas plataformas están construidas sobre APIs modernas basadas en estándares web, y XMLHttpRequest no forma parte de ellas. Una compilación tradicional de Angular SSR desplegada en una plataforma de este tipo falla en el momento en que HttpClient realiza su primera llamada a la API mientras se renderiza la página, ya que el mecanismo de transporte subyacente simplemente no existe.
fetch(), por el contrario, está disponible tanto en navegadores como en Node.js, Deno y los entornos de ejecución edge. Una vez que HttpClient lo utiliza, la misma aplicación SSR se vuelve portable: se puede renderizar en Cloudflare Workers o Deno Deploy y realizar llamadas a la API desde ubicaciones geográficamente más cercanas a los usuarios.
Cambiar el backend con withFetch()
El cambio de configuración consiste en una modificación, no en una refactorización. Los componentes y servicios siguen llamando a this.http.get() y funciones similares tal como antes; solo cambia la configuración del proveedor en la configuración de tu aplicación.
El fragmento a continuación registra HttpClient mediante provideHttpClient() y pasa dos características. withFetch() selecciona el transporte basado en fetch, mientras que withInterceptors() registra un interceptor de autenticación funcional definido más adelante.
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
import { authInterceptor } from './core/auth.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
// Injects HttpClient using the modern Fetch API backend
provideHttpClient(
withFetch(),
withInterceptors([authInterceptor])
)
]
};
Internamente, withFetch() indica a Angular que utilice FetchBackend en lugar de HttpXhrBackend como el componente que realiza realmente la solicitud de red. Todo lo que está por encima de esa capa permanece igual.
Una limitación que vale la pena conocer antes de activarlo: el backend de fetch no informa sobre los eventos de progreso de la carga, ya que fetch() no ofrece un equivalente al sistema de progreso de carga de XHR. Si su aplicación muestra una barra de progreso para las cargas de archivos, pruebe ese flujo de manera explícita.
Respuestas en streaming sin almacenar todo en búfer
XHR fue diseñado para respuestas completas y autónomas. Funciona mal con datos grandes o continuos: descargar un archivo de 50 MB o consumir una respuesta dividida en partes tiende a acumular un gran búfer en la memoria antes de que JavaScript pueda trabajar con él, lo que genera presión en la memoria y una interfaz lenta.
fetch() fue diseñado teniendo como núcleo a ReadableStream, de modo que el contenido puede ser consumido por partes a medida que llega desde la red. Gracias al backend de fetch, Angular puede procesar los datos recibidos en el momento en que llegan, en lugar de esperar a que se cargue todo el contenido.
Esto es especialmente importante para interfaces de tipo IA. Una interfaz de chat que lee Eventos enviados por el servidor o salidas fragmentadas de modelos de lenguaje grande necesita que los tokens aparezcan a medida que se generan, y un transporte con capacidad de transmisión en tiempo real es la base para ello, evitando acumular todo en la memoria del navegador.
Pero sea preciso respecto a lo que obtendrá. Un observable regular de http.get() sigue emitiendo la respuesta final una vez que el cuerpo está completo; el acceso incremental se realiza a través de eventos de progreso (reportProgress con observe: 'events'), y para flujos de tokens de larga duración, muchos equipos siguen optando por EventSource o una llamada directa a fetch(). Verifique el comportamiento que necesita consultando la documentación de su versión de Angular.
Qué ocurre con sus interceptores
Cada vez que cambia el motor de red, la primera pregunta que se hace un ingeniero experimentado es sobre los middleware: ¿seguirán funcionando los interceptores de autenticación y registro?
Con interceptores funcionales, la respuesta es sí. La intercepción opera sobre las abstracciones propias de Angular, HttpRequest y HttpResponse, que se encuentran por encima del nivel de transporte. Los interceptores transforman el objeto de solicitud de Angular, y solo después de que toda la cadena se haya ejecutado llega la solicitud final a FetchBackend, el cual la convierte en una llamada a fetch(). El interceptor nunca toca la solicitud nativa.
El ejemplo a continuación es independiente del backend. Lee un JWT, clona la solicitud inmutable con un encabezado Authorization y pasa esa copia a next, el cual la reenvía al siguiente interceptor y, finalmente, al backend.
import { HttpInterceptorFn } from '@angular/common/http';
// This interceptor works perfectly with both XHR and Fetch backends
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = localStorage.getItem('jwt');
// Clone and mutate the Angular abstraction, not the native request
const secureReq = req.clone({
setHeaders: { Authorization: `Bearer ${token}` }
});
// Hand off to the next interceptor, and ultimately, the Fetch backend
return next(secureReq);
};
Hay dos consideraciones importantes relacionadas con este fragmento específico y no con el backend de fetch. Primero, localStorage no existe durante la renderización del lado servidor, por lo que en entornos de SSR o edge deployments se debe proteger la llamada o inyectar el token de manera diferente. Segundo, almacenar tokens en localStorage los expone a cualquier vulnerabilidad XSS, y el código envía Bearer null cuando no hay token; el código de producción debe manejar ambos casos.
Los interceptores basados en clases siguen funcionando
Los interceptores antiguos, escritos como clases que implementan HttpInterceptor, siguen funcionando con el backend fetch. Sin embargo, parecen estar cada vez más desactualizados en relación con el resto del framework. Combinar withFetch() con interceptores funcionales registrados mediante withInterceptors() es el enfoque que se ajusta a las convenciones actuales de Angular, por lo que vale la pena migrar los basados en clases cuando se modifiquen.
Puntos clave
- Mover
HttpClientde XHR a fetch representa una actualización sutil pero significativa: una sola opción de proveedor, sin cambios en el código de llamada. - Agregue
withFetch()aprovideHttpClient()si su versión aún no utiliza fetch por defecto. - Fetch es lo que permite que Angular SSR funcione en Cloudflare Workers, Deno y otros entornos de ejecución perimetral similares que carecen de
XMLHttpRequest.
ReadableStream, lo que facilita el manejo de cargas grandes y respuestas en fragmentos, como las salidas de los LLM, pero hay que verificar cómo HttpClient muestra datos parciales antes de crear una interfaz de usuario basada en streaming.HttpInterceptorFn) siguen funcionando sin cambios; pruebe el progreso de las subidas y cualquier ruta de código exclusiva para SSR después del cambio.