Kurs Angular · Moduł 9: HttpClient
Rogatka na trakcie - interceptory HTTP
W tej lekcji8
Umiesz już doczepić do listu pieczęć klanu: budujesz HttpHeaders metodą set() i podajesz gotowy komplet w opcjach żądania. Przy jednej metodzie to robota na dwie linijki. Kłopot zaczyna się wtedy, gdy zamek wprowadza nowy porządek.
A porządek brzmi tak: każdy goniec wychodzący za mury niesie przy sobie żeton wartowniczy, po naszemu token. Nie tylko ten po listę samurajów - także ten z rekrutem, ten z rozkazem awansu, ten z prośbą o wykreślenie z rejestru. Twój rejestr klanu ma pięć metod, więc pięć razy przepisujesz tę samą linię z nagłówkiem Authorization. Serwis zbrojowni ma cztery, więc kopii jest już dziewięć. Jutro dochodzi drugi nagłówek i poprawiasz dziewięć miejsc naraz. Pojutrze ktoś dopisuje dziesiątą metodę i o pieczęci zapomina, serwer odpowiada 401, a wieczór schodzi na szukaniu jednej brakującej linijki.
Z tarczami z lekcji o awariach jest identycznie. Wygasły żeton wygląda w każdej metodzie tak samo: status 401 i konieczność odesłania użytkownika na ekran logowania. Przepisana do dziewięciu metod, ta sama obsługa błędu jest tą samą pracą wykonaną dziewięć razy - i w dziewięciu miejscach można ją zepsuć.
Na trakcie Tōkaidō nikt nie rozwiązywał tego, stawiając pisarza w każdym domu. Stawiano rogatkę: posterunek w poprzek drogi, przez który przechodzi każdy, kto tędy idzie. Jedno miejsce i jeden zwyczaj - przybij pieczęć, zapisz przejście w księdze, przepuść dalej. W Angularze taka rogatka nazywa się interceptor i o niej jest ta lekcja. To jej dotyczyła obietnica z lekcji o opcjach żądania: jedno miejsce, przez które przechodzi każde żądanie w aplikacji.
Czym rogatka jest, a czym nie jest
Rogatka to funkcja, której nigdy nie wołasz sam. Wywołuje ją Angular, i to w bardzo konkretnej chwili: żądanie jest już zbudowane przez HttpClient ze wszystkim, co mu podałeś, ale nie ruszyło jeszcze w sieć. Funkcja dostaje to gotowe żądanie do ręki i może je przed wysyłką podmienić. Dostaje też sposób na przekazanie go dalej, a w zamian - strumień zdarzeń wracających z drugiej strony, w który również wolno jej zajrzeć. Jedna rejestracja obejmuje wszystkie żądania w aplikacji, bez dopisywania czegokolwiek w serwisach.
Zapamiętaj to jednym zdaniem, bo jest to sedno całej lekcji: interceptor służy do globalnego modyfikowania żądań i odpowiedzi, a najczęstszy powód, dla którego się go stawia, to dopisanie tokena do każdego żądania.
Warto od razu odgrodzić to od trzech rzeczy, z którymi rogatkę się myli. Po pierwsze, nie jest to zapora do blokowania niepożądanych żądań. Żądania w Twojej aplikacji wysyła Twój własny kod, więc nie ma tu czego odsiewać, a interceptor praktycznie zawsze kończy się przekazaniem żądania dalej. Odmawianie wstępu to zadanie strażników tras, których stawiałeś przy bramach w module o routingu, oraz samego serwera po drugiej stronie. Po drugie, rogatka nie jest mechanizmem cache'owania odpowiedzi: pamięć na już pobrane dane zbudowałeś w lekcji o tarczach w serwisie, mapą i operatorem shareReplay, i nie brał w tym udziału ani jeden interceptor. Sama rogatka niczego nie zapamiętuje. Po trzecie, nie zajmuje się kompresją danych - tę uzgadniają między sobą przeglądarka i serwer nagłówkiem Content-Encoding, a Twój kod nie ogląda nawet bajtów, na których miałaby pracować.
Nie myl też rogatki ze strażnikiem bramy. Strażnik trasy odpowiadał raz na jedną nawigację i mówił "wpuszczam" albo "zawracam". Rogatka stoi na drodze i towarzyszy każdemu żądaniu z osobna, a jej odpowiedź to nie werdykt, tylko przepuszczony ruch. Wspólne mają tyle, że obie są zwykłymi funkcjami i obie mogą sięgnąć po serwis przez inject().
Kształt rogatki: HttpInterceptorFn
Żeby TypeScript wiedział, jakiej funkcji się tu spodziewać, pakiet @angular/common/http eksportuje gotowy alias typu o nazwie HttpInterceptorFn. Podpisujesz nim stałą po dwukropku i nie musisz wypisywać ani jednej adnotacji w środku - typy obu parametrów TypeScript dopowie sam.
Parametry są dwa i obu użyjesz w każdej rogatce, jaką napiszesz. Pierwszy, tradycyjnie nazywany req, to gotowe żądanie: obiekt HttpRequest z metodą, adresem, nagłówkami i ładunkiem. Drugi, next, to funkcja przekazująca dalej - wywołana z żądaniem, puszcza je na kolejny odcinek drogi i zwraca strumień zdarzeń, które z tej drogi wrócą. Ten strumień jest jednocześnie tym, co Twoja rogatka ma oddać na zewnątrz.
Zacznijmy od rogatki, która nikogo nie zatrzymuje - żeby najpierw zobaczyć samą konstrukcję, bez logiki w środku.
// interceptors/audit.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
export const auditInterceptor: HttpInterceptorFn = (req, next) => {
return next(req);
};Ta rogatka bierze żądanie i oddaje je nietknięte, a jej strumieniem wynikowym jest wprost to, co zwrócił next. Zwróć uwagę na to, czego tu nie ma: nie ma klasy, nie ma dekoratora, nie ma słowa new. To zwykła stała ze strzałkową funkcją, dokładnie tak jak przy strażnikach tras. Co ważniejsze, ta funkcja nie wykona się na razie ani razu - plik leży w projekcie, a Angular nic o nim nie wie, bo nikt jej jeszcze nigdzie nie zgłosił.
Nazwę typu zapamiętaj co do litery, bo trzy inne brzmią równie sensownie i każda zawodzi z innego powodu. Sprawdziłem je kompilatorem TypeScriptu na Angularze 19. Nazwa InterceptorFunction po prostu nie istnieje - import kończy się błędem TS2305 o braku takiego eksportu w module @angular/common/http. Nazwa HttpHandler istnieje, ale opisuje coś innego: to typ obiektu przekazującego dalej w starym wariancie klasowym, obiektu z metodą handle(), a nie typ Twojej funkcji; podpisanie nim funkcji strzałkowej kończy się błędem TS2322. Istnieje wreszcie HttpInterceptor bez końcówki i to jest prawdziwa pułapka, bo należy do wariantu poprzedniego: interfejsu, który implementowała klasa z metodą intercept(), rejestrowana potem przez withInterceptorsFromDi(). Funkcji strzałkowej pod ten interfejs też nie podstawisz - znowu TS2322. Wariant funkcyjny pojawił się w Angularze 15 i to jego polecam: jest krótszy, nie wymaga klasy ani wpisu w dostawcach, a inject() daje Ci w nim dostęp do wszystkiego, po co dawniej trzeba było sięgać przez konstruktor.
Postawić rogatkę na trakcie
Sama funkcja niczego nie przechwyci, dopóki nie wskażesz jej miejsca na drodze. Miejsce to jest jedno i znasz je z pierwszego postoju w tym module: wywołanie provideHttpClient() w pliku konfiguracyjnym aplikacji. Rogatki zgłasza się w nim funkcją withInterceptors(), która przyjmuje tablicę funkcji.
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { auditInterceptor } from './interceptors/audit.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withInterceptors([auditInterceptor]))
]
};Przyjrzyj się tej jednej linii token po tokenie, bo będziesz ją odtwarzał: najpierw nazwa provideHttpClient, potem nawias otwierający, potem nazwa withInterceptors, potem tablica z rogatkami, a na końcu nawias zamykający całe wywołanie. Od tej chwili każde żądanie wysłane przez HttpClient przechodzi przez auditInterceptor - który wciąż przepuszcza wszystkich bez zmian, ale już bierze udział w podróży.
Najważniejsze jest to, co się nie zmieniło. Rejestr klanu, zbrojownia i każdy inny serwis wyglądają dokładnie tak jak wczoraj: ani jednej nowej linii, ani jednego nowego importu. Serwis nie wie, że na trakcie stanął posterunek, i to jest cała siła tego rozwiązania. Nie zmieniły się też komponenty, bo one rozmawiają z serwisem, a nie z siecią.
Dwa szczegóły w tablicy potrafią zaboleć. Przy nazwie auditInterceptor nie ma nawiasów - podajesz samą funkcję, a nie jej wynik, tak samo jak przy strażnikach tras. Zapis [auditInterceptor()] próbowałby wywołać rogatkę natychmiast, w chwili wczytywania pliku konfiguracyjnego, i akurat tę pomyłkę TypeScript wyłapie za Ciebie: sprawdziłem kompilatorem, odpowiada błędem TS2554, że oczekiwano dwóch argumentów, a podano zero. Do tablicy nie wstawisz też klasy w starym stylu - withInterceptors przyjmuje wyłącznie funkcje, a klasa kończy się błędem TS2322 o braku pasującej sygnatury.
Pieczęć na każdym zwoju
Czas na prawdziwą robotę rogatki. Umówmy się, że w projekcie jest serwis AuthService z metodą getToken(), która oddaje aktualny żeton albo null, gdy nikt nie jest zalogowany - ten sam serwis, którego pytałeś o pieczęć przy bramie w module o routingu. Po serwis sięgniesz funkcją inject() z pakietu @angular/core, bo ciało interceptora jest kontekstem wstrzykiwania: to Angular wywołuje tę funkcję i sam ten kontekst przygotowuje.
Zanim jednak dopiszesz nagłówek, musisz poznać zasadę, która rządzi tu wszystkim: HttpRequest jest niemutowalny. Nie da się w nim niczego podmienić w miejscu. Przypisanie req.headers = ... nie jest kwestią dobrego stylu, tylko błędu kompilacji TS2540 o przypisaniu do właściwości tylko do odczytu; sprawdziłem to również dla req.url i wynik jest ten sam. Powód jest praktyczny: to samo żądanie przechodzi przez cały szereg rogatek, a przy ponawianiu może pojechać w drogę drugi raz. Gdyby każdy posterunek mógł je skrobać po swojemu, nikt nie wiedziałby, która wersja rozkazu naprawdę wyszła za mury.
Zamiast skrobania robisz więc odpis. Służy do tego metoda clone() wywołana na żądaniu: zwraca nowy obiekt HttpRequest, przepisuje do niego wszystko, czego nie nadpisujesz - metodę, adres, ładunek - i nakłada te pola, które podasz jej w obiekcie.
// interceptors/auth.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from '../services/auth.service';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);
const token = authService.getToken();
if (token) {
const authReq = req.clone({
headers: req.headers.set('Authorization', `Bearer ${token}`)
});
return next(authReq);
}
return next(req);
};Uruchomiłem tę rogatkę na prawdziwym łańcuchu HTTP Angulara 19 z podstawionym serwerem. Do serwera dotarło żądanie z nagłówkiem Authorization o wartości Bearer i doklejonym żetonem, a więc pieczęć trafiła na miejsce. Sprawdziłem przy okazji rzecz najważniejszą: oryginalny obiekt req po całej operacji nadal nie ma nagłówka Authorization. Odpis powstał obok, dostał nagłówek i to jego oddaliśmy do next. Adres i metoda przeszły do odpisu bez zmian - clone() nie każe ich wypisywać ponownie.
Wewnątrz odpisu działa druga niemutowalność, ta z lekcji o opcjach żądania: req.headers.set(...) też niczego nie dopisuje na miejscu, tylko zwraca nowy komplet nagłówków. Dlatego wynik tego wywołania trafia wprost do clone(). Gdybyś w osobnej linii napisał samo req.headers.set('Authorization', ...) i nie zrobił z wynikiem nic, żądanie pojechałoby bez pieczęci, a kompilator nie powiedziałby ani słowa.
Sama rogatka jeszcze nigdzie nie stoi, dopóki jej nie zgłosisz - a zgłoszenie wygląda dokładnie tak jak przed chwilą, tylko z inną nazwą w tablicy: provideHttpClient(withInterceptors([authInterceptor])).
Rozbroję jeszcze trzy nieporozumienia wokół clone(), bo krążą po dojo uparcie. Pierwsze: że kopia wysyła żądanie szybciej - nie, kopiowanie kilku pól nic nie przyspiesza, a jeśli już, to kosztuje odrobinę pracy; clone() jest po to, żeby dało się cokolwiek zmienić, a nie po to, żeby było prędzej. Drugie: że bez clone() rogatka się nie zarejestruje - nie, rejestracja to wyłącznie sprawa withInterceptors i o klonowaniu nic nie wie; auditInterceptor z początku lekcji nie miał ani jednego clone(), a zgłosił się bez szemrania. Trzecie: że to wyłącznie konwencja i wolno modyfikować bezpośrednio - nie wolno, i nie jest to kwestia umowy między programistami, lecz typu: kompilator odrzuca przypisanie do headers błędem TS2540.
Gdy serwis potrzebny jest wyłącznie po to, by zadać mu jedno pytanie, wolno te dwie pierwsze linie złożyć w jedną. Wynik inject() da się od razu odpytać kropką.
const token = inject(AuthService).getToken();Ten zapis znaczy dokładnie to samo, co poprzednie dwie linie, i to on jest w praktyce najczęstszy - stała authService nie przydawała się dalej do niczego. Zwróć jednak uwagę, gdzie ta linia stoi: w pierwszych liniach ciała rogatki. inject() wolno wywołać wyłącznie synchronicznie, w kontekście wstrzykiwania, więc nie przenoś go do funkcji, która wykona się później - za chwilę zobaczysz, co się dzieje, gdy ktoś spróbuje. Reszta wnętrza rogatki jest zawsze w tej samej kolejności: najpierw zdobądź żeton, potem zrób odpis żądania z nagłówkiem, potem oddaj ten odpis do next.
Co widać w drodze powrotnej
Do tej pory patrzyliśmy tylko w jedną stronę. Ale next(req) nie jest ślepym zaułkiem - zwraca strumień, a strumienia nie musisz oddawać nietkniętego. Wolno go podpiąć pod pipe() i dołożyć operatory, dokładnie tak jak robiłeś to w module o zwiadowcach ninja.
Zbudujmy więc księgę przejść: rogatkę, która mierzy, ile czasu goniec był w drodze. Do samego podglądania służy operator tap, który przepuszcza wartości bez zmiany, a przy okazji pozwala coś przy nich zrobić. Zdarzenia rozpoznajemy po polu type, a nazwy ich rodzajów daje wyliczenie HttpEventType, które znasz z lekcji o opcjach żądania - interesuje nas HttpEventType.Response, czyli koniec podróży.
// interceptors/logging.interceptor.ts
import { HttpEventType, HttpInterceptorFn } from '@angular/common/http';
import { tap } from 'rxjs';
export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
const started = Date.now();
return next(req).pipe(
tap(event => {
if (event.type === HttpEventType.Response) {
console.log(req.method, req.url, Date.now() - started, 'ms');
}
})
);
};Po uruchomieniu w konsoli pojawia się jeden wiersz na każde żądanie: metoda, adres i liczba milisekund. Liczby nie podam, bo zależy ona od sieci i od serwera, a przy podstawionym serwerze w moim teście wyszło zero. Ważniejsze jest to, co się nie zmieniło: żądanie poszło w drogę takie samo jak bez rogatki, odpowiedź wróciła do komponentu nietknięta, a tap niczego w strumieniu nie podmienił - on tylko patrzy.
Jeden szczegół zaskakuje przy pierwszym spotkaniu. Sprawdziłem, jakie zdarzenia przechodzą przez rogatkę przy zwykłym get(), i były dwa: najpierw Sent, potem Response. Rogatka widzi pełny ruch na trakcie, choć komponent poprosił tylko o treść odpowiedzi - odsiewanie zdarzeń dzieje się wyżej, już po interceptorach. Dlatego warunek na HttpEventType.Response nie jest ozdobą: bez niego wpis do księgi poleciałby także przy wysłaniu, czyli zanim w ogóle byłoby co mierzyć.
Zapamiętaj przy okazji, kiedy rogatka w ogóle się odzywa. Sprawdziłem to licznikiem: po zbudowaniu strumienia, ale przed subskrypcją, funkcja nie wykonała się ani razu; po pierwszej subskrypcji raz, po drugiej dwa razy. Strumienie HTTP są leniwe, więc rogatka jest przechodzona raz na każde faktyczne przejście gońca, a nie raz na aplikację.
Wygasły żeton, czyli błąd obsłużony w jednym miejscu
Skoro w drodze powrotnej wolno dokładać operatory, to możesz tam położyć również obsługę błędu - a jest jeden błąd, który wygląda tak samo w każdym żądaniu w całej aplikacji. Status 401 znaczy "twój żeton jest nieważny". Cokolwiek robiłeś, koniec jest ten sam: wyczyść żeton i odeślij użytkownika na ekran logowania.
Wszystkie klocki już znasz. catchError łapie błąd ze strumienia, throwError wypuszcza go dalej, HttpErrorResponse opisuje odpowiedź z błędem - to lekcja o tarczach. Router i jego navigate() to moduł o szlakach. Nowa jest tu tylko jedna zasada, ta o inject(), i zaraz zobaczysz, dlaczego jest tak ważna.
// interceptors/error.interceptor.ts
import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { catchError, throwError } from 'rxjs';
import { AuthService } from '../services/auth.service';
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);
const router = inject(Router);
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
authService.logout();
router.navigate(['/login']);
}
return throwError(() => error);
})
);
};Uruchomiłem to na serwerze odpowiadającym statusem 401. Wynik: żeton został wyczyszczony, router dostał rozkaz przejścia na /login, a komponent mimo wszystko dostał błąd ze statusem 401. To ostatnie jest tu najistotniejsze i wynika z throwError na końcu: rogatka nie połyka błędu, tylko wypuszcza go dalej, więc obsługa napisana wcześniej w serwisie nie przestaje działać ani na chwilę. Gdybyś zamiast tego zwrócił własną, udawaną odpowiedź, komponent nigdy by się nie dowiedział, że coś poszło nie tak.
Zwróć uwagę na miejsce, w którym stoją oba inject(): na samej górze ciała rogatki, przed pipe(). Kusi, żeby napisać je dopiero w środku catchError, tam gdzie faktycznie są potrzebne. Sprawdziłem, co się wtedy dzieje: kod kompiluje się bez jednej skargi, ale przy pierwszym błędzie 401 aplikacja wywala się z komunikatem NG0203 o tym, że inject() musi być wywołane w kontekście wstrzykiwania. Powód jest prosty: kontekst istnieje tylko w chwili, gdy Angular woła Twoją rogatkę, a callback catchError odpala się o wiele później, kiedy z sieci wróci błąd - kontekstu dawno już nie ma. Stałe zdobyte na górze przetrwają do tego momentu bez problemu i o to właśnie chodzi.
Retry z lekcji o tarczach ma tu równie dobre miejsce jak catchError, bo działa na tym samym strumieniu z next(req). Nie będziemy go powtarzać - sposób jest ten sam, zmienia się tylko to, że napisany raz obejmuje wszystkie żądania.
Kolejność rogatek na trakcie
Rogatki na jednym trakcie stoją jedna za drugą, a kolejność wpisu w tablicy jest kolejnością mijania. Zgłośmy wszystkie trzy naraz.
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authInterceptor } from './interceptors/auth.interceptor';
import { loggingInterceptor } from './interceptors/logging.interceptor';
import { errorInterceptor } from './interceptors/error.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([authInterceptor, loggingInterceptor, errorInterceptor])
)
]
};Prześledziłem przejście przez trzy takie posterunki i wynik jest wart zapamiętania: w drogę goniec mija je w kolejności zapisu, czyli auth, logging, error, a dopiero potem wychodzi w sieć. Z powrotem wraca w kolejności odwrotnej: error, logging, auth. To naturalne, bo każda rogatka opakowuje strumień poprzedniej - im dalej w tablicy, tym bliżej sieci.
Praktyczny wniosek jest taki, że rogatka dokładająca pieczęć powinna stać wcześniej niż ta, która patrzy na wynik. Wtedy licznik czasu mierzy przejście żądania już opieczętowanego, a obsługa błędów widzi odpowiedź na to samo żądanie, które naprawdę wyszło za mury. Odwrócona kolejność też się skompiluje i nawet zadziała, ale zaczniesz oglądać w księdze żądania bez nagłówka, którego przecież dokleiłeś.
Rogatka, którą wolno ominąć
Jest jednak trakt, na którym pieczęć zawadza. Dewizę wypisaną na bramie każdy może przeczytać bez żetonu, a wysyłanie tokena tam, gdzie nie jest potrzebny, to w najlepszym razie zbędna gadanina, w najgorszym - pokazanie klanowego sekretu obcemu serwerowi.
Kuszące jest sprawdzanie w rogatce adresu, czegoś w rodzaju "jeśli adres kończy się na motto, przepuść bez pieczęci". Odradzam to: warunek oparty na tekście adresu psuje się po każdej zmianie ścieżki i nikt o nim nie pamięta. Lepszy sposób polega na tym, żeby to wywołujący dopisał do żądania notatkę dla rogatki.
Służy do tego HttpContext: mały tobołek doczepiony do żądania, który - i to jest w nim najważniejsze - nigdy nie leci przez sieć. Istnieje wyłącznie w przeglądarce i jest po to, żeby Twój kod mógł rozmawiać z Twoimi interceptorami. Klucze do tego tobołka tworzy się klasą HttpContextToken, podając funkcję zwracającą wartość domyślną.
// interceptors/auth.context.ts
import { HttpContextToken } from '@angular/common/http';
export const SKIP_AUTH = new HttpContextToken<boolean>(() => false);Nawiasy ostre mówią, że pod tym kluczem leży wartość logiczna, a funkcja () => false daje odpowiedź na pytanie "a co, jeśli nikt nic nie wpisał". Sprawdziłem obie ścieżki na żywym obiekcie: świeżo utworzony kontekst zwraca dla tego klucza false, a żądanie, przy którym w ogóle nie ruszano kontekstu, również false. Domyślnie więc nikt rogatki nie omija, i tak właśnie ma być - o pominięciu trzeba poprosić wprost.
Teraz rogatka musi tę notatkę przeczytać. Każde żądanie ma pole context, a z niego wyjmujesz wartość metodą get(), podając klucz.
// interceptors/auth.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from '../services/auth.service';
import { SKIP_AUTH } from './auth.context';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
if (req.context.get(SKIP_AUTH)) {
return next(req);
}
const token = inject(AuthService).getToken();
if (!token) {
return next(req);
}
return next(req.clone({
headers: req.headers.set('Authorization', `Bearer ${token}`)
}));
};Nowa jest tu wyłącznie pierwsza para linii - reszta to ta sama pieczęć co wcześniej, tylko zapisana tak, że każdy przypadek kończy się własnym return, zamiast chować się w zagnieżdżonym if. Sprawdzenie kontekstu stoi przed inject() i to jest celowe: skoro żądanie ma iść bez pieczęci, nie ma po co pytać serwisu o żeton.
Zostaje strona wywołująca, czyli ten jeden goniec, który ma rogatkę minąć. Notatkę doczepiasz w tym samym obiekcie opcji, w którym podawałeś nagłówki i responseType - to po prostu kolejne jego pole.
import { HttpClient, HttpContext } from '@angular/common/http';
import { inject } from '@angular/core';
import { SKIP_AUTH } from '../interceptors/auth.context';
export class MottoService {
private http = inject(HttpClient);
readMotto() {
return this.http.get('/api/motto', {
responseType: 'text',
context: new HttpContext().set(SKIP_AUTH, true)
});
}
}Puściłem przez tę konfigurację dwa żądania naraz. Zwykłe, po listę samurajów, dotarło do serwera z nagłówkiem Authorization. To po dewizę dotarło bez niego - rogatka odczytała notatkę i przepuściła gońca bez pieczęci. Co się przy tym nie zmieniło: dla serwera oba żądania wyglądają zwyczajnie, bo sam tobołek z notatką nigdzie nie pojechał, a odpis żądania robiony w rogatce przenosi kontekst dalej razem z resztą pól.
Jest tu jedna różnica wobec nagłówków, o którą łatwo się potknąć. HttpHeaders był niemutowalny i jego set() zwracał nowy obiekt. HttpContext zachowuje się odwrotnie: sprawdziłem, że jego set() zmienia obiekt w miejscu i zwraca ten sam obiekt. Łańcuszek new HttpContext().set(...) działa więc w obu przypadkach, ale z dwóch różnych powodów, i tylko przy nagłówkach zgubienie wyniku kosztuje Cię utratę zmiany.
Pamiętaj: rogatka to jedno miejsce na trakcie, przez które przechodzi każdy goniec - dopisz w nim pieczęć, a przestaniesz ją dopisywać wszędzie indziej.
Hasła z dokumentacji w tej lekcji
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Do czego służą HTTP Interceptory?
2. Jakim typem oznaczamy współczesny, funkcyjny interceptor w Angularze 19?
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
Uzupełnij interceptor dodający token autoryzacji do każdego żądania.
- Układanie w poziomie
Ułóż tokeny jednej linii rejestrującej interceptor w provideHttpClient:
- Układanie w pionie
Uporządkuj kolejne linie wnętrza interceptora dodającego token:
- Klikanie w kolejności
Zbuduj wywołanie zwracające zmodyfikowane żądanie z interceptora przez next():