OdeToCode IC Logo

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...