AngularJS $http-post - convert binary to excel file and download

145,278

Solution 1

Just noticed you can't use it because of IE8/9 but I'll push submit anyway... maybe someone finds it useful

This can actually be done through the browser, using blob. Notice the responseType and the code in the success promise.

$http({
    url: 'your/webservice',
    method: "POST",
    data: json, //this is your json data string
    headers: {
       'Content-type': 'application/json'
    },
    responseType: 'arraybuffer'
}).success(function (data, status, headers, config) {
    var blob = new Blob([data], {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
    var objectUrl = URL.createObjectURL(blob);
    window.open(objectUrl);
}).error(function (data, status, headers, config) {
    //upload failed
});

There are some problems with it though like:

  1. It doesn't support IE 8 and 9:
  2. It opens a pop up window to open the objectUrl which people might have blocked
  3. Generates weird filenames

It did work!

blob The server side code in PHP I tested this with looks like this. I'm sure you can set similar headers in Java:

$file = "file.xlsx";
header('Content-disposition: attachment; filename='.$file);
header('Content-Length: ' . filesize($file));
header('Content-Transfer-Encoding: binary');
header('Cache-Control: must-revalidate');
header('Pragma: public');
echo json_encode(readfile($file));

Edit 20.04.2016

Browsers are making it harder to save data this way. One good option is to use filesaver.js. It provides a cross browser implementation for saveAs, and it should replace some of the code in the success promise above.

Solution 2

This is how you do it:

  1. Forget IE8/IE9, it is not worth the effort and does not pay the money back.
  2. You need to use the right HTTP header,use Accept to 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' and also you need to put responseType to 'arraybuffer'(ArrayBuffer but set with lowercase).
  3. HTML5 saveAs is used to save the actual data to your wanted format. Note it will still work without adding type in this case.
$http({
    url: 'your/webservice',
    method: 'POST',
    responseType: 'arraybuffer',
    data: json, //this is your json data string
    headers: {
        'Content-type': 'application/json',
        'Accept': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
    }
}).success(function(data){
    var blob = new Blob([data], {
        type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
    });
    saveAs(blob, 'File_Name_With_Some_Unique_Id_Time' + '.xlsx');
}).error(function(){
    //Some error log
});

Tip! Don't mix " and ', stick to always use ', in a professional environment you will have to pass js validation for example jshint, same goes for using === and not ==, and so on, but that is another topic :)

I would put the save excel in another service, so you have clean structure and the post is in a proper service of its own. I can make a JS fiddle for you, if you don't get my example working. Then I would also need some json data from you that you use for a full example.

Happy coding.. Eduardo

Solution 3

Download the server response as an array buffer. Store it as a Blob using the content type from the server (which should be application/vnd.openxmlformats-officedocument.spreadsheetml.sheet):

var httpPromise = this.$http.post(server, postData, { responseType: 'arraybuffer' });
httpPromise.then(response => this.save(new Blob([response.data],
    { type: response.headers('Content-Type') }), fileName));

Save the blob to the user's device:

save(blob, fileName) {
    if (window.navigator.msSaveOrOpenBlob) { // For IE:
        navigator.msSaveBlob(blob, fileName);
    } else { // For other browsers:
        var link = document.createElement('a');
        link.href = window.URL.createObjectURL(blob);
        link.download = fileName;
        link.click();
        window.URL.revokeObjectURL(link.href);
    }
}

Solution 4

Worked for me -

$scope.downloadFile = function () {
        Resource.downloadFile().then(function (response) {
            var blob = new Blob([response.data], { type: "application/pdf" });
            var objectUrl = URL.createObjectURL(blob);
            window.open(objectUrl);
        },
        function (error) {
            debugger;
        });
    };

Which calls the following from my resource factory-

  downloadFile: function () {
           var downloadRequst = {
                method: 'GET',
                url: 'http://localhost/api/downloadFile?fileId=dfckn4niudsifdh.pdf',
                headers: {
                    'Content-Type': "application/pdf",
                    'Accept': "application/pdf"
                },
                responseType: 'arraybuffer'
            }

            return $http(downloadRequst);
        }

Make sure your API sets the header content type too -

        response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
        response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");

Solution 5

There is no way (to my knowledge) to trigger the download window in your browser from Javascript. The only way to do it is to redirect the browser to a url that streams the file to the browser.

If you can modify your REST service, you might be able to solve it by changing so the POST request doesn't respond with the binary file, but with a url to that file. That'll get you the url in Javascript instead of the binary data, and you can redirect the browser to that url, which should prompt the download without leaving the original page.

Share:
145,278
Alex Man
Author by

Alex Man

profile for user123 at Stack Overflow, Q&A for professional and enthusiast programmers http://stackoverflow.com/users/flair/3253853.png

Updated on July 09, 2022

Comments

  • Alex Man
    Alex Man almost 2 years

    I've created an application in Angular JS for downloading an Excel workbook through $http post.

    In the below code I'm passing the information in the form of JSON , and send it to the server REST web service (java) through an angular $http post. The web service uses the information from the JSON and produces an Excel workbook. In the response within the success body of $http post, I'm getting binary data within that data variable, but don't know how to convert it and download as an Excel file.

    Can anyone please tell me some solution for this for converting the binary to Excel file and download?

    My code is as given below:

    $http({
            url: 'myweb.com/myrestService',
            method: "POST",
            data: json, //this is your json data string
            headers: {
               'Content-type': 'application/json'
            }
        }).success(function (data, status, headers, config) {
    
            // Here i'm getting excel sheet binary datas in 'data' 
    
        }).error(function (data, status, headers, config) {
    
        });
    
  • user2171669
    user2171669 over 9 years
    I think in many cases it wont work. U need to set content type of $http to 'blob'.
  • Simon H
    Simon H about 9 years
    Worked from me.. but depends on github.com/eligrey/FileSaver.js/blob/master/FileSaver.min.js FileSaver for saveAs
  • nosdalg
    nosdalg almost 9 years
    @jorg Is it possible to add similar headers in django python.
  • Jorg
    Jorg almost 9 years
    @Explore-X yes I'm positive you can. Not familiar with django though, so I can't tell you how exactly.
  • Mitaksh Gupta
    Mitaksh Gupta almost 9 years
    @Jorg in the screenshot of the Excel you have shared above, the text in the field, i.e. "my content", it is exceeding the width of the cell, is there an option through which I can set the options like default column-width or word wrap? The issue I am facing is that while printing a file like your above, the data gets truncated if there are multiple columns and the width of the data is more than the width of the column. Much thanks!
  • Jorg
    Jorg almost 9 years
    @MitakshGupta the file is a copy of what you saved. If you generated it with the proper column width it would come through that way I imagine. I can try if you like.
  • Mitaksh Gupta
    Mitaksh Gupta almost 9 years
    @Jorg Please have a look at this question I posted a couple of days back stackoverflow.com/questions/31918248/… This gives the exact description of the problem I am facing
  • chipit24
    chipit24 over 8 years
    This worked for me as well. Take a look at github.com/alferov/angular-file-saver if you're using AngularJS. And if you won't be supported IE8/9, it may be nice to let the user know, for that I use this: github.com/burocratik/outdated-browser. Also, setting responseType: 'arraybuffer' is important.
  • treejanitor
    treejanitor over 8 years
    Excellent! The responseType: 'arraybuffer' was the key for me. I had to fight with connect-livereload issues causing zip file corruption on the server, so this angular tweak was much faster-found welcome fix! Thanks!
  • Jorg
    Jorg about 8 years
    Try filesaver.js instead of window.open
  • puiu
    puiu about 7 years
    Just going to piggyback on the most upvoted comment to add that you don't need to use a responsetype: arraybuffer because you then have to manually convert it to a blob using new Blob(). Unless you need to modify the incoming blob from the server, then just use a response type of blob directly.
  • Vinoth
    Vinoth about 7 years
    can you answer this question stackoverflow.com/questions/43894768/…
  • Vinoth
    Vinoth about 7 years
    can you answer this question stackoverflow.com/questions/43894768/…
  • Wella
    Wella almost 7 years
    getting this error in safari. "'[object BlobConstructor]' is not a constructor (evaluating 'new Blob([data], { type: contentType })')"
  • Sledge
    Sledge over 6 years
    Is there a good way to control the filename using this approach?
  • Hardik Sondagar
    Hardik Sondagar about 6 years
    No longer works after Chrome 65 update. It's blocking cross-origin <a download> chromestatus.com/feature/4969697975992320
  • Jorg
    Jorg about 6 years
    @HardikSondagar FileSaver.js is working on this: github.com/eligrey/FileSaver.js/issues/428
  • Hardik Sondagar
    Hardik Sondagar about 6 years
    @Jorg Can we create a dynamic form with blob URL as an action and submit using js?
  • dierre
    dierre almost 5 years
    is there a way to get the filename in the header with this solution?
  • dierre
    dierre almost 5 years
    is there a way to get the filename from the headers?