5

I want to inform the user if an HTTP request fails, without having to write additional code for every HTTP request.

I had a working prototype for angular 2:

@Injectable()
export class MyErrorHandler extends ErrorHandler {

  constructor() {
    super(false);
  }

  handleError(error: any) {
    this.tellUser(error);
    super.handleError(error);
  }

  tellUser(error: any) {
    // dig for the root cause
    if (error.rejection) {
      error = error.rejection;
    }
    if (error instanceof ViewWrappedError) {
       error = error.originalError;
    }

    let message;
    if (error instanceof Response) {
      // TODO: localize and display nicely
      const messages = {
        0: "Could not connect to server",
      }
      message = messages[error.status] || ("The server reported a system error (HTTP " + error.status + ")");
    } else {
      message = "A system error occurred.";
    }
    console.warn(message);
  }
}

but the ViewWrappedError has been replaced in Angular 4 by

export function viewWrappedDebugError(err: any, context: DebugContext): Error {
  if (!(err instanceof Error)) {
    // errors that are not Error instances don't have a stack,
    // so it is ok to wrap them into a new Error object...
    err = new Error(err.toString());
  }
  _addDebugContext(err, context);
  return err;
}

Which, by virtue of invoking toString on the HttpResponse, makes it hard to query the status code ...

I'd have expected angular to provide a public, well supported API for centralized error handling. Is there really no way to centrally handle HTTP errors other than parsing error messages?

Update: I'd prefer if a component could easily override the centralized error handling.

2 Answers 2

1

Another option is simply having a service that delegates to the Http service while adding whatever custom error handling, debugging logic, extra headers etc that you need to interact with your API.

This service then becomes your centralized point for interacting with anything Http related and as such you can centralize your error handling right there. In any project I develop, I ALWAYS create such a class because practically every single API you interact with has some common configuration that has to happen with every request (even if that configuration is as little as specifying the base url to use).

Example of such a class:

export class ApiGateway {

  baseURL = "https://myapi.com";  // or sometimes pulled from another file
  constructor(private http: Http) { }

  get(path, params) {
    showLoadingIndicator();

    let headers = this.createMySpecialHeaders();
    let options = { headers: headers } // and whatever else you need to generalize
    let fullUrl = this.baseUrl + path + '?' + this.urlEncode(params)`;
    return this.get(path, params, options)
               .do(() => hideLoadingIndicator())
               .map(res => res.json())
               .catch(err => {
                    hideLoadingIndicator();
                  // show error message or whatever the app does with API errors etc
                  // sometimes rethrow the error, depending on the use case
                })
  }
}

To me, this is basic OOP principles - you wrap your interactions with things outside your control in an adapter class to allow you some protection against external changes and to change the API of that external thing to something you prefer, if necessary.

With such a class in place, if, for eg, you upgraded to Angular 4 and the means of receiving errors change, you only have one place to change to handle the new error technique.

Sign up to request clarification or add additional context in comments.

Comments

0

I'm centralizing this kind of logic inside a BaseService, and then inherit every service from It. Angular 2 doesn't provide Http Interceptors as the previous version, making this kind of stuff difficult to handle.

import { Injectable } from '@angular/core';
import { Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { NotificationService } from '********';


@Injectable()
export class BaseService {
  protected url: string;
  protected http: Http;

  constructor(http: Http, protected notification: NotificationService) {
    this.http = http;
  }

  protected extractData(res: Response) {
      //
      // Depende do formato da response, algumas APIs retornam um objeto data e dentro deste o conteudo propriamente dito
      // Em minhas APIs sempre retorno diretamente o objeto que desejo
      //
      return res.json();
  }

  protected handleError(error: Response | any) {
    let erros: string[] = [];

    switch (error.status) {
      case 400:
        let res = error.json();

    //
    // Depende do formato, minhas APIs sempre retornar um objeto com array errors quando tenho 400
    //
        for (let index = 0; index < res.errors.length; index++) {
          let msg = res.errors[index];
          erros.push(msg);
        };

        this.notification.showMultipleWarn(erros);
        break;
      case 404:
    this.notification.showWarning('O recurso requisitado não existe.');
    break;
      case 406:
      case 409:
      case 500:
        this.notification.showError('Ocorreu um erro inesperado.');
        break;
      default:
        this.notification.showError('Ocorreu um erro inesperado.');
        break;
    }

    return Observable.throw(new Error(error.status));
  }
}

Here's my blog post explaining some way to handle this less verbosely: Receitas Angular2: Interceptando erros HTTP de forma Global

The post is written in portuguese, but the code could give you some hints. I'm using this approach in some huge projects right now, and It's working fine.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.