Angular HttpClient und Interceptoren 2026: Request-Handling und Interview-Fragen

Umfassender Leitfaden zu Angular HttpClient Interceptoren: funktionale Interceptoren, Authentifizierung, Token-Refresh, Caching und häufige Interview-Fragen für Angular-Entwickler.

Angular HttpClient und Interceptoren 2026: Request-Handling und Interview-Fragen

Angular HttpClient Interceptoren verändern grundlegend, wie Anwendungen HTTP-Requests verarbeiten – von der Authentifizierung bis zur Retry-Logik. Angular 20 hat funktionale Interceptoren als empfohlenen Ansatz standardisiert und ersetzt damit das ältere klassenbasierte Muster durch eine vorhersagbarere und tree-shakable API.

Funktionale Interceptoren als Standard

Angular 20 empfiehlt funktionale Interceptoren mit withInterceptors() gegenüber klassenbasierten Interceptoren. Funktionale Interceptoren haben eine vorhersagbarere Ausführungsreihenfolge und integrieren sich besser mit Standalone-Komponenten.

HttpClient-Setup mit provideHttpClient

Bevor Interceptoren geschrieben werden können, muss HttpClient im Application-Bootstrap konfiguriert werden. Angulars Standalone-Architektur verwendet provideHttpClient() anstelle des älteren HttpClientModule.

Die Konfiguration erfolgt in der Application-Konfiguration:

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(
      // Interceptoren werden in Array-Reihenfolge ausgeführt
      withInterceptors([authInterceptor, loggingInterceptor])
    ),
  ],
});

Interceptoren werden in der Reihenfolge ausgeführt, die im Array angegeben ist. Der erste Interceptor verarbeitet den ausgehenden Request zuerst, und der letzte Interceptor sieht die Response zuerst.

Anatomie eines funktionalen Interceptors

Ein funktionaler Interceptor erhält den Request und eine next-Funktion. Der Aufruf von next(req) leitet den Request an den nächsten Interceptor in der Kette weiter oder an das Backend, falls dies der letzte Interceptor ist.

Die grundlegende Struktur sieht folgendermaßen aus:

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);
      },
    })
  );
};

Der Interceptor protokolliert die Request-Methode und URL und misst dann die Response-Zeit. Der tap-Operator beobachtet die Response, ohne sie zu modifizieren.

Authentifizierungs-Interceptor mit Token-Injection

Die meisten Anwendungen müssen Authentifizierungs-Tokens an ausgehende Requests anhängen. Der Interceptor klont den Request mit dem Authorization-Header, da HttpRequest-Objekte unveränderlich sind.

Dieses Muster ruft das Token von einem Service ab und fügt es hinzu:

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) => {
  // Services mit Angulars inject()-Funktion injizieren
  const authService = inject(AuthService);
  const token = authService.getAccessToken();

  // Auth-Header für öffentliche Endpoints überspringen
  if (req.url.includes('/public/') || !token) {
    return next(req);
  }

  // Request mit Authorization-Header klonen
  const authReq = req.clone({
    setHeaders: {
      Authorization: `Bearer ${token}`,
    },
  });

  return next(authReq);
};

Die inject()-Funktion funktioniert, weil Interceptoren innerhalb von Angulars Injection-Kontext ausgeführt werden. Dies eliminiert den Constructor-Injection-Boilerplate klassenbasierter Interceptoren.

Fehlerbehandlung und Retry-Logik

Netzwerkfehler passieren. Ein Retry-Interceptor kann fehlgeschlagene Requests automatisch mit exponentiellem Backoff wiederholen und so die Zuverlässigkeit verbessern, ohne Änderungen an jedem HTTP-Aufruf zu erfordern.

RxJS-Operatoren übernehmen die Retry-Logik:

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

