Comment servir un fichier à télécharger avec AngularJS ou Javascript?

96

J'ai du texte dans une zone de texte cachée. Lorsqu'un bouton est cliqué, je souhaite que le texte soit proposé au téléchargement sous forme de .txtfichier. Est-ce possible en utilisant AngularJS ou Javascript?

nickponline
la source
1
Quels navigateurs prenez-vous en charge? Cela peut être résolu de manière créative (comme les uris de données, les blobs, l'API d'historique du navigateur, etc.) mais cela dépend vraiment.
Benjamin Gruenbaum
Angular File Saver est un bon polyfill pour les navigateurs moins modernes.
georgeawg

Réponses:

110

Vous pouvez faire quelque chose comme ça en utilisant Blob.

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

dans votre contrôleur:

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

afin d'activer l'URL:

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

Veuillez noter que

Chaque fois que vous appelez createObjectURL (), une nouvelle URL d'objet est créée, même si vous en avez déjà créé une pour le même objet. Chacun de ces éléments doit être libéré en appelant URL.revokeObjectURL () lorsque vous n'en avez plus besoin. Les navigateurs les publieront automatiquement lorsque le document sera déchargé; cependant, pour des performances et une utilisation de la mémoire optimales, s'il existe des moments sûrs où vous pouvez les décharger explicitement, vous devez le faire.

Source: MDN

Tosh
la source
3
Navigateurs modernes et IE10 +
dave1010
@thriqon wow firefox + chrome montrant vraiment les autres là-haut!
JonnyRaa
Excellente solution, mais $scope.urln'a pas fonctionné pour moi. J'ai dû utiliser à la window.locationplace.
Gustavo Straube
7
J'ai remarqué que la balise d'ancrage est alors préfixée avec unsafe. Pour contourner cela, vous devrez ajouter 'blob' à la liste blanche dans votre app.js, en utilisant $ compileProvider `.config (['$ compileProvider', function ($ compileProvider) {$ compileProvider.aHrefSanitizationWhitelist (/ ^ \ s * (https? | ftp | mailto | tel | file | blob): /);} ` docs.angularjs.org/api/ng/provider/$compileProvider
coderman
10
L' downloadattribut n'est pris en charge dans aucune version d'IE ou de Safari bien que caniuse.com/#feat=download
Aaron
33

Cliquez simplement sur le bouton pour télécharger en utilisant le code suivant.

en html

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

Dans le contrôleur

$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
la source
1
@Amrut a travaillé pour moi au besoin, mais pouvez-vous expliquer le code?
Harsh Daftary
Aimez cette solution! Lorsque vous récupérez les données du serveur, par exemple en utilisant, $http.get(...)assurez-vous de définir responseType:'arraybuffer'comme expliqué ici: stackoverflow.com/questions/21628378/…
Tim Büthe
Fonctionne dans Chrome (Win), mais Safari (Mac) ouvre simplement le fichier blobbed dans le navigateur. (blob: https / ...) Comme ça, cette solution me permet d'attendre que mes promesses soient résolues.
sekky
26

Essaye ça

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

et visitez ce site, cela pourrait vous être utile :)

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

AhlemMustapha
la source
7
Méfiez-vous de l' downloadattribut qui n'est toujours pris en charge par aucune version d'IE ni de Safari. Découvrez-le ici: caniuse.com/#feat=download
Pierre-Adrien Buisson
Lorsque je l'utilise avec angular, il prend l'URL de $ urlRouterProvider et la redirige vers ma page par défaut, existe-t-il une solution pour télécharger un fichier au lieu de la navigation
HardikDG
Je trouve toujours que c'est condescendant quand quelqu'un publie un lien comme celui-là.
slugmandrew
22

Cela peut être fait en javascript sans avoir besoin d'ouvrir une autre fenêtre de navigateur.

window.location.assign('url');

Remplacez «url» par le lien vers votre fichier. Vous pouvez mettre cela dans une fonction et l'appeler avec ng-clicksi vous devez déclencher le téléchargement à partir d'un bouton.

mm8154
la source
2
Il remplace le site par Pdf doc à la place pour afficher la fenêtre de dialogue de téléchargement.
fdrv
Merci! Fonctionne comme un charme.
Sagi
14

Dans notre projet actuel au travail, nous avions un iFrame invisible et je devais fournir l'URL du fichier à l'iFrame pour obtenir une boîte de dialogue de téléchargement. En cliquant sur le bouton, le contrôleur génère l'URL dynamique et déclenche un événement $ scope où une personnalisation que directivej'ai écrite est listée. La directive ajoutera un iFrame au corps s'il n'existe pas déjà et y définit l'attribut url.

EDIT: Ajout d'une directive

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

Cette directive répond à un événement du contrôleur appelé downloadFile

donc dans votre contrôleur vous faites

$scope.$broadcast("downloadFile", url);
Ketan
la source
1
Un extrait de code serait d'une grande aide.
Sudhir N
La directive ci-dessus ne fonctionne pas pour moi, lorsque je mets la création d'iframe en dehors de la portée. $ On crée une iframe mais $ on event n'appelle pas lors de l'utilisation de la diffusion
Kanagu
Oui $ scope. $ Broadcast ne fonctionne que sur les enfants. Vous pouvez placer la directive sur la portée de niveau supérieur si possible.
Ketan
12

Vous pouvez définir location.hrefun URI de données contenant les données que vous souhaitez permettre à l'utilisateur de télécharger. En plus de cela, je ne pense pas qu'il y ait moyen de le faire avec juste JavaScript.

Jani Hartikainen
la source
Cela fonctionne très bien pour moi et était beaucoup plus propre que toutes les autres choses que nous essayions, ou à mon humble avis, les approches compliquées recommandées ci-dessus. Angular l'ignore entièrement. Ou, vous pouvez également utiliser window.open () / $ window.open () si vous voulez essayer d'ouvrir dans une autre fenêtre. Mais vous rencontrerez des bloqueurs de fenêtres contextuelles dans les navigateurs modernes ...
XML
1
Dans Angular 1.3, $location.hrefchangé en$window.location.href
igortg
C'est une excellente réponse. Le lien suivant sur SO fournit un exemple de travail complet: stackoverflow.com/a/30889331/1625820
herrtim
7

Je voudrais juste ajouter qu'au cas où il ne téléchargerait pas le fichier à cause de unsafe: blob: null ... lorsque vous survolez le bouton de téléchargement, vous devez le nettoyer. Par exemple,

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

app.config (fonction ($ compileProvider) {

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

Si vous avez accès à sur le serveur, envisagez de définir des en-têtes comme répondu à cette question plus générale .

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

En lisant les commentaires sur cette réponse, il est conseillé d'utiliser un Content-Type plus spécifique que l'octet-stream.

plong0
la source
4

J'ai eu le même problème et j'ai passé de nombreuses heures à trouver des solutions différentes, et maintenant je rejoins tous les commentaires de cet article, j'espère que cela vous sera utile, ma réponse a été correctement testée sur Internet Explorer 11, Chrome et 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>

DIRECTIVE:

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

        }
    }
})

DANS LE CONTRÔLEUR:

$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 SERVICE:

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 (au PRINTEMPS):

@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
la source
Merci, cela a vraiment fonctionné pour moi, d'autant plus que j'avais besoin de transferts de fichiers volumineux.
Marcos Paulo SUS
3

Cela a fonctionné pour moi en angulaire:

var a = document.createElement("a");
a.href = 'fileURL';
a.download = 'fileName';
a.click();
Zohab Ali
la source
Si vous avez une chaîne que vous souhaitez télécharger, changez simplement fileURL endata:text/plain;base64,${btoa(theStringGoesHere)}
Chicken Soup
2

Je ne voulais pas d'URL statique. J'ai AjaxFactory pour faire toutes les opérations ajax. J'obtiens l'URL de l'usine et je la lie comme suit.

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

Merci @AhlemMustapha

om471987
la source