53

I am using AngularJS, and I have a MVC 4 API that returns a HttpResponseMessage with an attachment.

var result = new MemoryStream(pdfStream, 0, pdfStream.Length) {
     Position = 0
};
var response = new HttpResponseMessage {
     StatusCode = HttpStatusCode.OK,
     Content = new StreamContent(result)
};
response.Content.Headers.ContentDisposition = 
           new ContentDispositionHeaderValue("attachment") {
                    FileName = "MyPdf.pdf"
           };
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return response;

I am using a jQuery plugin called fileDownload... which download the file beautifully... but I havent found the way to do this in the "Angular" way... any help will be appreciated.

// _e

Edwin Beltran
  • 4,419
  • 4
  • 20
  • 16

10 Answers10

72

I had the same problem. Solved it by using a javascript library called FileSaver

Just call

saveAs(file, 'filename');

Full http post request:

$http.post('apiUrl', myObject, { responseType: 'arraybuffer' })
  .success(function(data) {
            var file = new Blob([data], { type: 'application/pdf' });
            saveAs(file, 'filename.pdf');
        });
Naoe
  • 1,209
  • 8
  • 12
56

Here you have the angularjs http request to the API that any client will have to do. Just adapt the WS url and params (if you have) to your case. It's a mixture between Naoe's answer and this one:

$http({
    url: '/path/to/your/API',
    method: 'POST',
    params: {},
    headers: {
        'Content-type': 'application/pdf',
    },
    responseType: 'arraybuffer'
}).success(function (data, status, headers, config) {
    // TODO when WS success
    var file = new Blob([data], {
        type: 'application/csv'
    });
    //trick to download store a file having its URL
    var fileURL = URL.createObjectURL(file);
    var a = document.createElement('a');
    a.href = fileURL;
    a.target = '_blank';
    a.download = 'yourfilename.pdf';
    document.body.appendChild(a); //create the link "a"
    a.click(); //click the link "a"
    document.body.removeChild(a); //remove the link "a"
}).error(function (data, status, headers, config) {
    //TODO when WS error
});

Explanation of the code:

  1. Angularjs request a file.pdf at the URL: /path/to/your/API.
  2. Success is received on the response
  3. We execute a trick with JavaScript on the front-end:
    • Create an html link ta: <a>.
    • Click the hyperlink <a> tag, using the JS click() function
  4. Remove the html <a> tag, after its click.
tremendows
  • 4,262
  • 3
  • 34
  • 51
  • 1
    Any idea about browser compatibility of this solution? – Maciej Gurban Apr 13 '15 at 12:12
  • 2
    According to [MDN](https://developer.mozilla.org/en/docs/Web/API/Blob) IE10 is a minimum. – Maciej Gurban Apr 13 '15 at 12:20
  • 2
    responseType : 'arraybuffer' makes the difference! – Bruno Aug 03 '16 at 14:04
  • This code was very helpful; however I recommend that you also remove the link you added to the body after the `a.click();` code is executed so that the download links do not continue to exist on the page. – Anil Sep 14 '16 at 22:45
  • 3
    To accomplish the deleting of the link after it's added to the dom, simply replace the last 2 lines in your success handler with this: `var element = document.body.appendChild(a); a.click(); document.body.removeChild( element );` – Anil Sep 14 '16 at 22:48
  • not working in safari. getting this message . "'[object BlobConstructor]' is not a constructor (evaluating 'new Blob([data], { type: contentType })')" – Wella Aug 08 '17 at 11:07
  • According to developer.mozilla.org/es/docs/Web/API/Blob , Blob constructor works since Safari 6, which version are you using @Wella? – tremendows Aug 08 '17 at 13:47
  • 1
    @tremendows Thank you for your reply. yes, I'm using an earlier version than 6. – Wella Aug 10 '17 at 04:19
  • @Wella good moment to udpate the version? v6.0 it's available since July 25, 2012 (last it's Safari v10.1, July 2017). Web browsers and web libraries have advanced a lot during the last 5 years. – tremendows Aug 10 '17 at 09:10
  • 1
    Chrome 65 rolled out a [tougher](https://venturebeat.com/2018/02/14/google-details-how-chrome-will-block-ads/) popup blocker. The answer posted above now causes a "popup blocked" icon to appear in the address bar for me. Users have to allow the popup before the file will download. – mamacdon Mar 29 '18 at 13:56
  • @mamacdon How to prevent popup in chrome or is there any way to download file in chrome? – Manish Balodia Apr 16 '18 at 07:16
  • @ManishBalodia Try moving the code that performs the download into a `click` event handler. In my app I added a "Click to download" button for this. When the user clicks the button, it runs the code from the original answer, and does not trigger the popup blocker. The key is having the download connected to a user action, like a click: this appears to be permitted. But doing it from an Ajax callback or timer will get blocked. – mamacdon Apr 16 '18 at 14:56
  • Thank you very much, this worked for me! As a side note, from looking at documentation here: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL it looks like you will also want to add `URL.revokeObjectURL(file);` after `document.body.removeChild(a)` – Saltz3 Nov 02 '18 at 13:54
  • Would this solution 'stream' the data into the file? or will it all be loaded into the browser before moving into a file? – Mos No Mar 06 '19 at 05:55
  • Mos No , Stream the data into the file is called Download. A streaming file is simply played as it becomes available, while a download is stored onto memory. Take a look https://www.lifehack.org/536701/streaming-downloading-which-the-best-use-your-mobile-data – tremendows Mar 06 '19 at 09:22
10

per various post... you cannot trigger a download via XHR. I needed to implement condition for the download, so, My solution was:

//make the call to the api with the ID to validate
someResource.get( { id: someId }, function(data) {
     //confirm that the ID is validated
     if (data.isIdConfirmed) {
         //get the token from the validation and issue another call
         //to trigger the download
         window.open('someapi/print/:someId?token='+ data.token);
     }
});

I wish that somehow, or someday the download can be triggered using XHR to avoid the second call. // _e

Edwin Beltran
  • 4,419
  • 4
  • 20
  • 16
  • 3
    To keep things "angular", be sure to use $window or $location to kick off the file download. – Ben Lesh Jan 15 '13 at 17:14
  • i dont want to open a new window and download ,how i can do in the same window – Prashobh Oct 04 '13 at 08:01
  • @prash, you cannot trigger a download using XHR calls. I mean, you can serialize to base64, and see the data in the callback... but, the browser wont allow access to the file system to store the serialized document. // _e – Edwin Beltran Oct 24 '13 at 14:48
8

There is 2 ways to do it in angularjs..

1) By directly redirecting to your service call..

