How do you serve a file for download with AngularJS or Javascript?
Asked Answered
R

11

99

I have some text in a hidden textarea. When a button is clicked I would like to have the text offered for download as a .txt file. Is this possible using AngularJS or Javascript?

Rodger answered 13/5, 2013 at 3:39 Comment(2)
What browsers do you support? This can be solved in some creative ways (like data-uris, blobs, the browser's history API, etc) but that really depends.Porche
Angular File Saver is a good polyfill for less modern browsers.Carver
S
112

You can do something like this using Blob.

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

in your controller:

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

in order to enable the URL:

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

Please note that

Each time you call createObjectURL(), a new object URL is created, even if you've already created one for the same object. Each of these must be released by calling URL.revokeObjectURL() when you no longer need them. Browsers will release these automatically when the document is unloaded; however, for optimal performance and memory usage, if there are safe times when you can explicitly unload them, you should do so.

Source: MDN

Superincumbent answered 13/5, 2013 at 4:11 Comment(8)
Modern browsers & IE10+Castano
@thriqon wow firefox + chrome really showing the others up there!Vouchsafe
Great solution, but $scope.url didn't worked for me. I had to use window.location instead.Christo
I noticed that then anchor tag is prefixed with unsafe. In order to get around that you will need to add 'blob' to the white list in your app.js, using $compileProvider ` .config(['$compileProvider', function ($compileProvider) { $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|file|blob):/); } ` docs.angularjs.org/api/ng/provider/$compileProviderMalcom
The whitelist above is correct, but be careful if attempting to copy-and-paste; there are special bytes in the middle of "blob" (looks like a square on my screen), which prevents it from working properly. I tried to repeat without the special bytes, but stackoverflow appears to add them back.Dean
How does this behaves with large files? ... will overload the browser?Cudbear
The download attribute isn't supported in any IE or Safari versions though caniuse.com/#feat=downloadAppease
This open file in a new window instead of showing save file dialog.Souse
C
35

Just click the button to download using following code.

in html

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

In controller

$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();
		};
Counterglow answered 16/2, 2015 at 12:12 Comment(3)
@Counterglow worked for me as needed, but can you explain the code ?Renell
Like this solution! When getting the data from the server, e.g. using $http.get(...) make sure to set responseType:'arraybuffer' like explained here: #21628878Propagandism
Works in Chrome (Win), but Safari (Mac) just opens the blobbed file in the browser. (blob:https/...) Like that this solution lets me wait for my promises to be resolved though.Terminable
F
26

Try this

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

and visit this site it could be helpful for you :)

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

Fernando answered 8/3, 2014 at 12:19 Comment(3)
Beware of the download attribute that's still not supported by any IE nor Safari version. Check it out here : caniuse.com/#feat=downloadBookcase
When i use it with angular it take url to $urlRouterProvider and redirect to my default page, is there any solution to download a file instead of navigationLederer
I always find it patronising when someone posts a link like that.Caniff
B
24

This can be done in javascript without the need to open another browser window.

window.location.assign('url');

Replace 'url' with the link to your file. You can put this in a function and call it with ng-click if you need to trigger the download from a button.

Banded answered 28/6, 2014 at 9:50 Comment(2)
It replaces site with Pdf doc instead to show download dialog window.Souse
Thanks! Works like a charm.Cauline
P
15

In our current project at work we had a invisible iFrame and I had to feed the url for the file to the iFrame to get a download dialog box. On the button click, the controller generates the dynamic url and triggers a $scope event where a custom directive I wrote, is listing. The directive will append a iFrame to the body if it does not exist already and sets the url attribute on it.

EDIT: Adding a 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;
});

This directive responds to a controller event called downloadFile

so in your controller you do

$scope.$broadcast("downloadFile", url);
Prayer answered 13/5, 2013 at 4:55 Comment(3)
Code snippet would be a great help.Triatomic
The directive above is not working for me, when I put iframe creation outside scope.$on it creates iframe but $on event is not calling when using broadcastManicurist
Yes $scope.$broadcast works only on children. You can put the directive on the top level scope if possible.Prayer
V
12

You can set location.href to a data URI containing the data you want to let the user download. Besides this, I don't think there's any way to do it with just JavaScript.

Valerianaceous answered 13/5, 2013 at 3:57 Comment(3)
This works great for me, and was much cleaner than all the other things we were trying, or IMHO, the complicated approaches recommended above. Angular ignores it entirely. Or, you can also use window.open()/$window.open() if you want to try to open in another window. But you'll run into popup blockers in modern browsers...Vilmavim
In Angular 1.3, $location.href changed to $window.location.hrefBoult
This is a great answer. The following link on SO provides a full working example: https://mcmap.net/q/76698/-using-window-or-location-to-redirect-in-angularjsAssyrian
B
7

Would just like to add that in case it doesn't download the file because of unsafe:blob:null... when you hover over the download button, you have to sanitize it. For instance,

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

app.config(function($compileProvider){

$compileProvider.aHrefSanitizationWhitelist(/^\s*(|blob|):/);
Benitez answered 9/2, 2014 at 23:17 Comment(0)
W
5

If you have access to on the server, consider setting headers as answered in this more general question.

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

Reading the comments on that answer, it is advisable to use a more specific Content-Type than octet-stream.

Wiggler answered 5/8, 2016 at 15:45 Comment(0)
W
4

I had teh same problem and spend many hours find diferent solutions, and now I join all the comments in this post.I hope it, will be helpfull, my answer was correctly tested on Internet Explorer 11, Chrome and 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');
                    }
                }
            });

        }
    }
})

IN CONTROLLER:

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

IN 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(on SPRING):

@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;
}
Waler answered 20/5, 2016 at 23:40 Comment(1)
Thanks, this really worked for me, specially since i needed large file transfers.Legault
A
3

This worked for me in angular:

var a = document.createElement("a");
a.href = 'fileURL';
a.download = 'fileName';
a.click();
Ashkhabad answered 5/7, 2018 at 6:44 Comment(1)
If you have a string you want to download, just change fileURL to data:text/plain;base64,${btoa(theStringGoesHere)}Codon
E
2

I didnt want Static Url. I have AjaxFactory for doing all ajax operations. I am getting url from the factory and binding it as follows.

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

Thanks @AhlemMustapha

Enteric answered 16/6, 2014 at 20:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.