export const retryInterceptor: HttpInterceptorFn = (req, next) => {
  // Nur GET-Requests wiederholen (idempotent)
  if (req.method !== 'GET') {
    return next(req);
  }

  return next(req).pipe(
    retry({
      count: 3,
      delay: (error: HttpErrorResponse, retryCount: number) => {
        // Client-Fehler (4xx) nicht wiederholen
        if (error.status >= 400 && error.status < 500) {
          throw error;
        }
        // Exponentielles Backoff: 1s, 2s, 4s
        const delayMs = Math.pow(2, retryCount - 1) * 1000;
        console.log(`Retry #${retryCount} in ${delayMs}ms`);
        return timer(delayMs);
      },
    })
  );
};

Der Interceptor wiederholt nur GET-Requests, da POST, PUT und DELETE nicht idempotent sind. Client-Fehler (4xx) überspringen Retries, da der Server den Request explizit abgelehnt hat.

Bereit für deine Angular-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Response-Caching mit HttpContext

HttpClient bietet HttpContext, um Metadaten zwischen Interceptoren und aufrufendem Code zu übergeben. Ein Caching-Interceptor kann Context-Tokens lesen, um das Cache-Verhalten zu bestimmen.

Das Cache-Token und der Interceptor arbeiten zusammen:

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

// Context-Token zur Steuerung des Cachings pro Request
export const CACHE_REQUEST = new HttpContextToken<boolean>(() => false);

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

export const cacheInterceptor: HttpInterceptorFn = (req, next) => {
  // Nur cachen, wenn explizit via Context angefordert
  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());
      }
    })
  );
};

Der aufrufende Code aktiviert Caching mit dem Context-Token:

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),
    });
  }
}

Dieses Muster hält das Caching-Verhalten explizit an der Aufrufstelle, anstatt es in der globalen Konfiguration zu verbergen.

Token-Refresh mit 401-Handling

Wenn Access-Tokens ablaufen, gibt der Server eine 401-Response zurück. Ein Interceptor kann dies abfangen, das Token aktualisieren und den ursprünglichen Request transparent wiederholen.

Die Refresh-Logik koordiniert mehrere gleichzeitige Requests:

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) {
        // Warten bis der Refresh abgeschlossen ist
        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);
        })
      );
    })
  );
};

Das BehaviorSubject verhindert mehrfache Refresh-Aufrufe, wenn mehrere Requests gleichzeitig fehlschlagen. Sobald das Token aktualisiert ist, wiederholen alle wartenden Requests mit dem neuen Token.

Interview-Fragen: HttpClient und Interceptoren

Technische Interviews testen oft das Verständnis von HTTP-Handling-Patterns. Diese Fragen erscheinen häufig bei Angular-Positionen.

F: Warum werden funktionale Interceptoren gegenüber klassenbasierten Interceptoren bevorzugt?

Funktionale Interceptoren haben eine vorhersagbarere Ausführungsreihenfolge. Die Angular-Dokumentation besagt, dass funktionale Interceptoren mit withInterceptors() eine strikte Array-Reihenfolge einhalten, während DI-basierte Interceptoren in komplexen Modul-Hierarchien Reihenfolgeprobleme haben können. Funktionale Interceptoren produzieren auch kleinere Bundles, da sie besser tree-shaken.

F: Wie verhindert man, dass ein Interceptor bei bestimmten Requests ausgeführt wird?

Es gibt zwei Ansätze. Erstens kann das URL-Muster im Interceptor geprüft und next(req) ohne Modifikation für ausgeschlossene Pfade aufgerufen werden. Zweitens können HttpContext-Tokens verwendet werden, die der aufrufende Code setzt, um sich vom Interceptor-Verhalten abzumelden. Der Context-Ansatz hält die Ausschlussentscheidung an der Aufrufstelle.

F: Was passiert, wenn man vergisst, next() in einem Interceptor aufzurufen?

Der Request erreicht nie den Server. Das Observable, das von next() zurückgegeben wird, repräsentiert den Rest der Interceptor-Kette und den tatsächlichen HTTP-Aufruf. Ohne dessen Aufruf sehen nachfolgende Interceptoren und das Backend den Request nie. Dies ist beabsichtigt für Caching-Interceptoren, die gecachte Responses zurückgeben.

