Kolumne: Webentwicklung mit Angular

Abgefangen: So funktionieren Http-Interceptoren in Angular

Karsten Sitterberg

© Shutterstock / dencg

In dieser Folge der Kolumne „Webentwicklung mit Angular“ nimmt sich Karsten Sitterberg ein oft unterschätztes Feature vor: den neuen HttpClient, der seit Angular 4.3.1 zur Verfügung steht und auch im kommenden Angular 5 Verwendung finden wird. Welche neuen Möglichkeiten ergeben sich?

Abgefangen: Http Interceptoren in Angular

Angular 4.3.1 hat ein wichtiges neues Feature mitgebracht: den neuen HttpClient. Damit bestehende Anwendungen durch die API-Änderungen nicht beeinträchtigt werden, wurde der HttpClient im Modul @angular/common/http angelegt, während das alte Http-Modul im Paket @angular/http liegt. Das alte Http-Modul wird auch in Angular 5 noch vorhanden sein, dann aber als „deprecated“ markiert sein, so dass eine entsprechende Umstellung mit ausreichend Zeit vorgenommen werden kann.

Das API des neuen HttpClient orientiert sich am für eine Browseranwendung typischen Einsatzmuster: Üblicherweise werden JSON-Daten abgerufen, wobei Request-Parameter und -Header auf einfache Weise spezifizierbar sein sollen. Neben dem leichter zu verwendenden API bringt der HttpClient eine Besonderheit mit, die von Entwicklern, die mit AngularJS gearbeitet haben, bisher immer wieder vermisst wurde: Interceptoren. Ein Interceptor ist ein klassisches Muster, um Querschnittsaspekte zu implementieren.

Beispiele für Querschnittsaspekte im Kontext von HTTP-Kommunikation sind:

  • Logging von Metriken oder Diagnoseinformationen
  • Zustandsinformationen, bspw. ob aktuell eine Anfrage aktiv ist
  • Security-Aspekte wie Authentifizierungsstatus
TIPP: Die Artikelserie Querschnittsaspekte mit Angular befasste sich bereits mit den bisher bestehenden Umsetzungsoptionen von Querschnittsaspekten in Angular-Anwendungen. Dabei wurde auch der Http-Dienst zur Illustration der möglichen Ansätze verwendet.

 

Verwendung von Angular HttpClient mit Interceptor

Der neue HttpClient befindet sich im Paket @angular/common/http. Er wird automatisch in der Anwendung zur Verfügung gestellt, wenn man das HttpClientModule importiert. Um auch Interceptoren verwenden zu können, müssen diese mit Hilfe des Injection-Tokens HTTP_INTERCEPTORS  zur Verfügung gestellt werden. Durch dieses Token werden die zu verwendenden Interceptoren assoziiert. (Unter der Haube wird ein Multi-Provider verwendet, Angular konfiguriert die zu verwendenden Interceptoren per Dependency-Injection.) Um einen eigenen Interceptor, z.B. den LoggingHttpInterceptor, zu konfigurieren, könnte die app.module.ts beispielsweise so aussehen:

...
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';

import { LoggingHttpInterceptor } from './http.interceptor';

@NgModule({
 imports: [ ..., HttpClientModule ],
 providers: [
   {
      provide: HTTP_INTERCEPTORS,
      useClass: LoggingHttpInterceptor,
      multi: true
    }  
 ],
 ...
})
export class AppModule {}

 

Ein Interceptor muss das Interface HttpInterceptor implementieren, das lediglich eine Methode definiert:

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>

Die Methode intercept wird von Angular für jeden Request aufgerufen. Als Argumente werden der aktuelle Http-Request und der Nachfolge-Handler mitgegeben. Dies ist ein typisches Muster bei Interceptoren: Da es beliebig viele Interceptoren geben könnte, delegiert ein Interceptor nach der Verrichtung der eigenen Funktionalität weiter. Dies erfolgt durch den Aufruf von next.handle(request).

