使用Angular从服务器下载文本/CSV内容作为文件

57

我想从一个node.js服务器流式传输一个csv文件。 服务器部分非常简单:

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)
}

在我的Angular控制器中,我正在尝试做这样的事情

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

当我的视图中有一个带有ng-click的按钮被点击时,会调用此函数:

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

我查看了其他有关在Angular中从服务器下载文件的答案,但没有找到对我有效的内容。是否有一种常见的方法可以实现这个功能?这似乎应该是一件很简单的事情。


我能看到的第一件事是,您在$scope.csv中的URL与您在server.get中的不对应。 - dcodesmith
@dcodesmith 请忽略之前的消息(已经更新),那只是为了提问。实际上我确实在服务器上看到了该请求。 - Michael
5个回答

117

$http服务返回一个promise对象,其中包含如下两种回调方法。

$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
  });

2
它虽然以某种hackish的方式工作:首先,Chrome的弹出窗口拦截器阻止了点击,然后当我允许弹出窗口时,它在新窗口中打开了下载。有没有办法使它更加愉悦?如果没有,我想对我来说已经足够好了 :) - Michael
非常好,谢谢!我已经找到了另一个解决问题的方法(发布了答案)。我接受你的回答,因为你是第一个 :) - Michael
7
在Mozilla Firefox上无法正常工作...但在Chrome上正常。 - Shreyansh Bele
8
要让它在Mozilla Firefox上运行,请将您的锚点附加到文档:angular.element(document.body).append(anchor); - I3i0
1
https://dev59.com/pmEi5IYBdhLWcg3w2POB#35448277 可在IE 11和Chrome中使用。 - user3366706
显示剩余12条评论

21

关于这个问题,大部分网络上的参考资料都指出了一个事实,即你不能直接通过ajax调用下载文件。我曾经看到过一些(hackish)解决方案,其中包括使用iframes以及像@dcodesmith的解决方案,它们都可行并且有效。

这里有另外一个解决方案,它适用于Angular并且非常简单明了。

视图中,将csv下载按钮用以下方式包裹在<a>标签内:

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

(请注意,这里的target="_self很重要,它可以禁用Angular路由在ng-app内进行更多操作,请点击此处了解更多)

在你的控制器中,你可以按照以下方式定义csv_link

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

$window.location.search是可选的,只有在您想将附加搜索查询传递到服务器时才需要使用)

现在每次点击按钮时,它都应该开始下载。


1
有没有办法在向服务器发送大型对象并获取生成的Word/PDF文件时实现这一点? - Siva Kumar
@ShivKumar 是的,在响应POST请求时返回CSV文件的URL。 - idbehold
你能否设置JSFiddle并提供完整的控制器和UI代码? - Sanjay
2
这个解决方案与使用由Angular评估的简单<a/>标签没有什么不同。 - Meysam Feghhi

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

此代码适用于Mozilla和Chrome


谢谢您的回答。这个在Chrome和Firefox中都可以工作。 - Corey Quillen
每个浏览器的哪个版本? - valeriocomo
这太聪明了。该技术可用于从Angular中下载任意数据文件。 - Lavamantis
IE 版本是什么? - Komal Singh
在Firefox和Chrome中运行正常,但在Safari中不起作用。 - Chiranjib
运行正常,但是当我将它放在nginx前端代理后,出现了网络错误。 - andyczerwonka

7
这是对我而言在IE 11+、Firefox和Chrome上可行的方法。在Safari中,它会下载一个文件但被标记为未知类型且文件名未设置。
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();
}

0

使用 Angular 1.5.9

我是通过将 window.location 设置为 CSV 文件下载 URL 来使其正常工作的。已测试并且可以在最新版本的 Chrome 和 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>

php 中设置以下响应头:

$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',
];

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接