Przenoszenie Angular HttpClient do backendu Fetch za pomocą withFetch()
Dowiedz się, dlaczego HttpClient w Angular przechodzi z XMLHttpRequest na fetch, jak funkcja fetch() umożliwia edge SSR i transmisję strumieniową, oraz co to oznacza dla twoich interceptorów.
Źródła: Stop Using XMLHttpRequest: Why Angular 22 Shifted to the Native Fetch API
Przez większość swojego istnienia HttpClient z Angulara stanowił przyjazną warstwę opartą na RxJS, działającą nad XMLHttpRequest – API przeglądarki, które istniało jeszcze przed niemal wszystkimi innymi elementami nowoczesnej platformy internetowej. Ten wybór staje się coraz większym obciążeniem, gdy aplikacja jest renderowana na serwerze, uruchamiana w środowiskach typu edge lub korzysta z odpowiedzi strumieniowych. Ten przewodnik wyjaśnia, dlaczego backend oparty na funkcji fetch jest ważny, jak przejść na niego za pomocą jednej opcji providera oraz co zmienia się, a co pozostaje bez zmian w istniejących interceptorach.
Dlaczego XMLHttpRequest stał się obciążeniem
XHR pochodzi z 1999 roku, kiedy Microsoft stworzył je w celu obsługi Outlook Web Access. Jego projekt świadczy o jego wieku: jest stanowy, oparty na zdarzeniach i trudny w konfiguracji, a szerszy ekosystem JavaScript od prawie dekady oddala się od niego. Większość bibliotek i środowisk wykonawczych już dawno przyjęła oparty na obietnicach fetch(). Angular utrzymywał XHR głównie ze względu na silne zaangażowanie w kompatybilność wsteczną.
To kompromis wyglądał inaczej, gdy Angular zaczął korzystać z bezstrefowej detekcji zmian i renderowania po stronie serwera. W takim środowisku XHR to już nie tylko stary kod, którego nikt nie chce dotykać; aktywnie ogranicza to miejsca i sposoby działania aplikacji Angular. Tło oparte na fetch umożliwia frameworkowi modernizację jego warstwy sieciowej, prawidłowe obsługę danych strumieniowanych oraz działanie na platformach typu edge.
W chwili pisania niniejszego tekstu nowsze wersje Angulara przesuwają się w kierunku wykorzystania mechanizmu fetch jako preferowanego silnika dla HttpClient. Domyślne ustawienia oraz funkcje uznane za przestarzałe zmieniają się pomiędzy głównymi wersjami, dlatego warto sprawdzić notatki dotyczące danej wersji, zamiast zakładać, jaki backend jest aktywny.
Środowiska Edge nie zawierają XMLHttpRequest
Hosting backendów przechodzi od pełnych procesów Node.js w kontenerach Docker do lżejszych izolat V8, takich jak Cloudflare Workers, Deno i Fastly Compute. Jeśli chcesz lepiej poznać ten model środowisk, zapoznaj się z naszym porównaniem izolat Edge, WebAssembly oraz środowisk Node.js.
Te platformy są zbudowane wokół nowoczesnych API zgodnych ze standardami sieciowymi, a XMLHttpRequest nie należy do nich. Tradycyjna konfiguracja Angular SSR uruchomiona na takiej platformie zawodzi w momencie, gdy HttpClient wysyła pierwszy żądanie API podczas renderowania strony, ponieważ odpowiedni mechanizm transmisji po prostu nie istnieje.
fetch() natomiast jest dostępny zarówno w przeglądarkach, Node.js, Deno, jak i innych środowiskach Edge. Gdy HttpClient zacznie go używać, ta sama aplikacja SSR staje się przenośna – można ją uruchomić w Cloudflare Workers lub Deno Deploy oraz wysyłać żądania API z lokalizacji geograficznie bliższych użytkownikom.
Zmiana backendu przy użyciu withFetch()
Zmiana przełącznika to modyfikacja konfiguracji, a nie refaktoryzacja. Komponenty i usługi nadal wywołują this.http.get() oraz podobne metody dokładnie tak jak wcześniej; zmienia się jedynie konfiguracja dostawcy w pliku konfiguracyjnym aplikacji.
Poniższy fragment rejestruje HttpClient za pomocą provideHttpClient() i przekazuje dwa dodatkowe elementy. withFetch() wybiera transport oparty na metodzie fetch, natomiast withInterceptors() rejestruje interceptor autoryzacji funkcjonalnej, który jest zdefiniowany dalej.
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])
)
]
};
Wewnętrznie withFetch() informuje Angular, aby zamiast HttpXhrBackend używał FetchBackend jako komponentu wykonującego żądanie sieciowe. Wszystko powyżej tego poziomu pozostaje bez zmian.
Jedną z ograniczeń, o których warto wiedzieć przed uruchomieniem aplikacji, jest to, że backend używany do pobierania danych nie raportuje zdarzeń dotyczących postępu przesyłania plików, ponieważ fetch() nie oferuje odpowiednika mechanizmu monitorowania postępu przesyłania plików dostępnego w XHR. Jeśli twoja aplikacja wyświetla pasek postępu podczas przesyłania plików, sprawdź ten proces w sposób wyraźny.
Odpowiedzi strumieniowe bez buforowania wszystkiego
XHR został zaprojektowany wokół kompletnych, samodzielnych odpowiedzi. Słabo radzi sobie z dużymi lub ciągłymi danymi: pobieranie pliku o rozmiarze 50 MB lub przetwarzanie odpowiedzi w formie fragmentów sprawia, że w pamięci gromadzi się duży bufor, zanim JavaScript będzie mógł z nim pracować, co prowadzi do obciążenia pamięci i wolniejszej pracy interfejsu.
fetch() został zaprojektowany z ReadableStream jako swoją podstawą, dzięki czemu treść może być odczytywana po częściach w miarę jej przychodzenia z sieci. Dzięki takiemu rozwiązaniu Angular może przetwarzać przychodzące dane w momencie ich otrzymania, zamiast czekać na całą zawartość.
To ma szczególne znaczenie dla interfejsów w stylu AI. Interfejs czatowy, który odczytuje Server-Sent Events lub fragmentaryczne wyniki generowanych modeli językowych, potrzebuje tokenów po ich tworzeniu, a transport umożliwiający transmisję strumieniową stanowi podstawę do tego, bez gromadzenia wszystkiego w pamięci przeglądarki.
Należy jednak być precyzyjnym co do tego, co otrzymuje się w rezultacie. Zwykły obserwowalny obiekt http.get() nadal wysyła ostateczną odpowiedź po zakończeniu przesyłania ciała; dostęp stopniowy odbywa się poprzez wydarzenia postępu (reportProgress z użyciem observe: 'events'), a w przypadku długotrwałych strumieni tokenów wiele zespołów nadal korzysta z EventSource lub bezpośredniego wywołania fetch(). Sprawdź zachowanie, którego potrzebujesz, w dokumentacji dotyczącej Twojej wersji Angular.
Co się dzieje z twoimi interceptorami
Za każdym razem, gdy zmienia się silnik sieciowy, pierwszym pytaniem zadawanym przez doświadczonego inżyniera jest to dotyczące middleware: czy interceptorzy autoryzacji i logowania będą nadal działać?
Z funkcjonalnymi interceptorami odpowiedź brzmi tak. Intercepcja działa na bazie własnych abstrakcji Angulara, HttpRequest i HttpResponse, które znajdują się powyżej warstwy transportu. Intercepcje modyfikują obiekt żądania w Angularze, a dopiero po zakończeniu całego łańcucha żądanie trafia do FetchBackend, który przekształca je w wywołanie fetch(). Intercepcja nigdy nie dotyka żądania w formie natywnej.
Poniższy przykład jest niezależny od konkretnego backendu. Czyta token JWT, klonuje niezmienne żądanie z nagłówkiem Authorization i przekazuje tę kopię do funkcji next, która kieruje ją do następnego intercepcji, a ostatecznie do backendu.
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);
};
Do tego konkretnego fragmentu kodu, a nie do mechanizmu fetch backend, odnoszą się dwie uwagi. Po pierwsze, localStorage nie istnieje podczas renderowania po stronie serwera, więc w środowiskach SSR lub edge deployments należy inaczej zabezpieczyć tę operację lub wstawić token. Po drugie, przechowywanie tokenów w localStorage naraża je na zagrożenia typu XSS, a kod wysyła Bearer null, gdy token nie jest dostępny; kod produkcyjny powinien obsługiwać oba te przypadki.
Interceptory oparte na klasach nadal działają
Starsze intercepcje napisane jako klasy implementujące HttpInterceptor nadal funkcjonują z backendem fetch. Jednak coraz bardziej wydają się nie pasować do reszty frameworka. Połączenie withFetch() z intercepcjami funkcyjnymi zarejestrowanymi za pomocą withInterceptors() to podejście zgodne z obecnymi konwencjami Angular, więc warto przenieść te oparte na klasach, gdy tylko je edytujesz.
Główne wnioski
- Przeniesienie
HttpClientz XHR na fetch to cichy, ale znaczący ulepszenie: jedna opcja dostawcy, brak zmian w kodzie wywołującym. - Dodaj
withFetch()doprovideHttpClient(), jeśli twoja wersja nie używa już fetch jako standardu. - To właśnie fetch umożliwia działanie Angular SSR na Cloudflare Workers, Deno oraz podobnych środowiskach brzegowych, które nie posiadają
XMLHttpRequest.
ReadableStream, co ułatwia przenoszenie dużych obciążeń i odpowiedzi w fragmentach, takich jak wyniki LLM, ale sprawdź, w jaki sposób HttpClient prezentuje dane częściowe przed stworzeniem interfejsu użytkownika opartego na strumieniowaniu.HttpInterceptorFn) nadal działają bez zmian; przetestuj postęp przesyłania plików oraz wszystkie ścieżki kodu dostępne tylko w SSR po tej zmianie.