25

How can I get a 'progress' event from my AngularJS $http POST request that is uploading an image? Is it possible to do this client-side, or do I need the server to report the progress as it receives the data?

winduptoy
  • 5,366
  • 11
  • 49
  • 67
  • Do you need something accurate like "10% complete", then "25% complete", etc. or just some kind of animated graphic to indicate that work is going on? – Mark Rajcok Jan 12 '13 at 02:52

6 Answers6

21

Using pure angular:

function upload(data) {
    var formData = new FormData();
    Object.keys(data).forEach(function(key){formData.append(key, data[key]);});
    var defer = $q.defer();
    $http({
        method: 'POST',
        data: formData,
        url: <url>,
        headers: {'Content-Type': undefined},
        uploadEventHandlers: { progress: function(e) {
            defer.notify(e.loaded * 100 / e.total);
        }}
    }).then(defer.resolve.bind(defer), defer.reject.bind(defer));
    return defer.promise;
}

and somewhere else ...

// file is a JS File object
upload({avatar:file}).then(function(responce){
    console.log('success :) ', response);
}, function(){
    console.log('failed :(');
}, function(progress){
    console.log('uploading: ' + Math.floor(progress) + '%');
});
Sajjad Shirazi
  • 2,657
  • 26
  • 24
17

You can also use the simple/lightweight angular-file-upload directive that takes care of these stuff. It supports drag&drop, file progress/abort and file upload for non-HTML5 browsers with FileAPI flash shim

<div ng-controller="MyCtrl">
  <input type="file" ng-file-select="onFileSelect($files)" multiple>
</div>

JS:

//inject angular file upload directive.
angular.module('myApp', ['angularFileUpload']);

var MyCtrl = [ '$scope', '$upload', function($scope, $upload) {
  $scope.onFileSelect = function($files) {
    //$files: an array of files selected, each file has name, size, and type.
    for (var i = 0; i < $files.length; i++) {
      var $file = $files[i];
      $upload.upload({
        url: 'my/upload/url',
        file: $file,
        progress: function(e){}
      }).then(function(data, status, headers, config) {
        // file is uploaded successfully
        console.log(data);
      }); 
    }
  }
}];
danial
  • 4,058
  • 2
  • 32
  • 39
  • 1
    No. In tried solution in .net. I only get 100% for every file. There is no progress (11%, 25%) @danial do you have example in .net where progress work. Do I have to change spmething on server side? Progress callbacl only report 100% – Vlado Pandžić Mar 30 '15 at 19:01
  • Check out the wiki page https://github.com/danialfarid/ng-file-upload#net and post your question on the github issues. You might need to change some server settings and proxies to enable progress being reported. – danial Mar 31 '15 at 03:43
  • In other project (also .NET) my collegaue used https://github.com/blueimp/jQuery-File-Upload, and file progress worked. – Vlado Pandžić Mar 31 '15 at 06:35
  • Or may be my upload speed is too fast to actually see progress – Vlado Pandžić Mar 31 '15 at 07:45
  • @Vlado , I've used `scope.$apply(...)` on `onprogress` to show the range of percentage. – Paridokht Jun 26 '16 at 07:43
15

I don't think $http.post() can be used for this.

As for client-side, it should work with an HTML5 browser, but you'll probably have to create your own XMLHttpRequest object and onprogress listener. See AngularJS: tracking status of each file being uploaded simultaneously for ideas.

Community
  • 1
  • 1
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • 1
    I confirm that you still need to do it yourself. https://github.com/angular/angular.js/issues/14436 – Jay Feb 09 '17 at 15:51
3

I don't think Angular has something built-in to handle uploads.

I think your best bet is to use something like jQuery File Upload. An idea for a solution would to create a Service that returns {progress:0} as default and then inside itself, implements the jQuery File Upload's progress update callback, which then simply keeps updating the progress. Thanks to Angular's binding, the upload progress would be in sync.

angular.module('myApp.services', [])
  .factory('Uploader', function() {
  var uploaderService = {};

  var status = { progress: 0 };

  uploaderService.upload = function(inputEl) {
    inputEl.fileupload({
      /* ... */
      progressall: function (e, data) {
        status.progress = parseInt(data.loaded / data.total * 100, 10);
      }
    });
  };

  return uploaderService;
});
randomguy
  • 12,042
  • 16
  • 71
  • 101
2

Here is another solution:

window.XMLHttpRequest = (function (orig) {
    if (orig) {
        var intercept = [],
            result = function () {
            var r = new orig();

            if (r.upload) {
                $(r).on(
                    'abort error load loadend loadstart progress',
                    function (e) {
                        $(document).trigger('upload.XHR', e);
                    }
                );
            }

            if (intercept.length) {
                intercept[0].push({
                    request:r
                });
            }

            return r;
        };

        result.grab = function (f) {
            intercept.unshift([]);
            f();
            return intercept.shift();
        };

        return result;
    }

    return function () {
        try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
        try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
        try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
        throw new Error("This browser does not support XMLHttpRequest.");
    };
}(window.XMLHttpRequest));

Notes:

  • AngularJS currently stores a reference to window.XMLHttpRequest as private XHR variable, then uses it like this: new XHR(). I doubt this will ever change, so the shim-like code above should work just fine.

  • Mozilla has some extensions: XMLHttpRequest accepts optional arguments. The code above does not handle this, but AngularJS does not use these extensions anyway.

  • One of possible uses (if you want to show all current requests, and maybe implement some "Cancel" button):

$(document).on('upload.XHR', function (_e, e) {
   switch (e.type) {
       // do your thing here
   }
});
  • Another possible use:
var list = window.XMLHttpRequest.grab(function () {
    // start one or more $http requests here, or put some code
    // here that indirectly (but synchronously) starts requests
    $http.get(...);
    couchDoc.save();
    couchDoc.attach(blob, 'filename.ext');
    // etc
});

list[0].request.upload.addEventListener(...);
  • Or, you can combine both approaches with some modifications to the code above.
alx
  • 2,314
  • 2
  • 18
  • 22
0

you can use this where Im using simple angular function to upload file and $scope.progressBar variable to check the progress of uploading...

$scope.functionName = function(files) {
   var file = files[0];
   $scope.upload = $upload.upload({
   url: 'url',
   method: 'POST', 
   withCredentials: true, 
   data: {type:'uploadzip'},
   file: file, // or list of files ($files) for html5 only 
 }).progress(function(evt) {
   console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
   $scope.progressBar = parseInt(100.0 * evt.loaded / evt.total);
 }).success(function(data, status, headers, config) {
   console.log('upload succesfully...')
 }).error(function(err) {
   console.log(err.stack);
 }) 
}
Himanshu Teotia
  • 2,126
  • 1
  • 27
  • 38