Angular HttpClient i Interceptory w 2026: Obsługa Żądań HTTP i Pytania Rekrutacyjne

Kompletny przewodnik po funkcjonalnych interceptorach Angular 20, od wstrzykiwania tokenów uwierzytelniających po logikę ponawiania żądań z wykładniczym opóźnieniem. Zawiera pytania rekrutacyjne i wzorce produkcyjne.

Angular HttpClient i Interceptory w 2026: Obsługa Żądań HTTP i Pytania Rekrutacyjne

Interceptory Angular HttpClient transformują sposób, w jaki aplikacje obsługują żądania HTTP — od dodawania nagłówków uwierzytelniających po implementację logiki ponawiania. Angular 20 ustandaryzował funkcjonalne interceptory jako zalecane podejście, zastępując starszy wzorzec oparty na klasach bardziej przewidywalnym i lepiej optymalizowalnym API.

Funkcjonalne Interceptory to Standard

Angular 20 rekomenduje funkcjonalne interceptory z withInterceptors() zamiast interceptorów opartych na klasach. Funkcjonalne interceptory mają bardziej przewidywalną kolejność wykonywania i lepiej integrują się z komponentami standalone.

Konfiguracja HttpClient z provideHttpClient

Przed napisaniem interceptorów należy skonfigurować HttpClient w bootstrapie aplikacji. Architektura standalone Angulara używa provideHttpClient() zamiast przestarzałego HttpClientModule.

Konfiguracja odbywa się w konfiguracji aplikacji:

main.tstypescript
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { AppComponent } from './app/app.component';
import { authInterceptor } from './interceptors/auth.interceptor';
import { loggingInterceptor } from './interceptors/logging.interceptor';

bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(
      // Interceptory wykonują się w kolejności tablicy
      withInterceptors([authInterceptor, loggingInterceptor])
    ),
  ],
});

Interceptory wykonują się w kolejności określonej w tablicy. Pierwszy interceptor przetwarza wychodzące żądanie jako pierwszy, a ostatni interceptor jako pierwszy widzi odpowiedź.

Anatomia Funkcjonalnego Interceptora

Funkcjonalny interceptor otrzymuje żądanie i funkcję next. Wywołanie next(req) przekazuje żądanie do następnego interceptora w łańcuchu lub do backendu, jeśli jest to ostatni interceptor.

Podstawowa struktura wygląda następująco:

interceptors/logging.interceptor.tstypescript
import { HttpInterceptorFn, HttpRequest, HttpHandlerFn, HttpEvent } from '@angular/common/http';
import { Observable, tap } from 'rxjs';

export const loggingInterceptor: HttpInterceptorFn = (
  req: HttpRequest<unknown>,
  next: HttpHandlerFn
): Observable<HttpEvent<unknown>> => {
  const startTime = performance.now();
  console.log(`[HTTP] ${req.method} ${req.url}`);

  return next(req).pipe(
    tap({
      next: () => {
        const duration = Math.round(performance.now() - startTime);
        console.log(`[HTTP] ${req.method} ${req.url} completed in ${duration}ms`);
      },
      error: (err) => {
        console.error(`[HTTP] ${req.method} ${req.url} failed:`, err.message);
      },
    })
  );
};

Interceptor loguje metodę żądania i URL, a następnie mierzy czas odpowiedzi. Operator tap obserwuje odpowiedź bez jej modyfikowania.

Interceptor Uwierzytelniania z Wstrzykiwaniem Tokena

Większość aplikacji wymaga dołączenia tokenów uwierzytelniających do wychodzących żądań. Interceptor klonuje żądanie z nagłówkiem Authorization, ponieważ obiekty HttpRequest są niemutowalne.

Ten wzorzec pobiera token z serwisu i dołącza go:

interceptors/auth.interceptor.tstypescript
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from '../services/auth.service';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  // Wstrzykiwanie serwisów za pomocą funkcji inject()
  const authService = inject(AuthService);
  const token = authService.getAccessToken();

  // Pomijanie nagłówka auth dla publicznych endpointów
  if (req.url.includes('/public/') || !token) {
    return next(req);
  }

  // Klonowanie żądania z nagłówkiem Authorization
  const authReq = req.clone({
    setHeaders: {
      Authorization: `Bearer ${token}`,
    },
  });

  return next(authReq);
};