F: Wie behandelt man gleichzeitige Requests während eines Token-Refresh?

Es wird ein BehaviorSubject zur Koordination verwendet. Wenn der erste 401 einen Refresh auslöst, wird ein Flag gesetzt und null auf dem Subject emittiert. Nachfolgende 401s warten mit filter auf dem Subject, bis das neue Token ankommt. Dies verhindert mehrfache Refresh-Aufrufe und stellt sicher, dass alle ausstehenden Requests mit demselben frischen Token wiederholt werden.

F: Können Interceptoren den Response-Body modifizieren?

Ja. map() wird auf dem Observable verwendet, das von next() zurückgegeben wird, um HttpResponse-Objekte zu transformieren. Da Responses unveränderlich sind, werden sie mit modifizierten Bodies geklont. Dieses Muster funktioniert zum Hinzufügen berechneter Felder, Auspacken von API-Envelopes oder Normalisieren von Datenstrukturen.

Für weitere Angular Interview-Vorbereitung siehe das HttpClient Interview-Fragen Modul.

Testen funktionaler Interceptoren

Interceptoren erfordern Tests mit gemockten HTTP-Handlern. Angulars HttpTestingController funktioniert mit Interceptoren, die über provideHttpClient() registriert wurden.

Ein Test-Setup sieht folgendermaßen aus:

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' });
  });
});

Der Test verifiziert sowohl die Header-Injection als auch die Ausnahme für öffentliche Endpoints, ohne tatsächliche Netzwerk-Requests zu machen.

Produktionsmuster: Kombination von Interceptoren

Echte Anwendungen stapeln mehrere Interceptoren. Die Reihenfolge ist wichtig: Authentifizierung sollte vor Logging laufen, und Caching sollte vor Retry-Logik laufen.

Eine Produktionskonfiguration könnte so aussehen:

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([
        // Reihenfolge: auth -> refresh -> cache -> retry -> logging
        authInterceptor,
        tokenRefreshInterceptor,
        cacheInterceptor,
        retryInterceptor,
        loggingInterceptor,
      ])
    ),
  ],
});

Diese Reihenfolge stellt sicher, dass Tokens vor dem Refresh-Handling angehängt werden, Cache-Prüfungen vor Retries erfolgen und Logging das finale Timing erfasst.

Für umfassendere Angular-Patterns siehe das RxJS Grundlagen Modul, das die in Interceptoren verwendeten Operatoren behandelt.

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Wichtige Erkenntnisse zu Angular HttpClient Interceptoren

  • Funktionale Interceptoren mit withInterceptors() sind der Angular 20-Standard und bieten vorhersagbare Reihenfolge sowie besseres Tree-Shaking als klassenbasierte Alternativen
  • inject() innerhalb von Interceptoren verwenden, um auf Services ohne Constructor-Boilerplate zuzugreifen
  • Requests mit req.clone() klonen, um Header zu modifizieren, da HttpRequest-Objekte unveränderlich sind
  • HttpContext-Tokens machen Request-spezifisches Verhalten explizit an der Aufrufstelle, anstatt es in der globalen Konfiguration zu verbergen
  • Token-Refresh-Interceptoren sollten gleichzeitige Requests mit BehaviorSubject koordinieren, um mehrfache Refresh-Aufrufe zu verhindern
  • Die Interceptor-Reihenfolge ist wichtig: Authentifizierung vor Caching, Caching vor Retry, Logging zuletzt
Tägliche Challenge

Findest du den Bug in Angular?

Ein echter Codeausschnitt, ein versteckter Bug, ein Versuch pro Tag. Zum Ausprobieren ohne Konto.

Anthony Fillion-Maillet

Geschrieben von

Anthony Fillion-Maillet

Gründer von SharpSkill

Seit über 10 Jahren Fullstack-Entwickler. Er leitet SharpSkill und verantwortet alles, was hier erscheint.

Aktualisiert am 22. August 2026

Teilen

Verwandte Artikel