22

I have an API endpoint to which I must send a multipart HTTP request, composed of two parts, file (a file system file) and data (a JSON object).

After some research I found out how to do a multipart request in AngularJS:

$http({
   method: 'POST',
   url: url,
   headers: {
      'Content-Type': 'multipart/form-data'
   },
   data: {
      data: model,
      file: file
   },
   transformRequest: customFormDataObject
});

1) The customFormDataObject function initially had this form:

customFormDataObject formDataObject = function (data, headersGetter) {
   var fd = new FormData();
   angular.forEach(data, function (value, key) {
      fd.append(key, value);
   });

   var headers = headersGetter();
   delete headers['Content-Type'];

   return fd;
};

The outcome of this implementation is that the individual parts of the request do not have a contentType set to them.

2) After reading some more (https://stackoverflow.com/a/24535293/689216) I tried using a Blob for this, the customFormData object looking like this (a bit of a mess, basically the first part will be of contentType application/json, the second one image/png):

customFormDataObject = function (data, headersGetter) {
    var fd = new FormData();

    var contentType = 'application/json';
    angular.forEach(data, function (value, key) {
         fd.append(key, new Blob([JSON.stringify(value)], {
             type: contentType
         }));
         contentType = 'image/png';
    });

    var headers = headersGetter();
    delete headers['Content-Type'];

    return fd;
 };

This second approach sets the correct contentType for each part of the request, but it does not set any values for the parts.

Basically what happens is with 1) the correct values are set in the multiparts, but the contentType's are not set. With 2) the contentType's are set, but not the values for the multiparts.

Am I missing something? Is this functionality not supposed to work like this?

Thanks!

4
  • Have you tried setting content type on request instead of each form data element? Commented Mar 6, 2015 at 16:22
  • Yes, the content type for the request gets set automatically when doing delete headers['Content-Type']; Commented Mar 6, 2015 at 18:35
  • just for consideration: I recommend github.com/nervgh/angular-file-upload and I also uploaded blob: github.com/nervgh/angular-file-upload/issues/208 Commented Mar 11, 2015 at 20:18
  • 1
    file already is a Blob and JSON.stringify doesn't serialize the content of a file but the properties of file (if anything). Commented Mar 14, 2015 at 10:35

5 Answers 5

35

The easiest way to upload files in Angular:

var fd = new FormData();
fd.append('file', file);
fd.append('data', 'string');
$http.post(uploadUrl, fd, {
   transformRequest: angular.identity,
   headers: {'Content-Type': undefined}
})
.success(function(){
})
.error(function(){
});

Absolutely essential are the following two properties of the config object:

transformRequest: angular.identity

overrides Angular's default serialization, leaving our data intact.

headers: {'Content-Type': undefined }

lets the browser detect the correct Content-Type as multipart/form-data, and fill in the correct boundary.

Nothing else worked for me! Courtesy of Lady Louthan's wonderful blogpost.

Sign up to request clarification or add additional context in comments.

4 Comments

Great answer. Simple and effective solution.
This is the answer
Thanks! Sometimes, using XMLHttpRequest would help.
Perfect! tried many others, none works but only this!
4

Have you tried something like this:

$httpProvider.defaults.transformRequest = function(data) {
  if (data === undefined){
    return data;
  }

  var formData = new FormData();
  for (var key in data){
    if(typeof data[key] == 'object' && !(data[key] instanceof File)){
      formData.append(key, JSON.stringify(data[key]));
    }else{
      formData.append(key, data[key]);
    }
  }
  return formData;
};

Comments

4

I just solve exactly the same problem.

After some tests, I had this situation that you've described:

Basically what happens is with 1) the correct values are set in the multiparts, but the contentType's are not set. With 2) the contentType's are set, but not the values for the multiparts.

To fix this problem, I had to use Blob and Post Ajax instead of $http Post.

It seems that $http does not work correctly in this case.

Code:

    var formData = new FormData();

    var blobId = new Blob([100], { 'type':'text/plain' });
    formData.append('testId', blobId);

    var blobImage = fileService.base64ToBlob(contentToDecode, 'image/jpeg');
    formData.append('file', blobImage, 'imagem' + (i + 1) + '.jpg');

Request:

    $.ajax({
      url: url,
      data: formData,
      cache: false,
      contentType: false,
      processData: false,
      type: 'POST',
      success: function(response) {
        deferred.resolve(response);
        $rootScope.requestInProgress = false;
      },
      error: function(error) {
        deferred.reject(error);
        $rootScope.requestInProgress = false;
      }
    });

2 Comments

Please post a bit of code to help the user go in the right direction.
Yes, some code would help me a lot. I have completely postponed this functionality, but I would like to get it rolling at some point.
2

You can use https://github.com/danialfarid/ng-file-upload/.

In this file uploader, there is a provision for sending the file and data (in JSON format) separately as you mentioned in your question above.

For Ex:-

var upload = $upload.upload({
                 url: url,
                 file: file,
                 method: 'POST' or 'PUT', default POST,
                 headers: {'Content-Type': 'multipart/form-data'}, // only for html5
                 fileName: 'doc.jpg',
                 fileFormDataName: 'myFile',
                 data: {'data': model}
             });

In the above code, you can send either a POST or PUT request with 'multipart/form-data', file and data object as JSON separately.

For more information you can visit the above link and look at the ReadMe.md of the plug-in.

I know that my approach is a bit different from the one that you are currently following, but the objective is same.

Comments

0

what i did to solve this was.

var formData = new FormData(document.getElementById('myFormId'));

then in my service

                var deferred = $q.defer();
                $http.post('myurl', formData, {
                    cache: false,
                    contentType: false,
                    processData: false,
                })
                    .success(function (response) {
                        deferred.resolve(response);
                    })
                    .error(function (reject) {
                        deferred.reject(reject);
                    });
                return deferred.promise;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.