0

Trying to download a pdf doc with Angular, we wrote this:

var _config = { headers : {'Accept' : '*/*'},
                responseType : 'arraybuffer'
              };

var success = function(data, status, header, config) {
   $log.debug('Download resume success - type:' + typeof (data));
   var _contentType = (header('Content-Type'));
   var blob = new Blob([ data ], { type : _contentType });
   var url = (window.URL || window.webkitURL).createObjectURL(blob);
   var anchor = angular.element('<a/>');
   anchor.attr({
      href : url,
      target : '_blank',
      download : _fileName
   })[0].click();
}
$http.get(_url, _config).success(success).error(error);

We've tried all permutations of blob and arraybuffer but data always returns as a String with the extended characters 'decoded' which is to say broken.

_contentType is always application/pdf although we've tried forcing it to application/octet-stream as well.

Suggestions and pointers welcomed!

Update

This looks to be a bug someplace between Angular (1.3.15 & 1.4.8) and Chrome's (46.0) XMLHttpRequest implementation where response.data is always returned as a 'decoded' string. Neither Firefox (42) nor IE (10) have this problem and all of the solutions below would most likely work (as does or original solution).

I've reported this as a possible bug to both AngularJS & Chrome.

Mike Summers
  • 2,139
  • 3
  • 27
  • 57

4 Answers4

3

I ran in to this problem myself a few months back. I had to use FileSaver.js to handle it.

So after you install FileSaver.js have your success function look something like this:

function success(response) {
    var blob = new Blob([response.data], { type: "application/pdf"});
    //change download.pdf to the name of whatever you want your file to be
    saveAs(blob, "download.pdf");
}
user12341234
  • 6,573
  • 6
  • 23
  • 48
dethstrobe
  • 545
  • 4
  • 13
  • We've tried FileSaver.js, no difference. One question I have with FileSaver is how do I know the Blob is handled by FileSaver and not by the built-in Blob? – Mike Summers Nov 23 '15 at 21:29
  • What are you using in the backend? I also had problems with my node/express server corrupting my zip data. I had to fs.writeFile to a temporary file on the server then res.download(pathOfFile, filename + ".zip"); And that seemed to work. – dethstrobe Nov 23 '15 at 21:44
  • Spring is our backend. The file is only modified when it gets to Angular, when we inspect the `data` object. Over the wire it's fine. GET from another Spring server is fine. GET from Postman saves the file to disk and it's uncorrupted. The only time we have trouble is trying to get it download to our Angular client :-( – Mike Summers Nov 23 '15 at 21:59
  • What version of Angular are you using? Have you logged the data from the response to make sure the data is what you're expecting it to be? And lastly, are you making sure that the MIME type is set to `"application/pdf"`? I also noticed you're using `.success` to handle your http promise, which has been deprecated, which is why I'm asking which version you're using. – dethstrobe Nov 23 '15 at 22:08
  • 1.3.x, we've tried 1.4.x and no difference (although we're going to upgrade everything to 1.4.x). Yes, over the wire the data is a pdf file, save it to disk and Acrobat opens it without a problem. Yes, the mime type is always `application/pdf` unless we force it to `octet-stream`. – Mike Summers Nov 23 '15 at 22:25
  • Have you tried doing a `console.log` in your success callback to see what the data is? – dethstrobe Nov 23 '15 at 23:08
  • Yes, typeof(data) says 'string' which seems to me to be the root cause- that Angular is converting the raw stream to text and mangling the pdf's extended characters. – Mike Summers Nov 23 '15 at 23:13
  • That's your problem. Your backend needs to send it as an ArrayBuffer, which type will be object. – dethstrobe Nov 23 '15 at 23:31
  • That backend is Java/Spring and sends the raw file as `application/pdf`, what do you mean by 'ArrayBuffer'? Not catching the Response body as a `blob`? – Mike Summers Nov 24 '15 at 00:00
  • Mozilla has a really nice article on [ArrayBuffers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer). I recall there being problems sending string data that the Blob constructor and Angular just didn't like to play together with. I'll be damned if I can remember the exact reason, but it was the reason why I needed to make my node/express server use express's download API, which converted the zip file in to an ArrayBuffer for me. – dethstrobe Nov 24 '15 at 00:13
1

You need to tell angularjs that you are receiving a binary file in your $http.get or $http.post method. Use {responseType : 'arraybuffer'}.

For example:

$http.post('/pdf/link', requestData, { responseType : 'arraybuffer' }).then(function (data, status, headers) {

        headers = data.headers();
        var contentType = headers['content-type'];
        var linkElement = document.createElement('a');

        try {
            var blob = new Blob([data.data], {type: "application/pdf"});
            var url = window.URL.createObjectURL(blob);

            linkElement.setAttribute('href', url);

            linkElement.setAttribute("download", "mypdf.pdf");

            var clickEvent = new MouseEvent("click", {
                "view": window,
                "bubbles": true,
                "cancelable": false
            });

            linkElement.dispatchEvent(clickEvent);
        } catch (ex) {
            console.log(ex);
        }
    }
Kihats
  • 3,326
  • 5
  • 31
  • 46
  • This can be improved by the following lines: `linkElement.href = url; linkElement.download = 'file.pdf'; linkElement.click();` – Renis1235 Apr 10 '22 at 09:35
0

Just change it from your html:

<a target="_self" href="example.com/uploads/file.pdf" download="file_new_name.pdf">
Lucas
  • 9,871
  • 5
  • 42
  • 52
0

Check encoding with your api and in this lines, this worked for me:

            var filepdf = Base64.encode(response.data);
            $scope.filepdf = 'data:application/pdf;base64,' + pdfdata;

CONTROLLER

    $scope.pdfDownloads = function() {
        DownloadPath.get({id: $rootScope.user.account_id}, function (data) {
            $scope.createtext = "Download PDF file";
            $scope.download_pdf_filename = data.filename;
            $scope.getPdf = true;
            $scope.download_dir = file_url;

            $http.get($scope.download_dir).then(function (response) {
                var pdfdata= Base64.encode(response.data);
                $scope.pdfdata= 'data:application/pdf;base64,' + pdfdata;
                $('#pdfanchor').attr({
                    href: $scope.pdfdata,
                    download: $scope.download_pdf_filename
                })
            });                    
        });
    };

HTML

<a href=""ng-click="pdfDownloads()" >Download</a>

EDIT: THIS IS HOW WE STRUCTURED THE RESPONSE (PHP functions)

contentType = 'application/octet-stream';
contentDescription = 'File Transfer';
contentDisposition = 'attachment; filename=' . basename(file_nae);
expires = 0;
cacheControl = 'must-revalidate';
contentLength = filesize($file_name);
body =  base64_encode(file_get_contents($file_name));
Lucas
  • 9,871
  • 5
  • 42
  • 52