Angular HttpClient in den Fetch-Backend mit withFetch() verschieben
Erfahren Sie, warum Angulars HttpClient von XMLHttpRequest zu fetch wechselt, wie withFetch() Edge SSR und Streaming ermöglicht und was das für Ihre Interceptor-Funktionen bedeutet.
Quellen: Stop Using XMLHttpRequest: Why Angular 22 Shifted to the Native Fetch API
Während des größten Teils seiner Existenz war Angulars HttpClient eine benutzerfreundliche RxJS-Schicht, die auf XMLHttpRequest basierte – einer Browser-API, die fast allen anderen Komponenten der modernen Webplattform vorausging. Diese Wahl wird zunehmend zu einem Nachteil, sobald Ihre Anwendung auf dem Server gerendert wird, in Edge-Runtimes ausgeführt wird oder strömende Antworten verarbeitet. Dieser Leitfaden erläutert, warum ein auf fetch basierender Backend-Ansatz wichtig ist, wie man mit einer einzigen Provider-Einstellung darauf umsteigt und was sich bei Ihren bestehenden Interceptoren ändert und was nicht.
Warum XMLHttpRequest zu einem Nachteil wurde
XHR stammt aus dem Jahr 1999, als Microsoft es entwickelte, um Outlook Web Access zu unterstützen. Sein Design zeigt sein Alter: Es ist zustandsbasiert, ereignisgesteuert und schwer zu gestalten, und das umfangreichere JavaScript-Ökosystem hat sich bereits seit fast einem Jahrzehnt von ihm entfernt. Die meisten Bibliotheken und Laufzeiten haben sich schon vor langer Zeit für die versprechensbasierte fetch()-Funktion entschieden. Angular behielt XHR hauptsächlich wegen seines starken Engagements für Rückwärtskompatibilität im Hintergrund.
Dieser Kompromiss sah anders aus, als Angular auf zonenloses Änderungserkennungsverfahren und Serverseitige Darstellung setzte. In dieser Welt ist XHR nicht mehr nur alter Code, den niemand anfassen möchte; es beschränkt aktiv, wo und wie eine Angular-Anwendung ausgeführt werden kann. Ein auf fetch basierender Backend ermöglicht es dem Framework, seinen Netzwerkstack zu modernisieren, gestreamte Daten ordnungsgemäß zu verarbeiten und auf Edge-Plattformen zu laufen.
Zum Zeitpunkt der Erstellung dieses Textes neigen die neuesten Angular-Versionen dazu, fetch als bevorzugten Engine für HttpClient zu verwenden. Standardwerte und veraltete Funktionen ändern sich zwischen den Hauptversionen, daher sollten Sie die Veröffentlichungshinweise der von Ihnen genutzten Version prüfen, anstatt davon auszugehen, welcher Backend aktiv ist.
Edge-Runtimes liefern keine XMLHttpRequest
Die Backend-Hosting-Lösungen wechseln zunehmend von vollständigen Node.js-Prozessen in Docker-Containern zu leichten V8-Isolaten wie Cloudflare Workers, Deno und Fastly Compute. Wenn Sie sich genauer mit diesem Laufzeitmodell beschäftigen möchten, sehen Sie unseren Vergleich zu Edge-Isolaten, WebAssembly und Node.js-Laufzeiten.
Diese Plattformen basieren auf modernen Web-Standard-APIs, und XMLHttpRequest gehört nicht dazu. Eine traditionelle Angular-SSR-Kompileierung, die auf einer solchen Plattform bereitgestellt wird, scheitert bereits in dem Moment, in dem HttpClient beim Rendern der Seite den ersten API-Aufruf tätigt, da das zugrundeliegende Transportprotokoll einfach nicht vorhanden ist.
fetch() hingegen ist in Browsern, Node.js, Deno sowie den Edge-Runtimes verfügbar. Sobald HttpClient es verwendet, wird derselbe SSR-Anwendungscode portabel: Man kann ihn in Cloudflare Workers oder Deno Deploy rendern und von Standorten aus API-Aufrufe tätigen, die geografisch näher an den Nutzern liegen.
Austausch des Backends mit withFetch()
Der Umstieg stellt einen Konfigurationswechsel dar, keine Refaktorierung. Komponenten und Dienste rufen weiterhin this.http.get() sowie ähnliche Methoden genauso wie zuvor auf; nur die Einrichtung des Anbieters in der Anwendungskonfiguration ändert sich.
Der folgende Codeausschnitt registriert HttpClient über provideHttpClient() und übermittelt zwei Eigenschaften. withFetch() wählt den auf Fetch basierenden Transport aus, während withInterceptors() einen funktionalen Authentifizierungs-Interceptor registriert, der weiter unten definiert wird.
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])
)
]
};
Intern weist withFetch() Angular an, stattdessen FetchBackend anstelle von HttpXhrBackend als Komponente zu verwenden, die tatsächlich die Netzwerkanfrage ausführt. Alles darüber bleibt unverändert.
Eine Einschränkung, die man kennen sollte, bevor man den Schalter umlegt: Der Fetch-Backend gibt keine Informationen über den Upload-Progress an, da fetch() kein Äquivalent zum Upload-Progress von XHR bietet. Wenn Ihre Anwendung eine Fortschrittsleiste für Dateiuploads anzeigt, sollten Sie diesen Ablauf ausdrücklich testen.
Datenstromantworten ohne Vollpufferung
XHR ist auf vollständige, eigenständige Antworten ausgelegt. Er kommt mit großen oder kontinuierlichen Daten nur schlecht zurecht: Das Herunterladen einer 50 MB großen Datei oder das Verarbeiten einer in Blöcken gelieferten Antwort führt dazu, dass im Speicher ein großer Puffer angesammelt wird, bevor JavaScript damit arbeiten kann – was zu Speicherdruck und einer träge reagierenden Benutzeroberfläche führt.
fetch() wurde mit ReadableStream als Kernkonzept entwickelt, sodass der Dateninhalt nach und nach verarbeitet werden kann, sobald er über das Netzwerk ankommt. Dank des fetch-Backends kann Angular die eingehenden Daten bereits im Moment ihrer Ankunft verarbeiten, anstatt auf den gesamten Datenträger zu warten.
Dies ist insbesondere für interfaces im Stil von KI wichtig. Eine Chat-Oberfläche, die Server-Sent Events oder in Blöcken gelieferte LLM-Ausgaben verarbeitet, benötigt Tokens, die sofort sichtbar werden, sobald sie generiert werden – und ein streamfähiger Transportmechanismus bildet die Grundlage dafür, ohne dass alles in der Browser-Speicherung angesammelt wird.
Sie sollten jedoch genau wissen, was Sie erhalten. Eine normale http.get()-Observable gibt weiterhin die endgültige Antwort aus, sobald der Datenkörper vollständig ist; schrittweiser Zugriff erfolgt über Fortschrittsereignisse (reportProgress mit observe: 'events'), und für langanhaltende Token-Ströme greifen viele Teams weiterhin auf EventSource oder einen direkten fetch()-Aufruf zurück. Überprüfen Sie das gewünschte Verhalten anhand der Dokumentation für Ihre Angular-Version.
Was passiert mit Ihren Interzeptoren
Jedes Mal, wenn sich der Netzwerkmotor ändert, ist die erste Frage eines erfahrenen Entwicklers bezüglich des Middleware: Werden die Authentifizierungs- und Protokollierungsinterzeptoren weiterhin funktionieren?
Mit funktionalen Interzeptoren lautet die Antwort ja. Die Abfangung erfolgt über Angulars eigene HttpRequest- und HttpResponse-Abstraktionen, die über dem Transportmechanismus liegen. Interzeptoren wandeln das Angular-Anfrageobjekt um, und erst nachdem die gesamte Kette abgearbeitet wurde, erreicht die endgültige Anfrage FetchBackend, das sie in einen fetch()-Aufruf umwandelt. Der Interceptor berührt niemals die native Anfrage.
Das untenstehende Beispiel ist backend-unabhängig. Es liest ein JWT, kloniert die unveränderliche Anfrage mit einem Authorization-Header und gibt die Kopie an next weiter, welches sie an den nächsten Interceptor und schließlich an das Backend leitet.
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);
};
Zu diesem spezifischen Codeausschnitt gelten zwei Einschränkungen, die nicht auf den fetch-Backend zutreffen. Erstens existiert localStorage während der Serverseitigen Ausgabe nicht, weshalb bei SSR oder Edge-Deployment die Anfrage geschützt oder der Token auf andere Weise eingefügt werden sollte. Zweitens macht das Speichern von Tokens in localStorage sie anfällig für XSS-Sicherheitslücken, und der Code sendet Bearer null, wenn kein Token vorhanden ist; Produktionscode sollte beide Fälle berücksichtigen.
Klassenbasierte Interceptor funktionieren weiterhin
Ältere Interceptor-Implementierungen, die als Klassen geschrieben sind, die HttpInterceptor implementieren, funktionieren weiterhin mit dem fetch-Backend. Sie passen jedoch zunehmend nicht mehr zum Rest des Frameworks. Die Kombination von withFetch() mit funktionalen Interceptoren, die über withInterceptors() registriert werden, entspricht den aktuellen Angular-Konventionen – daher lohnt es sich, Klassen-basierte Interceptor-Implementierungen zu migrieren, sobald man sie anfasst.
Kernpunkte
- Den Wechsel von
HttpClientvon XHR zu fetch ist eine unauffällige, aber bedeutende Verbesserung: Es gibt nur eine Anbieteroption, und der Aufrufcode muss nicht geändert werden. - Fügen Sie
withFetch()zuprovideHttpClient()hinzu, wenn Ihre Version fetch nicht bereits standardmäßig verwendet. - Fetch ermöglicht es Angular SSR, auf Cloudflare Workers, Deno und ähnlichen Edge-Runtimes zu laufen, die
XMLHttpRequestnicht besitzen.
ReadableStream, was bei großen Datenmengen und in Teilen gelieferten Antworten wie z. B. LLM-Ausgaben hilft, doch prüfen Sie, wie HttpClient vor dem Erstellen einer Streaming-Oberfläche teilweise Daten bereitstellt.HttpInterceptorFn) funktionieren unverändert weiter; testen Sie den Upload-Progress sowie alle nur für SSR verwendeten Codepfade nach dem Umstieg.