Como você fornece um arquivo para download com AngularJS ou Javascript?

96

Tenho algum texto em uma área de texto oculta. Quando um botão é clicado, gostaria de ter o texto oferecido para download como um .txtarquivo. Isso é possível usando AngularJS ou Javascript?

apelido
fonte
1
Quais navegadores você suporta? Isso pode ser resolvido de algumas maneiras criativas (como dados-uris, blobs, a API de histórico do navegador, etc), mas isso realmente depende.
Benjamin Gruenbaum
Angular File Saver é um bom polyfill para navegadores menos modernos.
georgeawg

Respostas:

110

Você pode fazer algo assim usando Blob.

<a download="content.txt" ng-href="{{ url }}">download</a>

em seu controlador:

var content = 'file content for example';
var blob = new Blob([ content ], { type : 'text/plain' });
$scope.url = (window.URL || window.webkitURL).createObjectURL( blob );

para habilitar o URL:

app = angular.module(...);
app.config(['$compileProvider',
    function ($compileProvider) {
        $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|file|blob):/);
}]);

Observe que

Cada vez que você chama createObjectURL (), um novo URL de objeto é criado, mesmo se você já tiver criado um para o mesmo objeto. Cada um deles deve ser liberado chamando URL.revokeObjectURL () quando você não precisar mais deles. Os navegadores os liberarão automaticamente quando o documento for descarregado; entretanto, para desempenho e uso de memória ideais, se houver momentos seguros em que você possa descarregá-los explicitamente, você deve fazer isso.

Fonte: MDN

Tosh
fonte
3
Navegadores modernos e IE10 +
dave1010
@thriqon wow firefox + chrome realmente mostrando os outros lá em cima!
JonnyRaa
Ótima solução, mas $scope.urlnão funcionou para mim. Eu tive que usar em seu window.locationlugar.
Gustavo Straube
7
Percebi que a tag âncora é prefixada com inseguro. Para contornar isso, você precisará adicionar 'blob' à lista branca em seu app.js, usando $ compileProvider `.config (['$ compileProvider', function ($ compileProvider) {$ compileProvider.aHrefSanitizationWhitelist (/ ^ \ s * (https? | ftp | mailto | tel | arquivo | blob): /);} ` docs.angularjs.org/api/ng/provider/$compileProvider
coderman
10
O downloadatributo não é compatível com nenhuma versão do IE ou Safari, embora caniuse.com/#feat=download
Aaron
33

Basta clicar no botão para fazer o download usando o seguinte código.

em html

<a class="btn" ng-click="saveJSON()" ng-href="{{ url }}">Export to JSON</a>

No controlador

$scope.saveJSON = function () {
			$scope.toJSON = '';
			$scope.toJSON = angular.toJson($scope.data);
			var blob = new Blob([$scope.toJSON], { type:"application/json;charset=utf-8;" });			
			var downloadLink = angular.element('<a></a>');
                        downloadLink.attr('href',window.URL.createObjectURL(blob));
                        downloadLink.attr('download', 'fileName.json');
			downloadLink[0].click();
		};

Amrut
fonte
1
@Amrut funcionou para mim conforme necessário, mas você pode explicar o código?
Harsh Daftary
Curta essa solução! Ao obter os dados do servidor, por exemplo, usando $http.get(...)certifique-se de definir responseType:'arraybuffer'como explicado aqui: stackoverflow.com/questions/21628378/…
Tim Büthe 01 de
Funciona no Chrome (Win), mas Safari (Mac) apenas abre o arquivo blobbed no navegador. (blob: https / ...) Assim, essa solução me deixa esperar que minhas promessas sejam resolvidas.
sekky
26

Tente isto

<a target="_self" href="mysite.com/uploads/ahlem.pdf" download="foo.pdf">

e visite este site, pode ser útil para você :)

http://docs.angularjs.org/guide/

AhlemMustapha
fonte
7
Cuidado com o downloadatributo que ainda não é compatível com nenhuma versão do IE ou Safari. Confira aqui: caniuse.com/#feat=download
Pierre-Adrien Buisson
Quando eu uso com o angular, ele pega o url para $ urlRouterProvider e redireciona para a minha página padrão, existe alguma solução para baixar um arquivo em vez de navegação
HardikDG
Sempre acho paternalista quando alguém posta um link como esse.
slugmandrew de
22

Isso pode ser feito em javascript sem a necessidade de abrir outra janela do navegador.

window.location.assign('url');

Substitua 'url' pelo link para o seu arquivo. Você pode colocá-lo em uma função e chamá-lo com ng-clickse precisar acionar o download de um botão.

mm8154
fonte
2
Ele substitui o site por um documento PDF em vez de mostrar a janela de diálogo de download.
fdrv
Obrigado! Funciona como um encanto.
Sagi
14

Em nosso projeto atual no trabalho, tínhamos um iframe invisível e eu tive que alimentar o url do arquivo para o iframe para obter uma caixa de diálogo de download. No clique do botão, o controlador gera a url dinâmica e dispara um evento $ scope onde um custom directiveque escrevi está listando. A diretiva anexará um iframe ao corpo se ele ainda não existir e definirá o atributo url nele.

EDIT: Adicionando uma diretiva

appModule.directive('fileDownload', function ($compile) {
    var fd = {
        restrict: 'A',
        link: function (scope, iElement, iAttrs) {

            scope.$on("downloadFile", function (e, url) {
                var iFrame = iElement.find("iframe");
                if (!(iFrame && iFrame.length > 0)) {
                    iFrame = $("<iframe style='position:fixed;display:none;top:-1px;left:-1px;'/>");
                    iElement.append(iFrame);
                }

                iFrame.attr("src", url);


            });
        }
    };

    return fd;
});

Esta diretiva responde a um evento de controlador chamado downloadFile

então no seu controlador você faz

$scope.$broadcast("downloadFile", url);
Ketan
fonte
1
O snippet de código seria uma grande ajuda.
Sudhir N
A diretiva acima não está funcionando para mim, quando coloco a criação de iframe fora do escopo. $ On cria iframe, mas $ no evento não está chamando ao usar transmissão
Kanagu
Sim $ scope. $ Broadcast funciona apenas para crianças. Você pode colocar a diretiva no escopo de nível superior, se possível.
Ketan
12

Você pode definir location.hrefum URI de dados contendo os dados que deseja permitir que o usuário faça download. Além disso, acho que não há como fazer isso apenas com JavaScript.

Jani Hartikainen
fonte
Isso funciona muito bem para mim e era muito mais limpo do que todas as outras coisas que estávamos tentando, ou IMHO, as abordagens complicadas recomendadas acima. Angular ignora isso completamente. Ou você também pode usar window.open () / $ window.open () se quiser tentar abrir em outra janela. Mas você encontrará bloqueadores de pop-up em navegadores modernos ...
XML
1
No Angular 1.3, $location.hrefalterado para$window.location.href
igortg
Esta é uma ótima resposta. O link a seguir no SO fornece um exemplo funcional completo: stackoverflow.com/a/30889331/1625820
herrtim
7

Gostaria apenas de acrescentar que caso ele não baixe o arquivo por causa de inseguro: blob: null ... quando você passa o mouse sobre o botão de download, é necessário limpá-lo. Por exemplo,

var app = angular.module ('app', []);

