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 w
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.
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;
}
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.
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()}
.
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
}