AngularJS File Upload / Download

2 June 2016By Rich @Black Sand Solutions
  • AngularJS

A collection of directives and utility methods for working with files in Angular.

Angular File Upload / Download

OnFileChange

It is not possible to bind to input File type in angular so this is not possible: onchange="onFileChange(this)
https://github.com/angular/angular.js/issues/1375
This directive resolves this:

 angular.module("directives")
        .directive("OnFileChange", [OnFileChange]);

    function OnFileChange() {
        var directive = {
            restrict: "A",
            link: function (scope, element, attrs) {
                var onChangeHandler = scope.$eval(attrs.OnFileChange);
                element.bind('change', onChangeHandler);

                element.bind('click', function(){
                    //clear the value so that we can select a file with the same name
                    element[0].value = '';
                });

                scope.$on('$destroy', function () {
                    element.unbind('change', onChangeHandler);
                });
            }
        }

        return directive;
    }

ngSrcAuth

This directive allows the img tag to be used to request a file from an API endpoint that requires authentication - in this case bearer. Note that this directive requires the API endpoint to be passed in via the APP_CONFIG constant. It also expects the logged in users token to be accessible via the sessionService. Alternatively, both could be passed in scope attributes.

Usage: <img class="thumbnail" ng-src-auth="{{item.apiEndPointPath}}"/>

angular.module('directives')
        .directive('ngSrcAuth', ['$http', 'APP_CONFIG', 'sessionService', function ($http, APP_CONFIG, sessionService) {
            return {
                restrict: 'A',
                link: function (scope, element, attrs) {
                    //request file data from server, attaching auth header and token
                    var requestConfig = {
                        method: 'Get',                        
                        headers: { 'Authorization': 'Basic ' + sessionService.getToken() },
                        url: attrs.ngSrcAuth,
                        cache: 'true'
                    };

                    $http(requestConfig)
                        .success(function (file) {

                            //append to src attribute on associated img tag

                            if (file.isPlaceholder)
                            {
                                attrs.$set('src', APP_CONFIG.cdnUrl + '/' + file.filepath);
                                attrs.$set('title', file.filename);
                                attrs.$set('download-link', file.downloadUrl);
                            }
                            else
                            {
                                attrs.$set('src', "data:image/jpeg;base64," + file.data);
                                attrs.$set('title', file.filename);
                                attrs.$set('download-link', file.downloadUrl);
                            }

                        });
                }
            };
        }]);

ngDownloadAuth

Similar to the above this creates a link to a file which will download it from an API endpoint that required authentication. The directive expects sessionService to return the token for the logged in user.
Supports IE, Firefox and Chrome.

Usage: <a download-link="{{::file.downloadLink}}" target="_blank"> {{::file.filename}}</a>

angular.module('directives')
        .directive('downloadLink', ['$http', '$timeout', '$window', 'sessionService', function ($http, $timeout, $window, sessionService) {
            return {
                restrict: 'A',
                link: function (scope, element, attrs) {
                  
                    attrs.$set('title', 'download');
                    attrs.$set('style', 'cursor:pointer');

                    // handle the user clicking the element
                    element.bind("click", function (e) {
                        getFile();
                    });

                    function getFile() {
                        //request file data from server, attaching auth header and token
                        var requestConfig = {
                            method: 'Get',
                            headers: { 'Authorization': 'Basic ' + sessionService.getToken() },
                            url: attrs.downloadLink,
                            cache: 'true'
                        };

                        $http(requestConfig)
                            .success(function(file) {
                                //send the file to the browser
                                download(file.data, file.filename, file.mimeType);
                            });
                    }

                    function download(content, filename, contentType) {
                        var isChrome = !!window.chrome &amp;&amp; !!window.chrome.webstore;
                        if (isChrome)
                        {
                            //create a new a tag and attach the file data to it and trigger the download
                            //TODO: I am not happy with using the global document, but $document does not have a createElement method and not had time to create a better solution
                            var link = angular.element(document.createElement('a'));

                            link.attr('href', 'data:' + contentType + ';base64,' + content);
                            link.attr('download', filename);

                            $timeout(function () {
                                link[0].click();
                            });
                        }
                        else
                        {
                            //download is not supported, open file in new window
                            var blob = b64toBlob(content, contentType);

                            //IE
                            if (window.navigator.msSaveOrOpenBlob) {
                               
                                window.navigator.msSaveOrOpenBlob(blob, filename);
                            }
                            else
                            {
                                //everything else
                                var url = window.URL || window.webkitURL;
                                var fileUrl = url.createObjectURL(blob);
                                $window.open(fileUrl, filename);
                                url.revokeObjectURL(fileUrl);
                            }
                        }
                    }

                    function b64toBlob(b64Data, contentType) {
                        //http://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript
                        contentType = contentType || '';
                        var sliceSize = 512;

                        var byteCharacters = atob(b64Data);
                        var byteArrays = [];

                        for (var offset = 0; offset &lt; byteCharacters.length; offset += sliceSize) {
                            var slice = byteCharacters.slice(offset, offset + sliceSize);

                            var byteNumbers = new Array(slice.length);
                            for (var i = 0; i &lt; slice.length; i++) {
                                byteNumbers[i] = slice.charCodeAt(i);
                            }

                            var byteArray = new Uint8Array(byteNumbers);

                            byteArrays.push(byteArray);
                        }

                        var blob = new Blob(byteArrays, { type: contentType });
                        return blob;
                    }

                }
            };
        }]);

Utility Methods

Some useful utility methods for working with files, primarily images:

GetFileObject

        function getFileObject(file) {
            ///
            /// Convert the File object to an image object,
            /// complete with size, height and width
            ///
            var deferredObject = $q.defer();

            var reader = new FileReader();

            //http://stackoverflow.com/questions/12570834/how-to-preview-image-get-file-size-image-height-and-width-before-upload
            reader.onloadend = function (_file) {
                // This method is called by reader.readAsDataURL when it completes (pass or fail)
                var result = {};
                var image = new Image();
                image.src = _file.target.result;
                image.onload = function () {
                    var w = this.width,
                        h = this.height,
                        s = ~~(file.size / 1024) + 'KB';

                    result.dataUri = _file.target.result;
                    result.width = w;
                    result.height = h;
                    result.size = s;
                    result.name = file.name;

                    return deferredObject.resolve(result);
                };
                image.onerror = function () {
                    //do nothing
                };
            }

            reader.readAsDataURL(file);

            return deferredObject.promise;
        }

CreateBlob

The BlobBuilder API has been deprecated in favour of Blob, but older browsers don't know about the Blob constructor

       function createBlob(data, datatype) {
            var out;

            try {
                out = new Blob([data], { type: datatype });
            }
            catch (e) {
                window.BlobBuilder = window.BlobBuilder ||
                        window.WebKitBlobBuilder ||
                        window.MozBlobBuilder ||
                        window.MSBlobBuilder;

                if (e.name == 'TypeError' &amp;&amp; window.BlobBuilder) {
                    var bb = new BlobBuilder();
                    bb.append(data);
                    out = bb.getBlob(datatype);
                }
                else if (e.name == "InvalidStateError") {
                    out = new Blob([data], { type: datatype });
                }
                else {
                    // unknown error  
                }
            }
            return out;
        }

dataUrItoBlob

Convert base64/URLEncoded data component to raw binary data held in a string. Uses createBlob method above.

        function dataUrItoBlob(dataUri) {
            // c
            var byteString;
            if (dataUri.split(',')[0].indexOf('base64') &gt;= 0)
                byteString = atob(dataUri.split(',')[1]);
            else
                byteString = unescape(dataUri.split(',')[1]);

            // separate out the mime component
            var mimeString = dataUri.split(',')[0].split(':')[1].split(';')[0];

            // write the bytes of the string to a typed array
            var ia = new Uint8Array(byteString.length);
            for (var i = 0; i &lt; byteString.length; i++) {
                ia[i] = byteString.charCodeAt(i);
            }

            return new createBlob(ia, mimeString);
        }
All Posts