¿Cómo sirve un archivo para descargar con AngularJS o Javascript?

96

Tengo un texto en un área de texto oculta. Cuando se hace clic en un botón, me gustaría que el texto se ofreciera para descargar como un .txtarchivo. ¿Es esto posible usando AngularJS o Javascript?

nickponline
fuente
1
¿Qué navegadores son compatibles? Esto se puede resolver de algunas formas creativas (como data-uris, blobs, la API del historial del navegador, etc.) pero eso realmente depende.
Benjamin Gruenbaum
Angular File Saver es un buen polyfill para navegadores menos modernos.
georgeawg

Respuestas:

110

Puedes hacer algo como esto usando Blob.

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

en tu 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 la URL:

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

Tenga en cuenta que

Cada vez que llama a createObjectURL (), se crea una nueva URL de objeto, incluso si ya ha creado una para el mismo objeto. Cada uno de estos debe liberarse llamando a URL.revokeObjectURL () cuando ya no los necesite. Los navegadores los publicarán automáticamente cuando se descargue el documento; sin embargo, para un rendimiento y uso de memoria óptimos, si hay momentos seguros en los que puede descargarlos explícitamente, debe hacerlo.

Fuente: MDN

Tosh
fuente
3
Navegadores modernos e IE10 +
dave1010
@thriqon wow ¡firefox + chrome realmente muestra a los demás allí arriba!
JonnyRaa
Gran solución, pero $scope.urlno funcionó para mí. Tuve que usar window.locationen su lugar.
Gustavo Straube
7
Noté que luego la etiqueta de anclaje tiene el prefijo inseguro. Para evitarlo, deberá agregar 'blob' a la lista blanca en su app.js, usando $ compileProvider `.config (['$ compileProvider', function ($ compileProvider) {$ compileProvideraHrefSanitizationWhitelist (/ ^ \ s * (https? | ftp | mailto | tel | archivo | blob): /);} ` docs.angularjs.org/api/ng/provider/$compileProvider
coderman
10
El downloadatributo no es compatible con ninguna versión de IE o Safari, aunque caniuse.com/#feat=download
Aaron
33

Simplemente haga clic en el botón para descargar usando el siguiente código.

en html

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

En 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
fuente
1
@Amrut funcionó para mí según sea necesario, pero ¿puedes explicar el código?
Harsh Daftary
¡Como esta solución! Cuando obtenga los datos del servidor, por ejemplo, use $http.get(...)asegúrese de establecer responseType:'arraybuffer'como se explica aquí: stackoverflow.com/questions/21628378/…
Tim Büthe
Funciona en Chrome (Win), pero Safari (Mac) simplemente abre el archivo blobbed en el navegador. (blob: https / ...) Sin embargo, esta solución me permite esperar a que se resuelvan mis promesas.
sekky
26

Prueba esto

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

y visite este sitio, podría ser útil para usted :)

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

AhlemMustapha
fuente
7
Tenga cuidado con el downloadatributo que aún no es compatible con ninguna versión de IE ni Safari. Compruébalo aquí: caniuse.com/#feat=download
Pierre-Adrien Buisson
Cuando lo uso con angular, lleva la URL a $ urlRouterProvider y lo redirecciono a mi página predeterminada, ¿hay alguna solución para descargar un archivo en lugar de la navegación?
HardikDG
Siempre encuentro condescendiente cuando alguien publica un enlace como ese.
slugmandrew
22

Esto se puede hacer en javascript sin la necesidad de abrir otra ventana del navegador.

window.location.assign('url');

Reemplace 'url' con el enlace a su archivo. Puede poner esto en una función y llamarlo con ng-clicksi necesita activar la descarga desde un botón.

mm8154
fuente
2
Reemplaza el sitio con documento Pdf en su lugar para mostrar la ventana de diálogo de descarga.
fdrv
¡Gracias! Funciona de maravilla.
Sagi
14

En nuestro proyecto actual en el trabajo, teníamos un iFrame invisible y tuve que alimentar la URL del archivo al iFrame para obtener un cuadro de diálogo de descarga. Al hacer clic en el botón, el controlador genera la URL dinámica y desencadena un evento $ scope donde directivese enumera una personalizada que escribí. La directiva agregará un iFrame al cuerpo si aún no existe y establece el atributo url en él.

EDITAR: Agregar una directiva

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 directiva responde a un evento de controlador llamado downloadFile

entonces en tu controlador lo haces

$scope.$broadcast("downloadFile", url);
Ketan
fuente
1
El fragmento de código sería de gran ayuda.
Sudhir N
La directiva anterior no me funciona, cuando coloco la creación de iframe fuera del alcance. $ On crea iframe pero $ on event no llama cuando se usa la transmisión
Kanagu
Sí, $ scope. $ Broadcast solo funciona en niños. Puede poner la directiva en el ámbito de nivel superior si es posible.
Ketan
12

Puede establecer location.hrefun URI de datos que contenga los datos que desea que el usuario descargue. Además de esto, no creo que haya ninguna forma de hacerlo solo con JavaScript.

Jani Hartikainen
fuente
Esto funciona muy bien para mí, y fue mucho más limpio que todas las otras cosas que estábamos probando, o en mi humilde opinión, los complicados enfoques recomendados anteriormente. Angular lo ignora por completo. O también puede usar window.open () / $ window.open () si desea intentar abrir en otra ventana. Pero se encontrará con bloqueadores de ventanas emergentes en los navegadores modernos ...
XML
1
En Angular 1.3, $location.hrefcambiado a$window.location.href
igortg
Esta es una respuesta genial. El siguiente enlace en SO proporciona un ejemplo de trabajo completo: stackoverflow.com/a/30889331/1625820
herrtim
7

Solo me gustaría agregar eso en caso de que no descargue el archivo debido a inseguro: blob: null ... cuando se desplaza sobre el botón de descarga, debe desinfectarlo. Por ejemplo,

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

app.config (function ($ compileProvider) {

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

Si tiene acceso a en el servidor, considere configurar los encabezados como se responde en esta pregunta más general .

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

Al leer los comentarios sobre esa respuesta, es recomendable utilizar un tipo de contenido más específico que el flujo de octetos.

plong0
fuente
4

Tuve el mismo problema y dediqué muchas horas a buscar diferentes soluciones, y ahora me uno a todos los comentarios de esta publicación, espero que sea de ayuda, mi respuesta fue probada correctamente en Internet Explorer 11, Chrome y 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>

DIRECTIVA:

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

        }
    }
})

EN 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=[];
                                });
                            }; 

EN SERVICIO:

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 (en 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
fuente
Gracias, esto realmente funcionó para mí, especialmente porque necesitaba transferencias de archivos grandes.
Marcos Paulo SUS
3

Esto funcionó para mí en angular:

var a = document.createElement("a");
a.href = 'fileURL';
a.download = 'fileName';
a.click();
Zohab Ali
fuente
Si tiene una cadena que desea descargar, simplemente cambie fileURL adata:text/plain;base64,${btoa(theStringGoesHere)}
Chicken Soup
2

No quería URL estática. Tengo AjaxFactory para hacer todas las operaciones de Ajax. Recibo la URL de fábrica y la estoy vinculando de la siguiente manera.

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

Gracias @AhlemMustapha

om471987
fuente