Eine Rest-Schnittstelle über den von Angular bereitgestellten HTTP-Service an zu binden ist nicht schwer. Dank Observables ist auch die Fehlerbehandlung kein großer Aufwand. Trotzdem muss diese Behandlung für jede Kommunikation deklariert werden was zu lästigem Schreibaufwand führt und die Anwendung auch unübersichtlich macht. Wie sieht eine Möglichkeit aus, generisch auf HTTP Fehler zu reagieren, ohne diese Behandlung bei jedem Aufruf zu implementieren? Werfen wir einen Blick darauf.
Um zum Beispiel mit Rest-Schnittstellen zu kommunizieren verwenden wir in Angular den bereitgestellten HTTP Service der uns diverse Methoden zur Verfügung stellt die verschiedenen Request-Typen zu verwenden. Ein einfache Beispiel und dessen Verwendung könnte so aussehen:
jsonplace-holder.service.ts
@Injectable() export class JSONPlaceHolderService { constructor(private http: Http, @Inject(SERVICE_BASE_URL) private baseurl:string) { } ladeUserDaten(userId: number): Observable<any> { return this.http.get( this.baseurl + 'users/' + userId).map(r => r.json()); } }
app.component.ts
ladeDaten() { this.service.ladeUserDaten(5).subscribe( u => this.userDaten = u, error => console.log('Fehler: ' + error) ); }
(die Fehlerbehandlung durch eine Konsolen-Ausgabe zu implementieren ist natürlich keine gute Idee. In aller Regel werden wir einen zentralen Service verwenden der sich darum kümmert das die Ausgaben an den Benutzer weiter gegeben werden)
Die Behandlung der Fehler, egal in welcher Form sie nun implementiert ist, müsste nun bei jedem Methodenaufruf verwendet werden. Selbst dann wenn wir lediglich eine ganze allgmeine Meldung ausgeben wollen.
Die eigene ‚http‘ Implementierung
Der Titel klingt umfassender als es wirklich ist. Eigentlich erweitern wir den bestehenden HTTP-Service lediglich um eine einheitliche Fehlerbehandlung.
Dazu überschreiben wir die Request-Methode, leiten den eigentlichen Aufruf an die originale Implementierung weiter und registrieren eine Methode („catch“) die sich um unsere Fehlerbehandlung kümmern soll. In diesem Fall leitet der HTTP-Interceptor diese Fehlermeldung an einen separaten Service weiter um den User diese Information an zu zeigen.
jsonplace-holder.service.ts
// operators import 'rxjs/add/operator/catch'; import 'rxjs/add/observable/throw'; import 'rxjs/add/operator/map'; @Injectable() export class HttpInterceptor extends Http { constructor(backend: XHRBackend,options: RequestOptions, private errorService:ErrorHandleService) { super(backend, options) } public request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> { return super.request(url, options) .catch(this.handleError) } public handleError = (error: Response) => { this.errorService.addError(error.toString()); return Observable.throw(error) } }
Soweit so gut. Nun müssen wir lediglich dafür Sorge tragen das bei der Injizierung des HTTP-Services (wie oben zu sehen) unsere eigene Implementierung herangezogen wird und nicht die von Angular selbst. Dazu implementieren wir einen entsprechenden Provider in unserem Modul. Dieser Provider ist hier im Beispiel eine Factory-Methode, damit wir andere Services die ebenfalls per Depedency Injektion bereit gestellt werden an unsere Implementierung übergeben können:
app.module.ts
providers: [ ... ErrorHandleService, { provide: Http, deps: [XHRBackend, RequestOptions, ErrorHandleService], useFactory: (backend: XHRBackend, defaultOptions: RequestOptions, service: ErrorHandleService) => new HttpInterceptor(backend, defaultOptions, service) } ],
Nachricht an den User
Zur Vollständigkeit noch eine kurze Zusammenfassung wie die Benachrichtigung an den Benutzer erfolgt. Wie oben zu sehen verwenden wir einen Service der die Fehler entgegen nimmt. Dieser Service ist sehr überschaubar und hält lediglich ein „Subject“ (rxjs-Packet) bereit an dem sich andere Komponenten registrieren können. In unserem Mini-Beispiel erfolgt die Registrierung direkt in der Hauptkomponenten die bei neuen Nachrichten diese in einer Variablen ablegt, auf dessen Basis eine Meldung im Frontend angezeigt wird. Zusätzlich blenden wir die entsprechenden Fehlermeldungen noch nach einigen Sekunden aus.
error-handler.service.ts
@Injectable() export class ErrorHandleService { public messages = new Subject<string>(); addError(error: string) { this.messages.next(error); throw error; } }
app.component.ts
errorService.messages.subscribe(e => { this.globalErrorMessage = e; setTimeout(() => this.globalErrorMessage = null, 5000); });
app.component.html
<div *ngIf="globalErrorMessage"> <div class="alert alert-danger"> {{globalErrorMessage}}</div> </div>