How to get body/content of request from the HttpErrorResponse? [Angular ErrorHandler]

3.9k views Asked by At

Let's say I sent a post request to the server to create a user. If the server responds with an error, I want to get the body(form) I submitted(attached to that request) from the ErrorHandler. The reason for this is that, for example, when the "create user" fails, I want to show a notification with some details taken from the form and a button to redirect you back to the respective page with the fields populated again with the retrieved form.

This is how my ErrorHandler looks like:

@Injectable()
export class ErrorsHandler implements ErrorHandler {
    constructor(
        private injector: Injector,
    ) { }

    handleError(error: Error | HttpErrorResponse) {
        const errorsService = this.injector.get(ErrorsService);
        const router = this.injector.get(Router);
        const zone = this.injector.get(NgZone);
        if (error instanceof HttpErrorResponse) {
            // Server error happened
            if (!navigator.onLine) {
                return console.log('No Internet Connection.');
            }
            else if (error.status === HttpStatus.UNAUTHORIZED) {
                console.log('ErrorsHandler handled HttpStatus Unauthorized. Navigating back to \'/login\' page.');
                zone.run(() => router.navigate(['/login']));
            }
            else {
                // Http Error
                //How do I get the form from here? I need it for user notification.
                return console.log('%c SERVER ERROR ', 'background: #222; color: #ff6961 ; font-size: 15px; border: 2px solid #ff6961;', error);
            }
        } else {
            // Client Error Happend
            // Send the error to the server and then
            // redirect the user to the page with all the info
            errorsService
                .log(error)
                .subscribe(errorWithContextInfo => {
                    router.navigate(['/error'], { queryParams: errorWithContextInfo });
                });
        }
    }
}
3

There are 3 answers

0
Nick Wang On

First of all, you must confirm BE return JSON error in body. Next step you can custom HttpInterceptor for your idea, more details you can search by keyword angular httpinterceptor.

It is my source for HttpInterceptor, there may be some help.

import { Injectable } from '@angular/core';
import { HttpRequest, HttpInterceptor, HttpHandler, HttpEvent, HttpResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';

@Injectable()
export class SiteHttpInterceptor implements HttpInterceptor {
  constructor(
  ) {}

  intercept(request: HttpRequest<any>, httpHandler: HttpHandler): Observable<any> {
      let token = localStorage.getItem('token');
      if (('/api/users/token').indexOf(request.url) < 0 && token) {
          request = request.clone({
              setHeaders: {
                  'authorization': 'bearer ' + token,
                  'Authenticationtoken': 'bearer ' + token
              }
          });
      }

      return httpHandler.handle(request).pipe(
          tap((event: HttpEvent<any>) => {
              //success
          },
          (err: any) => {
              //error
          }),
          catchError(err => {
              if (err.status === 401) {
                  // if you want to do sth. when 401 can code here
              } else {
                  // other
              }
              return throwError(err);
          })
      );
  }
}

and please setup the HttpInterceptor to app.module.ts

import { SiteHttpInterceptor } from './providers/http-interceptor';
@NgModule({
providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }]

Let me know is it ok for you or not :)

0
Gabriel Lopez On

I think it's not posible to get the body from a HttpErrorResponse instance since it extends HttpResponseBase which doesn't have a body property as the normal HttpResponse does.

 export declare class HttpErrorResponse extends HttpResponseBase implements Error {
    readonly name: string;
    readonly message: string;
    readonly error: any | null;
    /**
     * Errors are never okay, even when the status code is in the 2xx success range.
     */
    readonly ok: boolean;
    constructor(init: {
        error?: any;
        headers?: HttpHeaders;
        status?: number;
        statusText?: string;
        url?: string;
    });
}

What I've done is to use a Response Incerceptor:

import { Injectable } from '@angular/core';
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { ResponseBusiness } from '../Models/General/ResponseBusiness.model';
import { MsgService } from '../services/msg.service';
import { AlertService } from '../services/alert.service';

@Injectable()
export class ResponseInterceptor implements HttpInterceptor {
  constructor(private _msg: MsgService, private _alertService: AlertService) {
  }

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

    return next.handle(req).map(resp => {

      const response = <HttpResponse<ResponseBusiness<Object>>> resp;

      if (resp instanceof HttpResponse) {
      }


      /* do whatever you need with the req.body */

      if (resp instanceof HttpErrorResponse) {
        const body = JSON.parse(req.body);
        if (body && body.avoidMsg) {
          return resp;
        }
      }

      if (response.status === 200 && !response.body.result.status) {
        this._msg.setMsg({message: `${response.body.result.codeNumber} ${response.body.result.codeDescription}`, tipo: 'error'});
      }
      return resp;
    });

  }
}

Then add the inteceptor to you app.module like so:

providers: [
    {provide: HTTP_INTERCEPTORS, useClass: ResponseInterceptor, multi: true}],
0
Adam On

I've got a variation on @GabrielLopez 's answer with an interceptor:

import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse}
  from "@angular/common/http";
import {Observable, throwError} from "rxjs";
import {catchError, tap} from "rxjs/operators";

@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>,
            next: HttpHandler):
        Observable<HttpEvent<any>> {
    return next.handle(req)
      .pipe(
        tap((ev: HttpEvent<any>) => {
          if (ev instanceof HttpResponse) {
            console.log(`processing response ${ev.status}`);
          }
        }),
        catchError(response => {
          console.log('Processing http error', response);
          if (response.error) {
            return throwError(response.error);
          } else if (response.message) {
            return throwError(response.message);
          } else {
            return throwError(response);
          }
        })
      );
  }
}

And like Gabriel's answer, the interceptor needs to be declared in the app.module.ts providers:

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpErrorInterceptor,
      multi: true
    },
    ...

I'm not happy with this because it means probably that the Angular HttpClient error handling is overengineered. Getting the error from a REST API call shouldn't be difficult.