AngularJS: how to implement a simple file upload with multipart form?

The question:

I want to do a simple multipart form post from AngularJS to a node.js server,
the form should contain a JSON object in one part and an image in the other part,
(I’m currently posting only the JSON object with $resource)

I figured I should start with input type=”file”, but then found out that AngularJS can’t bind to that..

all the examples I can find are for wraping jQuery plugins for drag & drop. I want a simple upload of one file.

I’m new to AngularJS and don’t feel comfortable at all with writing my own directives.

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

A real working solution with no other dependencies than angularjs (tested with v.1.0.6)

html

<input type="file" name="file" onchange="angular.element(this).scope().uploadFile(this.files)"/>

Angularjs (1.0.6) not support ng-model on “input-file” tags so you have to do it in a “native-way” that pass the all (eventually) selected files from the user.

controller

$scope.uploadFile = function(files) {
    var fd = new FormData();
    //Take the first selected file
    fd.append("file", files[0]);

    $http.post(uploadUrl, fd, {
        withCredentials: true,
        headers: {'Content-Type': undefined },
        transformRequest: angular.identity
    }).success( ...all right!... ).error( ..damn!... );

};

The cool part is the undefined content-type and the transformRequest: angular.identity that give at the $http the ability to choose the right “content-type” and manage the boundary needed when handling multipart data.

Method 2

You can use the simple/lightweight ng-file-upload directive.
It supports drag&drop, file progress and file upload for non-HTML5 browsers with FileAPI flash shim

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

JS:

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

var MyCtrl = [ '$scope', 'Upload', function($scope, Upload) {
  $scope.onFileSelect = function($files) {
  Upload.upload({
    url: 'my/upload/url',
    file: $files,            
  }).progress(function(e) {
  }).then(function(data, status, headers, config) {
    // file is uploaded successfully
    console.log(data);
  }); 

}];

Method 3

It is more efficient to send a file directly.

The base64 encoding of Content-Type: multipart/form-data adds an extra 33% overhead. If the server supports it, it is more efficient to send the files directly:

$scope.upload = function(url, file) {
    var config = { headers: { 'Content-Type': undefined },
                   transformResponse: angular.identity
                 };
    return $http.post(url, file, config);
};

When sending a POST with a File object, it is important to set 'Content-Type': undefined. The XHR send method will then detect the File object and automatically set the content type.

To send multiple files, see Doing Multiple $http.post Requests Directly from a FileList


I figured I should start with input type=”file”, but then found out that AngularJS can’t bind to that..

The <input type=file> element does not by default work with the ng-model directive. It needs a custom directive:

Working Demo of “select-ng-files” Directive that Works with ng-model1

angular.module("app",[]);

angular.module("app").directive("selectNgFiles", function() {
  return {
    require: "ngModel",
    link: function postLink(scope,elem,attrs,ngModel) {
      elem.on("change", function(e) {
        var files = elem[0].files;
        ngModel.$setViewValue(files);
      })
    }
  }
});
<script src="//unpkg.com/angular/angular.js"></script>
  <body ng-app="app">
    <h1>AngularJS Input `type=file` Demo</h1>
    
    <input type="file" select-ng-files ng-model="fileArray" multiple>
    
    <h2>Files</h2>
    <div ng-repeat="file in fileArray">
      {{file.name}}
    </div>
  </body>

$http.post with content type multipart/form-data

If one must send multipart/form-data:

<form role="form" enctype="multipart/form-data" name="myForm">
    <input type="text"  ng-model="fdata.UserName">
    <input type="text"  ng-model="fdata.FirstName">
    <input type="file"  select-ng-files ng-model="filesArray" multiple>
    <button type="submit" ng-click="upload()">save</button>
</form>
$scope.upload = function() {
    var fd = new FormData();
    fd.append("data", angular.toJson($scope.fdata));
    for (i=0; i<$scope.filesArray.length; i++) {
        fd.append("file"+i, $scope.filesArray[i]);
    };

    var config = { headers: {'Content-Type': undefined},
                   transformRequest: angular.identity
                 }
    return $http.post(url, fd, config);
};

When sending a POST with the FormData API, it is important to set 'Content-Type': undefined. The XHR send method will then detect the FormData object and automatically set the content type header to multipart/form-data with the proper boundary.

Method 4

I just had this issue. So there are a few approaches. The first is that new browsers support the

var formData = new FormData();

Follow this link to a blog with info about how support is limited to modern browsers but otherwise it totally solves this issue.

Otherwise you can post the form to an iframe using the target attribute.
When you post the form be sure to set the target to an iframe with its display property set to none.
The target is the name of the iframe. (Just so you know.)

I hope this helps

Method 5

I know this is a late entry but I have created a simple upload directive. Which you can get working in no time!

<input type="file" multiple ng-simple-upload web-api-url="/api/post"
       callback-fn="myCallback" />

ng-simple-upload more on Github with an example using Web API.

Method 6

You could upload via $resource by assigning data to params attribute of resource actions like so:

$scope.uploadFile = function(files) {
    var fdata = new FormData();
    fdata.append("file", files[0]);

    $resource('api/post/:id', { id: "@id" }, {
        postWithFile: {
            method: "POST",
            data: fdata,
            transformRequest: angular.identity,
            headers: { 'Content-Type': undefined }
        }
    }).postWithFile(fdata).$promise.then(function(response){
         //successful 
    },function(error){
        //error
    });
};

Method 7

I just wrote a simple directive (from existing one ofcourse) for a simple uploader in AngularJs.

(The exact jQuery uploader plugin is https://github.com/blueimp/jQuery-File-Upload)

A Simple Uploader using AngularJs (with CORS Implementation)

(Though the server side is for PHP, you can simple change it node also)


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Comment