Building a FileReader Service For AngularJS: The Service

Wednesday, July 3, 2013

In the previous post we looked at promises in AngularJS both from both the creator and client perspective. Here is an Angular service that wraps the FileReader and transforms an evented API into a promise API. The onload event resolves a promise, and the onerror event rejects a promise. Notice the use of scope.$apply to propagate the promise results.

(function (module) {
    
    var fileReader = function ($q, $log) {

        var onLoad = function(reader, deferred, scope) {
            return function () {
                scope.$apply(function () {
                    deferred.resolve(reader.result);
                });
            };
        };

        var onError = function (reader, deferred, scope) {
            return function () {
                scope.$apply(function () {
                    deferred.reject(reader.result);
                });
            };
        };

        var onProgress = function(reader, scope) {
            return function (event) {
                scope.$broadcast("fileProgress",
                    {
                        total: event.total,
                        loaded: event.loaded
                    });
            };
        };

        var getReader = function(deferred, scope) {
            var reader = new FileReader();
            reader.onload = onLoad(reader, deferred, scope);
            reader.onerror = onError(reader, deferred, scope);
            reader.onprogress = onProgress(reader, scope);
            return reader;
        };

        var readAsDataURL = function (file, scope) {
            var deferred = $q.defer();
            
            var reader = getReader(deferred, scope);         
            reader.readAsDataURL(file);
            
            return deferred.promise;
        };

        return {
            readAsDataUrl: readAsDataURL  
        };
    };

    module.factory("fileReader",
                   ["$q", "$log", fileReader]);

}(angular.module("testApp")));

The service only exposes a single method (readAsDataUrl). The rest of the FileReader methods are omitted for brevity, but they follow the same pattern.

One interesting event is the onprogress event of a FileReader. Since there is nothing to do with the promise during a progress event, the event is instead transformed and forwarded using scope.$broadcast. Other interested parties can use $scope.$on to register event handlers for such broadcasts. For example, here is a controller  using the reader the reader service.

var UploadController = function ($scope, fileReader) {
    
    $scope.getFile = function () {
        $scope.progress = 0;
        fileReader.readAsDataUrl($scope.file, $scope)
                      .then(function(result) {
                          $scope.imageSrc = result;
                      });
    };

    $scope.$on("fileProgress", function(e, progress) {
        $scope.progress = progress.loaded / progress.total;
    });

};

The progress value is fed into a progress element:

<progress value="{{progress}}"></progress>

But how does a controller know what file to read?

That’s where we can build some file input and drag and drop directives...


Comments
gravatar Johan Wednesday, July 3, 2013
How would you handle multiple upload fields, say one for profile image and one for a smaller avatar, and still have a functional progress indicator for each field? Would it be a good idea to have a upload directive use the service instead with a two way bound attribute linked to the controller?
Comments are closed.

My Pluralsight Courses

K.Scott Allen OdeToCode by K. Scott Allen
What JavaScript Developers Should Know About ECMAScript 2015
The Podcast!