Horia Toma
Horia Toma

Reputation: 1129

Downloading a zip file using angular

I'm trying to implement the download of a file through angular.js The file comes from the server in binary format, the content type is application/octet-stream

The download is a GET using $resource. Looking at the parameter passed to the callback (called content below), it's an object containing the byte array and also a list of properties for $resource.

Tried several ways to serve the file, but without success.

First of them:

...
var a = document.createElement('a');
a.href = "data:attachment/zip," + content;
a.download = zipName;
a.click();

In this case, the content of the zip file is [object Object]

I tried extracting the array from the object and joining everything into a string variable. The zip file in this case is way larger than the normal size. I had to set isArray: true in the service that calls $resource, otherwise there was no way to extract the byte content from the response object.

Here is how I did it:

var str = '';
for (var i = 0; i < content.length; i++) {
   str += content[i][0];
}

...
var a = document.createElement('a');
a.href = "data:attachment/zip," + str;
a.download = zipName;
a.click();

Worth mentioning that calling encodeURI on str increments drastically the size of the downloaded zip, but the archive remains invalid.

I also tried creating a Blob from the str and setting the content type to application/octet-stream, without any luck.

var blob = new Blob([str], {'type':"application/octet-stream"});
a.href = window.URL.createObjectURL(blob);
...

Don't know what I'm missing here, but it looks rather a problem of getting the right format for the byte array content and setting the correct href before simulating the click for downloading.

Help is appreciated.

Thanks

Upvotes: 2

Views: 14664

Answers (3)

Jesse Pangburn
Jesse Pangburn

Reputation: 746

In case anyone is still on AngularJS (like me) and wants to do this, I took David's answer and made it work with the angular $resource instead of using the lower level $http directly. If you use $resource, this should help you:

    var myReportingResource = $resource(baseURL + '/mypath/:command', {},{
        getExportZip: {
            method: 'GET',
            params: {
                command: 'exportzip'
            },
            responseType: 'arraybuffer',
            // don't try to convert the zip to JSON
            // instead take the data that comes back and put it in an object under a content key
            transformResponse: function(data){
                return {content: data};
            }
        }
    });

    // call the resource like this
    myReportingResource.getExportZip(yourParams).$promise.then(function(zipData){
            // create a anchor element, stick the zip data in it, and click it to download
            var anchor = angular.element('<a/>');
            anchor.attr({
                href: URL.createObjectURL(new Blob([zipData.content], {'type':'application/octet-stream'})),
                download: 'myfilename.zip'
            })[0].click();

        });

You need the transformResponse bit because otherwise AngularJS will convert your response to JSON- which is wrong with binary data. This is why later you use zipData.content to pass the data into the Blob. You can get rid of the content part, it's there for simplicity with my error handling code.

This works in Chrome and Safari as of May 2019. Didn't test anywhere else.

Upvotes: 1

atom
atom

Reputation: 722

Angular is no needed.

var zip_file_path = "" //put inside "" your server path with file.zip
var zip_file_name = "" //put inside "" file name or something
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = zip_file_path;
a.download = zip_file_name;
a.click();
document.body.removeChild(a);

Upvotes: 1

David Villacis C.
David Villacis C.

Reputation: 101

I just found your post and fixed an answer using what you enlist.

  • First you have to ensure that your angular $http request includes, like the following get example (include responseType: 'arraybuffer')

    $http.get('/downloadZip', {
                params: {
                    file: encodeURIComponent(filepath)
                },
                responseType: 'arraybuffer'
    //your code
    
  • Second on your success or promise handler you should change your window.URL.createObjectURL(blob) to URL.createObjectURL(blob). Implementing something similar to the following:

    var a = document.createElement('a');
    var blob = new Blob([data], {'type':"application/octet-stream"});
    a.href = URL.createObjectURL(blob);
    a.download = "filename.zip";
    a.click();
    

With these you are creating a new anchor element and simulating to opening it. With a correct Blob creation since the request had been modified correctly.

Upvotes: 2

Related Questions