<a href="some/path/to/the/file">clickme</a>

2) By submitting hidden form.

$scope.saveAsPDF = function() {
    var form = document.createElement("form");
    form.setAttribute("action", "some/path/to/the/file");
    form.setAttribute("method", "get");
    form.setAttribute("target", "_blank");

    var hiddenEle1 = document.createElement("input");
    hiddenEle1.setAttribute("type", "hidden");
    hiddenEle1.setAttribute("name", "some");
    hiddenEle1.setAttribute("value", value);

    form.append(hiddenEle1 );

    form.submit();

}

use the hidden element when you have to post some element

<button ng-click="saveAsPDF()">Save As PDF</button>
c0r3yz
  • 835
  • 8
  • 19
  • I really tried ANY other way I could find, but just nothing worked for me except for workaround #2... Thanks! – yair Apr 20 '16 at 15:26
3

The solution by tremendows worked well for me. However , file was not getting saved in Internet Explorer 10+ also. The below code worked for me for IE browser.

var file = new Blob(([data]), { type: 'application/pdf' });
if (window.navigator.msSaveOrOpenBlob) {
    navigator.msSaveBlob(file, 'fileName.pdf');
}
Mike
  • 809
  • 1
  • 10
  • 23
Manu Sharma
  • 671
  • 7
  • 11
3

Another example using Blob() Code:

function save(url, params, fileName){
    $http.get(url, {params: params}).success(function(exporter) {
        var blob = new Blob([exporter], {type: "text/plain;charset=utf-8;"});
        saveAs(blob, fileName);
    }).error(function(err) {
        console.log('err', err);
    });
};

// Save as Code
function saveAs(blob, fileName){
    var url = window.URL.createObjectURL(blob);

    var doc = document.createElement("a");
    doc.href = url;
    doc.download = fileName;
    doc.click();
    window.URL.revokeObjectURL(url);
}
Makah
  • 4,435
  • 3
  • 47
  • 68
3

This is how I solved this problem

$scope.downloadPDF = function () {
    var link = document.createElement("a");
    link.setAttribute("href",  "path_to_pdf_file/pdf_filename.pdf");
    link.setAttribute("download", "download_name.pdf");
    document.body.appendChild(link); // Required for FF
    link.click(); // This will download the data file named "download_name.pdf"
}
0
string trackPathTemp = track.trackPath;

            //The File Path
            var videoFilePath = HttpContext.Current.Server.MapPath("~/" + trackPathTemp);

            var stream = new FileStream(videoFilePath, FileMode.Open, FileAccess.Read);
            var result = new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StreamContent(stream)
            };
            result.Content.Headers.ContentType = new MediaTypeHeaderValue("video/mp4");
            result.Content.Headers.ContentRange = new ContentRangeHeaderValue(0, stream.Length);
            // result.Content.Headers.Add("filename", "Video.mp4");
            result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
            {
                FileName = "Video.mp4"
            };
            return result;
Lucas Zamboulis
  • 2,494
  • 5
  • 24
  • 27
Ranjit
  • 11
  • 1
0

using FileSaver.js solved my issue thanks for help, below code helped me

'$'

 DownloadClaimForm: function (claim) 
{
 url = baseAddress + "DownLoadFile";
 return  $http.post(baseAddress + "DownLoadFile", claim, {responseType: 'arraybuffer' })
                            .success(function (data) {
                                var file = new Blob([data], { type: 'application/pdf' });
                                saveAs(file, 'Claims.pdf');
                            });


    }
Petter Friberg
  • 21,252
  • 9
  • 60
  • 109
0

There is angular service written angular file server Uses FileSaver.js and Blob.js

 vm.download = function(text) {
    var data = new Blob([text], { type: 'text/plain;charset=utf-8' });
    FileSaver.saveAs(data, 'text.txt');
  };
Augustas
  • 1,167
  • 21
  • 31