angular: 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

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 39 (4 by maintainers)

Most upvoted comments

@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.'));
  }

@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.

I struggled with this for hours and nothing worked except adding the one line above:

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

Is there another way to prompt the user to download the file?

//This doesn’t work

let url = window.URL.createObjectURL(blob);              
             window.open(url, "_blank");
          //This works but is there another way to prompt the user???
          var link = document.createElement('a');
           link.href = window.URL.createObjectURL(blob);
           link.download = file.fileName;
           link.click();

Hello again… I was able to make it work with native xhr. I’m happy with this solution, however I still believe that there is a bug in angular2.

Here are both methods. The first does not work:

    generateMonthlySpreadsheet2(searchCriteria: Object) {
        let headers = new Headers();
        headers.append('Content-Type', 'application/json');
        headers.append('responseType', 'blob');

        return this._http.post(`${AppSettings.REPORTS_API_URL}/Report/MonthlySpreadsheet`, {headers: headers})
            .map(response => {
                if (response.status == 400) {
                    this.handleError;
                } else if (response.status == 200) {
                    var contentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
                    var blob = new Blob([(<any>response)._body], { type: contentType });            // size is 89KB instead of 52KB
//                    var blob = new Blob([(<any>response).arrayBuffer()], { type: contentType });  // size is 98KB instead of 52KB
//                    var blob = new Blob([(<any>response).blob()], { type: contentType });         // received Error: The request body isn't either a blob or an array buffer
                    return blob;
                }
            })
            .catch(this.handleError);
    }

This one works:

    generateMonthlySpreadsheet(searchCriteria: Object): Observable<Object[]> {
        return Observable.create(observer => {

            let xhr = new XMLHttpRequest();

            xhr.open('POST', `${AppSettings.REPORTS_API_URL}/Report/MonthlySpreadsheet`, true);
            xhr.setRequestHeader('Content-type', 'application/json');
            xhr.responseType='blob';

            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {

                        var contentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
                        var blob = new Blob([xhr.response], { type: contentType });
                        observer.next(blob);
                        observer.complete();
                    } else {
                        observer.error(xhr.response);
                    }
                }
            }
            xhr.send();

        });
    }

You can close this out if you feel you should, however I’m sure someone else will bump up against this one.

Thank you for the xhr suggestion!

Hard to say anything without repro 😦

@aulona2014 nothing wrong with your service, but the wrong is about how you renamed it.

public downloadCSVFile(id) { this.ws.download(id).subscribe( (res) => { //console.log(res) FileSaver.saveAs(res, ‘test.txt’) } ); }

you get a file with extension .docx from API, and you renamed it to .txt, that’s why you getting that’s text.

try to change the code above, to this or you can renamed it to name you to want, but with the original extension.

 public downloadCSVFile(id) {
      this.ws.download(id).subscribe(
      (res) => {
           //console.log(res)
        FileSaver.saveAs(res, 'test.docx')
      }
    );
  }

and you need open with the application support extension .docx, (like ms word, google docs)

this is an example, I’m getting the text similar to you because I’m open the extension .docx in notepad, where notepad is not supported open .docx file.

image

Thank may help.

I have to include FileSaver and it shows the icon to download or save the file. My backend is in spring

downloadFile(): Observable<Object[]> { return Observable.create(observer => {

    let xhr = new XMLHttpRequest();

    xhr.open('GET', `http://localhost:8080/download`, true);
    xhr.setRequestHeader('Content-type', 'application/json');
    xhr.responseType='blob';

    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {

          var contentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
          var blob = new Blob([xhr.response], { type: contentType });
          observer.next(blob);
          observer.complete();
          FileSaver.saveAs(blob, "Factura" + EXCEL_EXTENSION);
        } else {
          observer.error(xhr.response);
        }
      }
    }
    xhr.send();
  });

}