34

Context: I'm trying to download a binary file from a backend (that requires some data posted as json-body) and save it with file-saver using the filename specified by the backend in the content-disposition header. To access the headers I think I need the HttpResponse.

But I'm unable to use angular's HttpClient.post<T>(...): Observable<HttpResponse<T>>; method with a Blob.

When I call

this.httpclient.post<Blob>('MyBackendUrl', 
        params, 
        {observe: 'response', responseType: 'blob'});
the compiler complains about the 'blob' ('json' is accepted by the compiler):


error TS2345: Argument of type '{ observe: "response"; responseType: "blob"; }' is not assignable to parameter of type '{ headers?: HttpHeaders | { [header: string]: string | string[]; }; observe?: "body"; params?: Ht...'.
  Types of property 'observe' are incompatible.
    Type '"response"' is not assignable to type '"body"'.

When I put the options in an own object as seen in https://stackoverflow.com/a/48016652/2131459 (but without the "as" ...) the post(...):Observable is called and I cannot access the headers.

Btw, even the simple example return this.http.get<Blob>('backendUrl', {responseType: 'blob'}); as seen e.g. in https://stackoverflow.com/a/46882407/2131459 doesn't work for me.

Versions used

  • Angular Version: 5.0.3 (will be updated to latest 5 in a week or so)
  • typescript: 2.4.2
  • webpack: 3.8.1
Patrick Favre
  • 29,166
  • 6
  • 96
  • 114
Chris
  • 1,148
  • 1
  • 9
  • 29
  • I'm sorry, I can't seem to get your issue : you can't read headers from the response, or you want to download a blob file ? –  Mar 16 '18 at 11:13

3 Answers3

61

When using observe:response, don't type the call (post<Blob>(...)), as the returned Observable will be of HttpResponse. So this should work:

this.httpclient.post('MyBackendUrl', 
    params,
    {observe: 'response', responseType: 'blob'}
);

Why this happens, is there's two versions of the post method, one with a generic type, one without:

/**
     * Construct a POST request which interprets the body as JSON and returns the full event stream.
     *
     * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `T`.
     */
    post<T>(url: string, body: any | null, options: {
        headers?: HttpHeaders | {
            [header: string]: string | string[];
        };
        observe: 'events';
        params?: HttpParams | {
            [param: string]: string | string[];
        };
        reportProgress?: boolean;
        responseType?: 'json';
        withCredentials?: boolean;
    }): Observable<HttpEvent<T>>;
    /**
     * Construct a POST request which interprets the body as an `ArrayBuffer` and returns the full response.
     *
     * @return an `Observable` of the `HttpResponse` for the request, with a body type of `ArrayBuffer`.
     */
    post(url: string, body: any | null, options: {
        headers?: HttpHeaders | {
            [header: string]: string | string[];
        };
        observe: 'response';
        params?: HttpParams | {
            [param: string]: string | string[];
        };
        reportProgress?: boolean;
        responseType: 'arraybuffer';
        withCredentials?: boolean;
    }): Observable<HttpResponse<ArrayBuffer>>;
funkizer
  • 3,224
  • 1
  • 13
  • 16
  • 7
    Thanks. I hadn't realized that adding the `responseType: 'blob'` implicitly changes the result to Observable / Obserrvable> – Chris Mar 16 '18 at 13:32
15

you can use as like

responseType: 'blob' as 'json'
dheeraj kumar
  • 161
  • 1
  • 6
11

Other answers are right but they are missing the example.

The main answer first when the responseType is set the return type of the response is changed to Blob. To solve this add observe: 'response' which returns HTTPResponse.

Example: I stumbled upon this issue and spent 6 hours solving.

So, here I present an example to get filename from headers and download the file:

downloadPDF(url: string): Observable<any> {
return this.http.get<any>(url, { responseType: 'blob', observe: 'response' }).pipe(
  map((result:HttpResponse<Blob>) => {
    console.log(result);
    saveAs(result, "Quotation.pdf");
    return result;
  }));

Here the http is instance of HttpClient, saveAs() is a method of FileSaver npm package same as the OP.

There is one more problem you might get only 5 headers(Cache-Control, Pragma, etc) in the result.headers and not your custom header for e.g. x-filename.

The reason behind this CORS. Basically CORS doesn't allow browsers to access more than handfull of headers (listed in the link above).

So solve this you would have to change server/API to send Access-Control-Expose-Headers header with the request.

Kishan Vaishnav
  • 1,201
  • 10
  • 27