How to get body from HttpErrorResponse in Angular 6?
Asked Answered
R

5

19

I have created a REST API call in my Angular app which downloads a file.

I am setting responseType to 'blob' since I am expecting a file in response.

But when there is no file available at the server the Response has a error code as 404 i.e Bad Request with some message in body.

But I am not able to parse that error message from body since HttpErrorResponse is giving a blob object in error.error

How do I get the actual body from the error object instead of blob.

Also is there any way to configure angular that on success of an api call parse the request in blob otherwise parse it in json ???

Hoping for a resolution

Rockribbed answered 13/12, 2018 at 9:4 Comment(6)
did you try {responseType: 'blob' as 'json'}.?Grani
When you the data from your API, if it's a valid file then what is the responseType and if it's and error, what is the responseType? Are they different, like: for valid file: 'application/octet-stream' and for errors: 'application/json'?Matabele
have you tried error.error.toString()?Gasometry
By responseType I am considering contentType header in response. So When the response is correct and file binary comes in response the content type is content-type: application/octet-stream and where there is error and just a error string comes it is 'content-type: text/plain'. @xyzRockribbed
error.error.toString() is outputing [object Blob] @PierreDucRockribbed
same behaviour @SureshKumarAriyaRockribbed
M
11

Try this

if(error.error instanceof Blob) {
    error.error.text().then(text => {
      let error_msg = (JSON.parse(text).message);
      console.log(error_msg)
    });
} else {
    //handle regular json error - useful if you are offline
}       
Manichaeism answered 22/5, 2020 at 10:41 Comment(1)
this work as expected, thanks 👍Scintillation
C
4

Parameter: { observe: 'response' }, let you read the full response including the headers. See the below description:-

Tell HttpClient that you want the full response with the observe option:

getConfigResponse(): Observable<HttpResponse<Config>> {
    return this.http.get<Config>(this.configUrl, { observe: 'response' });
}

Now HttpClient.get() returns an Observable of typed HttpResponse rather than just the JSON data.

this.configService.getConfigResponse()
    // resp is of type `HttpResponse<Config>`
    .subscribe(resp => {
        // display its headers
        const keys = resp.headers.keys();
        this.headers = keys.map(key =>
            `${key}: ${resp.headers.get(key)}`);

        // access the body directly, which is typed as `Config`.
        this.config = { ...resp.body };
    });

and getting Error body like that:-

private handleError(error: HttpErrorResponse) {
  if (error.error instanceof ErrorEvent) {
    // A client-side or network error occurred. Handle it accordingly.
    console.error('An error occurred:', error.error.message);
  } else {
    // The backend returned an unsuccessful response code.
    // The response body may contain clues as to what went wrong,
    console.error(
      `Backend returned code ${error.status}, ` +
      `body was: ${error.error}`);
  }
  // return an observable with a user-facing error message
  return throwError(
    'Something bad happened; please try again later.');
};

import { catchError} from 'rxjs/operators';

getConfig() {
  return this.http.get<Config>(this.configUrl)
    .pipe(
      catchError(this.handleError)
    );
}

Reference: https://angular.io/guide/http : Reading the full response

Change your code accordingly.

Caphaitien answered 13/12, 2018 at 9:26 Comment(1)
I am not asking for how to handle the error ! I am asking about a use case in which by providing responseType as blob. HttpErrorResponse is not correctly been parsed by Angular !Rockribbed
V
4

For future visitors (since the title is generic):

If the backend returns JSON upon error (ideally, following RFC 7807, which would also mean application/problem+json content-type too), the error.error is a JSON object, not a string. So to print it, for example, you would need to stringify it first:

console.error(
  `Backend returned code ${error.status}, ` +
  `body was: ${JSON.stringify(error.error)}`);

I believe the confusion starts from the official Angular documentation, which contains this statement:

// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.error(
  `Backend returned code ${error.status}, ` +
  `body was: ${error.error}`);

But with error.error being a JSON object (in the standard cases), you get printed [object Object] for the body instead of the string representation of that JSON object. Same unhelpful output if you try ${error.error.toString()}.

Various answered 4/12, 2019 at 18:47 Comment(1)
I was wondering, being string literal is a valid JSON String("string"). Is there a way to compare it. "var23" == JSON.stringify("var23").toString() is false. "var23".length is 5 and JSON.stringify("var23").length is 7, because of the JSON double quotes notationBenson
M
1

If the returned ContentType are different then you can leverage it to distinguish whether it's a correct binary file or a text in binary format.

lets consider you have two files, a service, which handles your request and a component which does the business logic

Inside your service, have your download method like:

 public downloadFile(yourParams): Observable<yourType | Blob> {
        return this._http.post(yourRequestURL, yourParams.body, {responseType: 'blob'}).pipe(
            switchMap((data: Blob) => {
                if (data.type == <ResponseType> 'application/octet-stream') {
                    // this is a correct binary data, great return as it is
                    return of(data);
                } else {
                    // this is some error message, returned as a blob
                    let reader = new FileReader();
                    reader.readAsBinaryString(data);  // read that message
                    return fromEvent(reader, 'loadend').pipe(
                        map(() => {
                            return JSON.parse(reader.result); // parse it as it's a text.
                            // considering you are handling JSON data in your app, if not then return as it is
                        })
                    );
                }
            })
        );
}

In your component

 public downloadFile(params): void {
        this._service.downloadFile(params)
            subscribe((data: yourType | Blob) => {
                if (data instanceof Blob) {
                    fileSaverSave(data, filename);  // use fileSaver or however you are downloading the content
                    // add an import for saveAs (import { saveAs as fileSaverSave } from 'file-saver';)
                } else {
                    // do your componnet logic to show the errors
                }
            })    
    }

If you wish, you can have everything inside your component itself.

Matabele answered 13/12, 2018 at 11:26 Comment(0)
R
0

You could try a separate error handler function, which returns the response as T as follows -

public handleError<T>(operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {

        // TODO: send the error to remote logging infrastructure
        console.error(error); // log to console instead

        // TODO: better job of transforming error for user consumption
        console.log(`${operation} failed: ${error.message}`);

        // Let the app keep running by returning an empty result.
        return of(result as T);
    };
}

Then simply use it to track errors in your request as follows -

return this.http.post(this.appconstants.downloadUrl, data, { responseType: 'blob' }).pipe(
    map(this.loggerService.extractFiles),
    catchError(this.loggerService.handleError<any>('downloadFile'))    // <----
);

FYI, the function extractFiles that I used above to return a file is as follows -

public extractFiles(res: Blob): Blob{
    return res;
}
Rosinweed answered 13/12, 2018 at 9:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.