Am Ende der Verarbeitungskette steht dann die reguläre Funktionalität, die den Request ausführt („Chain of responsibility“-Pattern). Ein Interceptor kann natürlich auch die Kette unterbrechen, dann wird entsprechend keine Http-Anfrage durchgeführt. Als Rückgabe der intercept-Methode wird ein Observable erwartet, welches das Ergebnis des Http-Requests darstellt.

Ein vollständig funktionsfähiger Interceptor, der jedoch keine Auswirkungen auf das Verhalten hat, zeigt das folgende Beispiel:

import { Injectable } from '@angular/core';
import {
 HttpInterceptor,
 HttpRequest,
 HttpHandler,
 HttpEvent
} from '@angular/common/http';

import { Observable } from 'rxjs/Observable';

@Injectable()
export class NoopHttpInterceptor implements HttpInterceptor {
 intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
   return next.handle(request);
 }
}

Ein typischer Anwendungsfall für einen Interceptor ist es, zusätzliche HTTP-Header zu setzen, zum Beispiel zur Diagnose oder Authentifizierung. Ein solcher Interceptor verändert den Request. Da in Angular sowohl der Http-Request als auch die Header unveränderlich sind, wird ein neuer Request erzeugt, der die neuen Header erhält. Im Code sieht dies folgendermaßen aus:

@Injectable()
export class DebugIdHttpInterceptor implements HttpInterceptor {
 intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
   ...

   const req = request.clone({
     headers: request.headers.set('x-debug-id', 'c0fefe')
   });
   return next.handle(req);
 }
}

Soll mit der Antwort gearbeitet werden, zum Beispiel um diese zu protokollieren, wird mit dem Response-Observable gearbeitet. Der do()-Operator aus RxJS erlaubt es, Seiteneffekte zu definieren, mit der die Logging-Logik implementiert werden kann. Das zeigt folgendes Beispiel:

import {
 HttpInterceptor,
 HttpRequest,
 HttpResponse,
 HttpErrorResponse,
 HttpHandler,
 HttpEvent
} from '@angular/common/http';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';

@Injectable()
export class LoggingHttpInterceptor implements HttpInterceptor {
 intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
   ...

   return next
     .handle(request)
     .do((ev: HttpEvent<any>) => {
       if (ev instanceof HttpResponse) {
         console.log('Response:', ev);
       }
     });
 }
}

Etwas aufwändiger gestaltet sich die Implementierung, wenn lediglich im Fehlerfall Programmlogik ausgeführt werden soll. Auch hier wird das RxJS-API bemüht, um mittels catch()-Operator auf Fehlerereignisse zu reagieren. Um den Fehler nicht zu verschlucken, muss dieser anschließend erneut über ein Observable zurückgeliefert werden, wie auch im folgenden Beispiel zu sehen:

import {
 HttpInterceptor,
 HttpRequest,
 HttpResponse,
 HttpErrorResponse,
 HttpHandler,
 HttpEvent
} from '@angular/common/http';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';

@Injectable()
export class ErrorLoggingHttpInterceptor implements HttpInterceptor {
 intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
   ...

   return next
     .handle(request)
     .catch(response => {
       if (response instanceof HttpErrorResponse) {
         console.error('Error', response);
       }
       return Observable.throw(response);
     });
 }
}

Fazit

Mit Angular Version 4.3 wurde endlich das bereits aus AngularJS bekannte Konzept von Http-Interceptoren nachgeliefert. Damit lassen sich typische Aufgaben elegant implementieren und die Funktionalitäten des jeweiligen Interceptor isoliert testen.

Geschrieben von
Karsten Sitterberg
Karsten Sitterberg
Karsten Sitterberg ist als freiberuflicher Entwickler, Trainer und Berater für Java und Webtechnologien tätig. Karsten ist Physiker (MSc) und Oracle-zertifizierter Java Developer. Seit 2012 arbeitet er mit trion zusammen.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: