57

I am trying to stream a csv file from a node.js server. The server portion is very simple :

server.get('/orders' function(req, res) {
  res.setHeader('content-type', 'text/csv');
  res.setHeader('content-disposition', 'attachment; filename='orders.csv');
  return orders.pipe(res); // assuming orders is a csv file readable stream (doesn't have to be a stream, can be a normal response)
}

In my angular controller I am trying to do something like this

$scope.csv = function() {
    $http({method: 'GET', url: '/orders'});
};

This function is called when there's a click on a button with ng-click in my view :

<button ng-click="csv()">.csv</button>

I have looked at other answers about downloading files from server in Angular, but didn't find anything that worked for me. Is there a common way to do this ? Seems like something that should be simple.

dcodesmith
  • 9,590
  • 4
  • 36
  • 40
Michael
  • 22,196
  • 33
  • 132
  • 187
  • first I can see is that you url in `$scope.csv` isn't doesn't correspond with what you have in `server.get` – dcodesmith Jan 03 '14 at 12:57
  • @dcodesmith ignore that please (fixed) was just for the question. I actually do see the request on the server – Michael Jan 03 '14 at 12:59

5 Answers5

117

$http service returns a promise which has two callback methods as shown below.

$http({method: 'GET', url: '/someUrl'}).
  success(function(data, status, headers, config) {
     var anchor = angular.element('<a/>');
     anchor.attr({
         href: 'data:attachment/csv;charset=utf-8,' + encodeURI(data),
         target: '_blank',
         download: 'filename.csv'
     })[0].click();

  }).
  error(function(data, status, headers, config) {
    // handle error
  });
dcodesmith
  • 9,590
  • 4
  • 36
  • 40
  • That doesn't solve my problem since it just prints the file content to the console. I need to tell it somehow to let the browser handle the download... – Michael Jan 03 '14 at 13:10
  • have a look at this http://stackoverflow.com/questions/20300547/download-csv-file-from-web-api-in-angular-js – dcodesmith Jan 03 '14 at 13:16
  • 2
    It worked although in a kind of a hackish way: first chrome's popup blocker blocked the click, then when I allowed popups, it opened the download in a new window. Is there anyway to make it more pleasant ? if no, I guess it's good enough for me :) – Michael Jan 03 '14 at 14:12
  • @Michael that should work. Let me know if it does pls – dcodesmith Jan 03 '14 at 14:44
  • very nice, thanks! I have found another solution to the problem (posted the answer). I'm accepting yours since you were first :) – Michael Jan 03 '14 at 15:01
  • Hmm, no one's had this so far not that I'm aware of anyway. When you "download" is it a file or what? – dcodesmith May 27 '14 at 12:43
  • What i've done is far easier, in my button method, instead of performing a request and handling successes, elements, etc. I just used it to form the full url, then used window.location = myUrl; if the url points to a downloadable file, it will download, but remember that you have to configure your server so even if a file type is to be displayed in a browser, it snaps to download. – jmdiego Oct 24 '14 at 15:07
  • Great solution, solved my problem with downloading a file through AngularJS and C# web API ! – R Pelzer Oct 28 '14 at 10:51
  • This works great for small files. However, for files larger than 1Mb, the download is not triggered in the browser (but devtools shows that the request contains the data). Any idea why this happens and how to solve this ? – laugri Oct 06 '15 at 16:38
  • 7
    On Mozilla Firefox is not working ...working fine in Chrome. – Shreyansh Bele Feb 02 '16 at 07:52
  • Great answer I just did a copy paste && it worked as a charm! – davidrl1000 Mar 13 '16 at 06:12
  • @fbtb, try replacing `charset=utf-8` with `base64 ` – dcodesmith Mar 21 '16 at 20:45
  • 8
    To get it work on Mozilla Firefox, attach your anchor to document: angular.element(document.body).append(anchor); – I3i0 May 31 '16 at 14:19
  • This works just fine for me. However, it saves the file without prompting for a name, which would have been nice. Plus it automatically opens the file in Excel, but that might just be the way that I have Windows configured – Mawg says reinstate Monica Jan 18 '17 at 00:12
  • Unfortunately this doesn't work when I put it behind an nginx front-end proxy. Otherwise, it works fine. – andyczerwonka May 02 '17 at 12:23
  • 1
    https://stackoverflow.com/a/35448277/3366706 works for IE 11 and chrome – user3366706 Jun 02 '17 at 21:45
  • https://stackoverflow.com/questions/20300547/download-csv-file-from-web-api-in-angular-js/64315710#64315710 – Tejashree Oct 12 '20 at 10:15
21

Most of the references on the web about this issue point out to the fact that you cannot download files via ajax call 'out of the box'. I have seen (hackish) solutions that involve iframes and also solutions like @dcodesmith's that work and are perfectly viable.

Here's another solution I found that works in Angular and is very straighforward.

In the view, wrap the csv download button with <a> tag the following way :

<a target="_self" ng-href="{{csv_link}}">
  <button>download csv</button>
</a>

(Notice the target="_self there, it's crucial to disable Angular's routing inside the ng-app more about it here)

Inside youre controller you can define csv_link the following way :

$scope.csv_link = '/orders' + $window.location.search;

(the $window.location.search is optional and onlt if you want to pass additionaly search query to your server)

Now everytime you click the button, it should start downloading.

Community
  • 1
  • 1
Michael
  • 22,196
  • 33
  • 132
  • 187
21
var anchor = angular.element('<a/>');
anchor.css({display: 'none'}); // Make sure it's not visible
angular.element(document.body).append(anchor); // Attach to document

anchor.attr({
    href: 'data:attachment/csv;charset=utf-8,' + encodeURI(data),
    target: '_blank',
    download: 'filename.csv'
})[0].click();

anchor.remove(); // Clean it up afterwards

This code works both Mozilla and chrome

Komal Singh
  • 451
  • 5
  • 19
7

This is what worked for me for IE 11+, Firefox and Chrome. In safari it downloads a file but as unknown and the filename is not set.

if (window.navigator.msSaveOrOpenBlob) {
    var blob = new Blob([csvDataString]);  //csv data string as an array.
    // IE hack; see http://msdn.microsoft.com/en-us/library/ie/hh779016.aspx
    window.navigator.msSaveBlob(blob, fileName);
} else {
    var anchor = angular.element('<a/>');
    anchor.css({display: 'none'}); // Make sure it's not visible
    angular.element(document.body).append(anchor); // Attach to document for FireFox

    anchor.attr({
        href: 'data:attachment/csv;charset=utf-8,' + encodeURI(csvDataString),
        target: '_blank',
        download: fileName
})[0].click();
anchor.remove();
}
Nilath
  • 133
  • 2
  • 8
0

Using angular 1.5.9

I made it working like this by setting the window.location to the csv file download url. Tested and its working with the latest version of Chrome and IE11.

Angular

   $scope.downloadStats = function downloadStats{
        var csvFileRoute = '/stats/download';
        $window.location = url;
    }

html

<a target="_self" ng-click="downloadStats()"><i class="fa fa-download"></i> CSV</a>

In php set the below headers for the response:

$headers = [
    'content-type'              => 'text/csv',
    'Content-Disposition'       => 'attachment; filename="export.csv"',
    'Cache-control'             => 'private, must-revalidate, post-check=0, pre-check=0',
    'Content-transfer-encoding' => 'binary',
    'Expires' => '0',
    'Pragma' => 'public',
];
Adam Prax
  • 6,413
  • 3
  • 30
  • 31
forsimb
  • 91
  • 1
  • 7