Search code examples
javascriptangularjsxmlhttprequestangularjs-scopeangular-services

Applying results from a factory service to a scope controller


I am writing an UploadService. The upload so far works fine. But I'd like to update the scope of the controller with the xhr callbacks, in order to display relevant information and UI.

How would I do that? I think the factory service is not the right place to clutter with controller specific stuff.

adminServices.factory('UploadService', [function() {
  return {
    beginUpload: function(files, options) {
        var xhr = new XMLHttpRequest();
        xhr.upload.addEventListener("progress", this.onUploadProgress, false);
        xhr.addEventListener("load", this.onUploadComplete, false);
        xhr.addEventListener("error", this.onUploadFailed, false);
        xhr.addEventListener("abort", this.onUploadCanceled, false);
    },
    onUploadProgress: function(progress) {
      console.log("upload progress"); //HERE I CAN'T UPDATE the CONTROLLER's SCOPE
    },
    onUploadComplete: function(result) {
      console.log("upload complete"); //NOR HERE
    },


app.directive('fileUpload', function() {
   return {
      restrict: 'E',
      scope: {},
      template: '', //omitted for brevity
      controller: function($scope, UploadService) {
         $scope.upload = function() {             
             UploadService.beginUpload($scope.files, options);
         };
      //HERE I'D LIKE TO HAVE NOTIFICATIONS OF THE onXYZ methods...

Solution

  • Try doing the following in the factory:

    adminServices.factory('UploadService', [function() {
      //Create a UploadService Class
    
      function UploadService (scope) { //Constructor. Receive scope.      
        //Set Class public properties
        this.scope = scope;
        this.xhr = new XMLHttpRequest();
        //Write any initialisation code here. But reserve event handlers for the class user.
      }
    
      //Write the beginUpload function
      UploadService.prototype.beginUpload = function (files, options) {
        //Upload code goes here. Use this.xhr
      }
    
      //Write the onUploadProgress event handler function
      UploadService.prototype.onUploadProgress = function (callback) {
        var self = this;
        this.xhr.upload.addEventListener("progress", function (event) {
           //Here you got the event object.
           self.scope.$apply(function(){
             callback(event);//Execute callback passing through the event object.
             //Since we want to update the controller, this must happen inside a scope.$apply function 
           });
        }, false);
      }
    
      //Write other event handlers in the same way
      //...
    
      return UploadService;
    }]);
    

    And now, you can use the UploadService factory inside the directive controller as follows:

    app.directive('fileUpload', function() {
      return {
        restrict: 'E',
        scope: {},
        template: '', //omitted for brevity
        controller: function($scope, UploadService) {
          //Create an UploadService object sending the current scope through the constructor.
          var uploadService = new UploadService($scope);
    
          //Add a progress event handler 
          uploadService.onUploadProgress(function(event){
            //Update scope here.
            if (event.lengthComputable) {
              $scope.uploadProgress = event.loaded / event.total;
            }
          });
    
          $scope.upload = function() {             
            uploadService.beginUpload($scope.files, options);
          };
    

    Hope it helps. Cheers :)