Funkcja inject() działa, ponieważ interceptory wykonują się w kontekście wstrzykiwania zależności Angulara. Eliminuje to szablonowy kod konstruktora z interceptorów opartych na klasach.

Obsługa Błędów i Logika Ponawiania

Błędy sieciowe się zdarzają. Interceptor ponawiania może automatycznie ponawiać nieudane żądania z wykładniczym opóźnieniem, poprawiając niezawodność bez konieczności modyfikacji każdego wywołania HTTP.

Operatory RxJS obsługują logikę ponawiania:

interceptors/retry.interceptor.tstypescript
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { retry, timer } from 'rxjs';

export const retryInterceptor: HttpInterceptorFn = (req, next) => {
  // Ponawianie tylko żądań GET (idempotentnych)
  if (req.method !== 'GET') {
    return next(req);
  }

  return next(req).pipe(
    retry({
      count: 3,
      delay: (error: HttpErrorResponse, retryCount: number) => {
        // Nie ponawiaj błędów klienta (4xx)
        if (error.status >= 400 && error.status < 500) {
          throw error;
        }
        // Wykładnicze opóźnienie: 1s, 2s, 4s
        const delayMs = Math.pow(2, retryCount - 1) * 1000;
        console.log(`Ponowna próba #${retryCount} za ${delayMs}ms`);
        return timer(delayMs);
      },
    })
  );
};

Interceptor ponawia tylko żądania GET, ponieważ POST, PUT i DELETE nie są idempotentne. Błędy klienta (4xx) pomijają ponawianie, ponieważ serwer jawnie odrzucił żądanie.

Gotowy na rozmowy o Angular?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Cachowanie Odpowiedzi z HttpContext

HttpClient udostępnia HttpContext do przekazywania metadanych między interceptorami a kodem wywołującym. Interceptor cachujący może odczytywać tokeny kontekstu, aby określić zachowanie cache.

Token cache i interceptor współpracują ze sobą:

interceptors/cache.interceptor.tstypescript
import { HttpInterceptorFn, HttpContextToken, HttpResponse } from '@angular/common/http';
import { of, tap } from 'rxjs';

// Token kontekstu do kontrolowania cachowania per-żądanie
export const CACHE_REQUEST = new HttpContextToken<boolean>(() => false);

const cache = new Map<string, HttpResponse<unknown>>();

export const cacheInterceptor: HttpInterceptorFn = (req, next) => {
  // Cachuj tylko jeśli jawnie żądane przez kontekst
  if (!req.context.get(CACHE_REQUEST) || req.method !== 'GET') {
    return next(req);
  }

  const cacheKey = req.urlWithParams;
  const cachedResponse = cache.get(cacheKey);

  if (cachedResponse) {
    console.log(`[Cache] HIT: ${cacheKey}`);
    return of(cachedResponse.clone());
  }

  return next(req).pipe(
    tap((event) => {
      if (event instanceof HttpResponse) {
        console.log(`[Cache] STORE: ${cacheKey}`);
        cache.set(cacheKey, event.clone());
      }
    })
  );
};

Kod wywołujący włącza cachowanie za pomocą tokena kontekstu:

services/user.service.tstypescript
import { HttpClient, HttpContext } from '@angular/common/http';
import { CACHE_REQUEST } from '../interceptors/cache.interceptor';

export class UserService {
  constructor(private http: HttpClient) {}

  getUser(id: string) {
    return this.http.get(`/api/users/${id}`, {
      context: new HttpContext().set(CACHE_REQUEST, true),
    });
  }
}

Ten wzorzec utrzymuje zachowanie cachowania jawnym w miejscu wywołania, zamiast ukrywać je w globalnej konfiguracji.

Odświeżanie Tokena z Obsługą 401

Gdy tokeny dostępu wygasają, serwer zwraca odpowiedź 401. Interceptor może przechwycić ten błąd, odświeżyć token i transparentnie ponowić oryginalne żądanie.

Logika odświeżania koordynuje wiele współbieżnych żądań:

interceptors/token-refresh.interceptor.tstypescript
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { catchError, switchMap, throwError, BehaviorSubject, filter, take } from 'rxjs';
import { AuthService } from '../services/auth.service';

let isRefreshing = false;
const refreshTokenSubject = new BehaviorSubject<string | null>(null);