app.config (function ($ compileProvider) {

$compileProvider.aHrefSanitizationWhitelist(/^\s*(|blob|):/);
Samir Alajmovic
fonte
5

Se você tiver acesso a no servidor, considere definir os cabeçalhos conforme respondido nesta pergunta mais geral .

Content-Type: application/octet-stream
Content-Disposition: attachment;filename=\"filename.xxx\"

Lendo os comentários sobre essa resposta, é aconselhável usar um Content-Type mais específico do que o octet-stream.

plong0
fonte
4

Tive o mesmo problema e passei muitas horas procurando diferentes soluções, e agora junto todos os comentários deste post. Espero que seja útil, minha resposta foi testada corretamente no Internet Explorer 11, Chrome e FireFox.

HTML:

<a href="#" class="btn btn-default" file-name="'fileName.extension'"  ng-click="getFile()" file-download="myBlobObject"><i class="fa fa-file-excel-o"></i></a>

DIRETIVA:

directive('fileDownload',function(){
    return{
        restrict:'A',
        scope:{
            fileDownload:'=',
            fileName:'=',
        },

        link:function(scope,elem,atrs){


            scope.$watch('fileDownload',function(newValue, oldValue){

                if(newValue!=undefined && newValue!=null){
                    console.debug('Downloading a new file'); 
                    var isFirefox = typeof InstallTrigger !== 'undefined';
                    var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
                    var isIE = /*@cc_on!@*/false || !!document.documentMode;
                    var isEdge = !isIE && !!window.StyleMedia;
                    var isChrome = !!window.chrome && !!window.chrome.webstore;
                    var isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
                    var isBlink = (isChrome || isOpera) && !!window.CSS;

                    if(isFirefox || isIE || isChrome){
                        if(isChrome){
                            console.log('Manage Google Chrome download');
                            var url = window.URL || window.webkitURL;
                            var fileURL = url.createObjectURL(scope.fileDownload);
                            var downloadLink = angular.element('<a></a>');//create a new  <a> tag element
                            downloadLink.attr('href',fileURL);
                            downloadLink.attr('download',scope.fileName);
                            downloadLink.attr('target','_self');
                            downloadLink[0].click();//call click function
                            url.revokeObjectURL(fileURL);//revoke the object from URL
                        }
                        if(isIE){
                            console.log('Manage IE download>10');
                            window.navigator.msSaveOrOpenBlob(scope.fileDownload,scope.fileName); 
                        }
                        if(isFirefox){
                            console.log('Manage Mozilla Firefox download');
                            var url = window.URL || window.webkitURL;
                            var fileURL = url.createObjectURL(scope.fileDownload);
                            var a=elem[0];//recover the <a> tag from directive
                            a.href=fileURL;
                            a.download=scope.fileName;
                            a.target='_self';
                            a.click();//we call click function
                        }


                    }else{
                        alert('SORRY YOUR BROWSER IS NOT COMPATIBLE');
                    }
                }
            });

        }
    }
})

NO CONTROLADOR:

$scope.myBlobObject=undefined;
$scope.getFile=function(){
        console.log('download started, you can show a wating animation');
        serviceAsPromise.getStream({param1:'data1',param1:'data2', ...})
        .then(function(data){//is important that the data was returned as Aray Buffer
                console.log('Stream download complete, stop animation!');
                $scope.myBlobObject=new Blob([data],{ type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
        },function(fail){
                console.log('Download Error, stop animation and show error message');
                                    $scope.myBlobObject=[];
                                });
                            }; 

EM SERVIÇO:

function getStream(params){
                 console.log("RUNNING");
                 var deferred = $q.defer();

                 $http({
                     url:'../downloadURL/',
                     method:"PUT",//you can use also GET or POST
                     data:params,
                     headers:{'Content-type': 'application/json'},
                     responseType : 'arraybuffer',//THIS IS IMPORTANT
                    })
                    .success(function (data) {
                        console.debug("SUCCESS");
                        deferred.resolve(data);
                    }).error(function (data) {
                         console.error("ERROR");
                         deferred.reject(data);
                    });

                 return deferred.promise;
                };

BACKEND (em PRIMAVERA):

@RequestMapping(value = "/downloadURL/", method = RequestMethod.PUT)
public void downloadExcel(HttpServletResponse response,
        @RequestBody Map<String,String> spParams
        ) throws IOException {
        OutputStream outStream=null;
outStream = response.getOutputStream();//is important manage the exceptions here
ObjectThatWritesOnOutputStream myWriter= new ObjectThatWritesOnOutputStream();// note that this object doesn exist on JAVA,
ObjectThatWritesOnOutputStream.write(outStream);//you can configure more things here
outStream.flush();
return;
}
Havelino
fonte
Obrigado, isso realmente funcionou para mim, especialmente porque eu precisava de grandes transferências de arquivos.
Marcos Paulo SUS
3

Isso funcionou para mim no angular:

var a = document.createElement("a");
a.href = 'fileURL';
a.download = 'fileName';
a.click();
Zohab Ali
fonte
Se você deseja fazer o download de uma string, basta alterar fileURL paradata:text/plain;base64,${btoa(theStringGoesHere)}
Chicken Soup
2

Eu não queria um URL estático. Eu tenho AjaxFactory para fazer todas as operações de Ajax. Estou obtendo o url da fábrica e vinculando-o da seguinte maneira.

<a target="_self" href="{{ file.downloadUrl + '/' + order.OrderId + '/' + fileName }}" download="{{fileName}}">{{fileName}}</a>

Obrigado @AhlemMustapha

om471987
fonte