Angularjs upload multiple files using $http service and custom directive.

Problem: For Data binding of input type as file we need some extra effort since auto data binding for input type file doesn’t work as expected.

Let’s go through the solution for handling multiple files in angularjs and upload it to grails end point.

<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

</head>
<body ng-app="demoApp" ng-controller="mainCtrl as vm">
<div class="container">
  <div class="row">
    <div class="col-md-12">
      <blockquote>
        Angularjs multple file upload 
      </blockquote>
    </div>
    <div class="row">
     <form name="fileform">
      <div class="form-group">
          <label for="fileinput" class="col-sm-3 control-label">Files</label><br>
            <div class="col-sm-12">
              <input type="file"  id="fileinput" name="logos" file-model="vm.logos" multiple="multiple" class="form-control"  placeholder="Files" />
             </div>
      </div>
      <div class="form-group">
        <div class="col-sm-12">
           <button type="submit" ng-click="vm.create()" class="btn btn-default">Create</button>
        </div>
      </div>
     </form>
    </div>
  </div>
</div>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<script>
(function(){

  angular.module("demoApp",[])
  .controller('mainCtrl',function($http){
     var vm = this;
     vm.create = function(){
     var fd = new FormData();
	 angular.forEach(vm.logos, function (val, key) {
		fd.append('file'+key, val);						
	  });
	  $http.post('http://localhost:8080/' + 'uploads', fd, {
		transformRequest: angular.identity
		, headers: {
	     	'Content-Type': undefined
		}
	})
	   .success(function(serviceResponse) {
          console.log(serviceResponse);
       })
	  };
  })
  .directive('fileModel', ['$parse', function ($parse) {
	return {
	    restrict: 'A'
		, link: function (scope, element, attrs) {
		var model = $parse(attrs.fileModel);
		var modelSetter = model.assign;
		element.bind('change', function () {
			var files = [];
	     	angular.forEach(element[0].files,function(file){
               files.push(file);
			})
			scope.$apply(function () {
		     	modelSetter(scope, files);
			 });
			});
		};
	};
  }]);
})();
</script>
</body>
</html>

Lets go through the above code step by step.

From line 21 to 24, input type of file is defined with angularjs directive named as file-model” and its implementation is under the script block with name fileModel.

Inside the directive implementation we listen on change event and loop through the files “element[0].files” and push it to the files array.

For inspecting the files, open the chrome debugger tool using CTRL + Shift + J and type “document.getElementsByName('logos')[0].files” inside the console tab after selecting the files.

From line 45 to 49 we make use of FormData to send file as a paramter via http service, also we change the header definition in http service for sending file data.

“http://localhost:8080/uploads” is the end point defined for server side file handling in grails application.

Define a controller and action inside grails application then map it to a url in urlmapping file.

Let’s look into the server side code for handling the form data.

 def uploadFiles(){
      params.each{item->
         if(item.getKey().startsWith("file")){
            def fileName = item.getValue().originalFilename
            def ext = fileName.split('\\.')[1]
            item.getValue().transferTo(new File('D:/myimages/'+fileName+'.'+ext))
           }
       }
      render "success"
  }