export const tokenRefreshInterceptor: HttpInterceptorFn = (req, next) => {
  const authService = inject(AuthService);

  return next(req).pipe(
    catchError((error: HttpErrorResponse) => {
      if (error.status !== 401 || req.url.includes('/auth/refresh')) {
        return throwError(() => error);
      }

      if (isRefreshing) {
        // Czekaj na zakończenie odświeżania
        return refreshTokenSubject.pipe(
          filter((token) => token !== null),
          take(1),
          switchMap((token) => {
            const retryReq = req.clone({
              setHeaders: { Authorization: `Bearer ${token}` },
            });
            return next(retryReq);
          })
        );
      }

      isRefreshing = true;
      refreshTokenSubject.next(null);

      return authService.refreshToken().pipe(
        switchMap((response) => {
          isRefreshing = false;
          refreshTokenSubject.next(response.accessToken);
          const retryReq = req.clone({
            setHeaders: { Authorization: `Bearer ${response.accessToken}` },
          });
          return next(retryReq);
        }),
        catchError((refreshError) => {
          isRefreshing = false;
          authService.logout();
          return throwError(() => refreshError);
        })
      );
    })
  );
};

BehaviorSubject zapobiega wielokrotnym wywołaniom odświeżania, gdy kilka żądań kończy się niepowodzeniem jednocześnie. Po odświeżeniu tokena wszystkie żądania w kolejce ponawiają się z nowym tokenem.

Pytania Rekrutacyjne: HttpClient i Interceptory

Rozmowy techniczne często badają zrozumienie wzorców obsługi HTTP. Te pytania pojawiają się często na stanowiskach Angular.

P: Dlaczego funkcjonalne interceptory są preferowane nad interceptorami opartymi na klasach?

Funkcjonalne interceptory mają bardziej przewidywalną kolejność wykonywania. Dokumentacja Angular stwierdza, że funkcjonalne interceptory z withInterceptors() zachowują ścisłą kolejność tablicy, podczas gdy interceptory oparte na DI mogą mieć problemy z kolejnością w złożonych hierarchiach modułów. Funkcjonalne interceptory również produkują mniejsze bundle, ponieważ lepiej się tree-shake'ują.

P: Jak zapobiec uruchomieniu interceptora dla konkretnych żądań?

Istnieją dwa podejścia. Po pierwsze, sprawdzenie wzorca URL w interceptorze i wywołanie next(req) bez modyfikacji dla wykluczonych ścieżek. Po drugie, użycie tokenów HttpContext, które kod wywołujący ustawia, aby zrezygnować z zachowania interceptora. Podejście z kontekstem utrzymuje decyzję o wykluczeniu w miejscu wywołania.

P: Co się dzieje, jeśli zapomnisz wywołać next() w interceptorze?

Żądanie nigdy nie dotrze do serwera. Observable zwrócony przez next() reprezentuje resztę łańcucha interceptorów i rzeczywiste wywołanie HTTP. Bez jego wywołania, downstream interceptory i backend nigdy nie zobaczą żądania. Jest to celowe dla interceptorów cachujących, które zwracają odpowiedzi z cache.

P: Jak obsługiwać współbieżne żądania podczas odświeżania tokena?

Użyj BehaviorSubject do koordynacji. Gdy pierwszy błąd 401 uruchamia odświeżanie, ustaw flagę i wyemituj null na subject. Kolejne błędy 401 czekają na subject z filter, aż nadejdzie nowy token. Zapobiega to wielokrotnym wywołaniom odświeżania i zapewnia, że wszystkie oczekujące żądania ponawiają się z tym samym świeżym tokenem.

P: Czy interceptory mogą modyfikować ciało odpowiedzi?

Tak. Użyj map() na Observable zwróconym przez next(), aby transformować obiekty HttpResponse. Ponieważ odpowiedzi są niemutowalne, klonuj je ze zmodyfikowanymi ciałami. Ten wzorzec działa do dodawania pól obliczonych, rozpakowywania kopert API lub normalizacji struktur danych.

Więcej materiałów do przygotowania do rozmów kwalifikacyjnych z Angular znajdziesz w module pytań rekrutacyjnych HttpClient.

Testowanie Funkcjonalnych Interceptorów

Interceptory wymagają testowania z mockowanymi handlerami HTTP. HttpTestingController Angulara działa z interceptorami zarejestrowanymi przez provideHttpClient().

Konfiguracja testu wygląda następująco:

interceptors/auth.interceptor.spec.tstypescript
import { TestBed } from '@angular/core/testing';
import { HttpClient, provideHttpClient, withInterceptors } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { authInterceptor } from './auth.interceptor';
import { AuthService } from '../services/auth.service';

describe('authInterceptor', () => {
  let httpClient: HttpClient;
  let httpTesting: HttpTestingController;
  let authServiceSpy: jasmine.SpyObj<AuthService>;

  beforeEach(() => {
    authServiceSpy = jasmine.createSpyObj('AuthService', ['getAccessToken']);

    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(withInterceptors([authInterceptor])),
        provideHttpClientTesting(),
        { provide: AuthService, useValue: authServiceSpy },
      ],
    });

    httpClient = TestBed.inject(HttpClient);
    httpTesting = TestBed.inject(HttpTestingController);
  });

  it('should add Authorization header when token exists', () => {
    authServiceSpy.getAccessToken.and.returnValue('test-token');

    httpClient.get('/api/data').subscribe();

    const req = httpTesting.expectOne('/api/data');
    expect(req.request.headers.get('Authorization')).toBe('Bearer test-token');
    req.flush({ data: 'test' });
  });

  it('should skip Authorization for public endpoints', () => {
    authServiceSpy.getAccessToken.and.returnValue('test-token');

    httpClient.get('/public/health').subscribe();

    const req = httpTesting.expectOne('/public/health');
    expect(req.request.headers.has('Authorization')).toBeFalse();
    req.flush({ status: 'ok' });
  });
});

Test weryfikuje zarówno wstrzykiwanie nagłówka, jak i wyjątek dla publicznych endpointów bez wykonywania rzeczywistych żądań sieciowych.

Wzorce Produkcyjne: Łączenie Interceptorów

Rzeczywiste aplikacje układają wiele interceptorów w stos. Kolejność ma znaczenie: uwierzytelnianie powinno być przed logowaniem, a cachowanie przed logiką ponawiania.

Konfiguracja produkcyjna może wyglądać tak:

main.tstypescript
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { AppComponent } from './app/app.component';
import { authInterceptor } from './interceptors/auth.interceptor';
import { tokenRefreshInterceptor } from './interceptors/token-refresh.interceptor';
import { cacheInterceptor } from './interceptors/cache.interceptor';
import { retryInterceptor } from './interceptors/retry.interceptor';
import { loggingInterceptor } from './interceptors/logging.interceptor';

bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(
      withInterceptors([
        // Kolejność: auth -> refresh -> cache -> retry -> logging
        authInterceptor,
        tokenRefreshInterceptor,
        cacheInterceptor,
        retryInterceptor,
        loggingInterceptor,
      ])
    ),
  ],
});

Ta kolejność zapewnia, że tokeny są dołączane przed obsługą odświeżania, sprawdzenia cache odbywają się przed ponawianiem, a logowanie rejestruje końcowy czas.

Więcej wzorców Angular znajdziesz w module podstaw RxJS, który obejmuje operatory używane w interceptorach.

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Kluczowe Wnioski dla Interceptorów Angular HttpClient

  • Funkcjonalne interceptory z withInterceptors() są standardem Angular 20, oferując przewidywalną kolejność i lepszą optymalizację tree-shaking niż alternatywy oparte na klasach
  • Użyj inject() wewnątrz interceptorów, aby uzyskać dostęp do serwisów bez szablonowego kodu konstruktora
  • Klonuj żądania za pomocą req.clone(), aby modyfikować nagłówki, ponieważ obiekty HttpRequest są niemutowalne
  • Tokeny HttpContext czynią zachowanie per-żądanie jawnym w miejscu wywołania, zamiast ukrywać je w globalnej konfiguracji
  • Interceptory odświeżania tokenów powinny koordynować współbieżne żądania za pomocą BehaviorSubject, aby zapobiec wielokrotnym wywołaniom odświeżania
  • Kolejność interceptorów ma znaczenie: uwierzytelnianie przed cachowaniem, cachowanie przed ponawianiem, logowanie na końcu
Wyzwanie dnia

Znajdziesz błąd w Angular?

Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Anthony Fillion-Maillet

Autor:

Anthony Fillion-Maillet

Założyciel SharpSkill

Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.

Zaktualizowano 22 sierpnia 2026

Udostępnij

Powiązane artykuły