Solvedangular Angular2 download excel file from Web API, file is corrupt

**I'm submitting a bug report

I believe that I have encountered a bug in angular2 with downloading a file from a Web API.

I know you want a plunkr that demonstrates the problem, but I'm not sure how to do that since we need a web api, as well as the angular2 code.

Current behavior

I am trying to download a file that I created with ClosedXML. I have verified that the file is not corrupt but, for some reason, it works just with Angular1, not Angular2.

Minimal reproduction of the problem with instructions

The web api code to return the file is:

HttpResponseMessage response = new HttpResponseMessage();
response.StatusCode = HttpStatusCode.OK;
response.Content = new ByteArrayContent(ms.GetBuffer());
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
return response;

In Angular2, in my web service:

let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('responseType', 'arrayBuffer');
this.observableDataGet = this._http.post(`${AppSettings.REPORTS_API_URL}/Report/MonthlySpreadsheet`, {headers: this.getHeaders()})
    .map(response => {
        if (response.status == 400) {
            return "FAILURE";
        } else if (response.status == 200) {
            var contentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
            var blob = new Blob([response.arrayBuffer()], { type: contentType });
            return blob;
        }
    })

and in my component:

.subscribe(blob => {
    var downloadUrl= URL.createObjectURL(blob);
    window.open(downloadUrl);
},

A file IS downloaded, but it is corrupt when I try to access it and is TWICE the size of the file when downloaded with Angular1.

Expected behavior
If I call the SAME API with Angular1, the file is downloaded fine. My service code:

function generateMonthlySpreadsheet(header) {
    var request = $http({
        method: "post",
        responseType: 'arraybuffer',
        url: TEST_API_URL + 'Report/MonthlySpreadsheet',
        timeout: 30000,
        headers: header
    });
    return ( request.then(handleSuccess, handleError) );
}

where handleSuccess returns response.data (which I can't get at for angular2)

and the code to invoke the service:

alertAppService.generateMonthlySpreadsheet(header).then(function (data){
    var blob = new Blob([data], {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
    var objectUrl = URL.createObjectURL(blob);
    window.open(objectUrl);

Interestingly enough, in Angular2, if I simply change my webservice to a GET (I wanted POST however, but just to try it out) then got rid of the service code and simply made this call, the file is fine:

window.open(${AppSettings.REPORTS_API_URL}/Report/MonthlySpreadsheet, "_blank");

So, really, what is the difference here? Why does the same or very similar code work for Angular1 but not Angular2??

Please tell us about your environment:
Windows 10, Web Storem and Visual Studio, Nuget and NPM

  • Angular version: 2.0.X
    2.2.1

  • Browser:
    all

I'm sorry to not produce a plunkr here. Hopefully, you can help determine if this is a bug or not. I seriously think it is.

Thank you,

Karen Healey

40 Answers

✔️Accepted Answer

@healkar01 I had the same issue and I resolved using native angular2 http request in this way:

Backend:

export function exportExcel(req, res) {
  return Person.dummyExcel()
    .then(excel => {
      res.setHeader('Content-Type', 'application/vnd.ms-excel');
      res.setHeader('Content-Disposition', `attachment; filename=people-export.xlsx`);
      res.end(excel);
    })
    .catch(handleError(res));
}

(Note: excel is a binary Buffer)

Angular2 Service:

import { Response, ResponseContentType } from '@angular/http';
import { AuthHttp } from 'angular2-jwt';

...
exportExcel(): Observable<any> {
    return this.authHttp.get(`${environment.API_BASEURL}/api/people/export`, { responseType: ResponseContentType.Blob })
                        .map(res => res.blob())
                        .catch(this.handleError)
  }

Angular2 Component:

exportExcel() { 
    this.personService.exportExcel()
    .subscribe(data  => {console.log(`excel data: ${data}`); fileSaver.saveAs(data, 'people-export.xlsx')},
               error => console.log("Error downloading the file."),
               ()    => console.log('Completed file download.'));
  }

Other Answers:

@mgarcs Thanks!
Your answer should be up-voted more!

I didn't notice the difference at first.
In short, the OP mistake was using responseType as a header.
Instead one should use it in get options (second argument, type: RequestOptionsArgs).
like:

this.http.get(url , { responseType: ResponseContentType.Blob }).

The service looks fine but the component is wrong. You already have a blob (your data is a blob) and you are creating another blob from the current blob.

I have simply this:

        .subscribe(blob => {

                /*
                 var downloadUrl= URL.createObjectURL(blob)
                 window.open(downloadUrl, "_blank");
                 */

                // Doing it this way allows you to name the file
                var link=document.createElement('a');
                link.href=window.URL.createObjectURL(blob);
                link.download="Report.xlsx";
                link.click();
            },

Karen

I am a relatively new developer.

Thank you all for your contribution. The following works in Chrome but not in IE 11:
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = file.fileName;
link.click();

My HttpReponseMessage looks like this:
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new ByteArrayContent(buffer);
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
response.StatusCode = HttpStatusCode.OK;
return response;

I appreciate any input to make this work in IE 11.

Thanks.

More Issues: