30

I have a form with two input text and one upload. I have to send it to the server but I have some problem concatenating the file with the text. The server expects this answer:

"title=first_input" "text=second_input" "file=my_file.pdf"

This is the html:

<input type="text" ng-model="title">
<input type="text" ng-model="text">
<input type="file" file-model="myFile"/>
<button ng-click="send()">

This is the Controller:

$scope.title = null;
$scope.text = null;

$scope.send = function(){
  var file = $scope.myFile;
  var uploadUrl = 'my_url';
  blockUI.start();
  Add.uploadFileToUrl(file, $scope.newPost.title, $scope.newPost.text, uploadUrl);
};

This is the Directive fileModel:

  return {
restrict: 'A',
link: function(scope, element, attrs) {
  var model = $parse(attrs.fileModel);
  var modelSetter = model.assign;

  element.bind('change', function(){
    scope.$apply(function(){
      modelSetter(scope, element[0].files[0]);
    });
  });
}
};

And this is the Service which call the server:

  this.uploadFileToUrl = function(file, title, text, uploadUrl){
   var fd = new FormData();
   fd.append('file', file);
   var obj = {
     title: title,
     text: text,
     file: fd
   };
   var newObj = JSON.stringify(obj);

     $http.post(uploadUrl, newObj, {
       transformRequest: angular.identity,
       headers: {'Content-Type': 'multipart/form-data'}
     })
  .success(function(){
    blockUI.stop();
  })
  .error(function(error){
    toaster.pop('error', 'Errore', error);
  });
}

If I try to send, I get Error 400, and the response is: Multipart form parse error - Invalid boundary in multipart: None. The Payload of Request is: {"title":"sadf","text":"sdfsadf","file":{}}

Madis Otenurm
  • 61
  • 1
  • 7
panagulis72
  • 2,129
  • 6
  • 31
  • 71

6 Answers6

64

Don't serialize FormData with POSTing to server. Do this:

this.uploadFileToUrl = function(file, title, text, uploadUrl){
    var payload = new FormData();

    payload.append("title", title);
    payload.append('text', text);
    payload.append('file', file);

    return $http({
        url: uploadUrl,
        method: 'POST',
        data: payload,
        //assign content-type as undefined, the browser
        //will assign the correct boundary for us
        headers: { 'Content-Type': undefined},
        //prevents serializing payload.  don't do it.
        transformRequest: angular.identity
    });
}

Then use it:

MyService.uploadFileToUrl(file, title, text, uploadUrl).then(successCallback).catch(errorCallback);
biology.info
  • 3,500
  • 2
  • 28
  • 39
Kyle
  • 5,407
  • 6
  • 32
  • 47
  • 8
    The problem is on your back end controller. Not Javascript. – Kyle May 05 '16 at 00:16
  • I don't manager the backend side, I will ask to my friend who does it. P.s. you wrote fd.append but I suppose they all are payload.append, right? – panagulis72 May 05 '16 at 00:25
  • Yes. My bad. Payload is fd – Kyle May 05 '16 at 00:30
  • Maybe there is an error because the file is undefined? Look the Request Payload: -----------------------------185657102119953796721894889690 Content-Disposition: form-data; name="title" tgrfdc -----------------------------185657102119953796721894889690 Content-Disposition: form-data; name="text" tgfrds -----------------------------185657102119953796721894889690 Content-Disposition: form-data; name="file" undefined -----------------------------185657102119953796721894889690-- – panagulis72 May 05 '16 at 00:35
  • It was the problem, the server ask for the file and it could not be undefined... but I solved, because I didn't load the file containing the directive for the uploading.....LOL thank you very very much, I used your solution. Thanks again – panagulis72 May 05 '16 at 00:47
  • There is a problem which is still remains unanswered, when sending data using this method, the content type for other form data(other than file content) is missing which is an issue when we are sending any multibyte character string as form data... – r_goyal Sep 27 '17 at 14:31
  • Works nicely, but I observe it escapes " to %22 in the name. Something like Content-Disposition: form-data; name="it's a%22 field" test-data. Any idea how to keep it " only? – Gautam Kumar Samal Aug 21 '18 at 09:08
6

Here is the complete solution

html code,

create the text anf file upload fields as shown below

    <div class="form-group">
        <div>
            <label for="usr">User Name:</label>
            <input type="text" id="usr" ng-model="model.username">
        </div>
        <div>
            <label for="pwd">Password:</label>
            <input type="password" id="pwd" ng-model="model.password">
        </div><hr>
        <div>
            <div class="col-lg-6">
                <input type="file" file-model="model.somefile"/>
            </div>


        </div>
        <div>
            <label for="dob">Dob:</label>
            <input type="date" id="dob" ng-model="model.dob">
        </div>
        <div>
            <label for="email">Email:</label>
            <input type="email"id="email" ng-model="model.email">
        </div>


        <button type="submit" ng-click="saveData(model)" >Submit</button>

directive code

create a filemodel directive to parse file

.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(){
            scope.$apply(function(){
                modelSetter(scope, element[0].files[0]);
            });
        });
    }
};}]);

Service code

append the file and fields to form data and do $http.post as shown below remember to keep 'Content-Type': undefined

 .service('fileUploadService', ['$http', function ($http) {
    this.uploadFileToUrl = function(file, username, password, dob, email, uploadUrl){
        var myFormData = new FormData();

        myFormData.append('file', file);
        myFormData.append('username', username);
        myFormData.append('password', password);
        myFormData.append('dob', dob);
        myFormData.append('email', email);


        $http.post(uploadUrl, myFormData, {
            transformRequest: angular.identity,
            headers: {'Content-Type': undefined}
        })
            .success(function(){

            })
            .error(function(){
            });
    }
}]);

In controller

Now in controller call the service by sending required data to be appended in parameters,

$scope.saveData  = function(model){
    var file = model.myFile;
    var uploadUrl = "/api/createUsers";
    fileUpload.uploadFileToUrl(file, model.username, model.password, model.dob, model.email, uploadUrl);
};
JpG
  • 774
  • 8
  • 22
2

You're sending JSON-formatted data to a server which isn't expecting that format. You already provided the format that the server needs, so you'll need to format it yourself which is pretty simple.

var data = '"title='+title+'" "text='+text+'" "file='+file+'"';
$http.post(uploadUrl, data)
Dr. Cool
  • 3,713
  • 3
  • 22
  • 26
  • Since In my service I've wrote: var data = '"title=' + title +'" "text=' + text + '" "file=' + file +'"'; $http.post(uploadUrl, data, { transformRequest: angular.identity, headers: {'Content-Type': 'multipart/form-data'} }) and the response is: "Multipart form parse error - Invalid boundary in multipart: None".. in fact if I see the payload request, it is: "title=Aasd" "text=njkopè" "file=undefined" ...why? – panagulis72 May 04 '16 at 23:51
  • I changed the code slightly. Now it uses the parameters being passed into the uploadFileToUrl function which will include the "file" parameter. Try that. – Dr. Cool May 04 '16 at 23:55
  • Always undefined... :// – panagulis72 May 05 '16 at 00:06
2

This never gonna work, you can't stringify your FormData object.

You should do this:

this.uploadFileToUrl = function(file, title, text, uploadUrl){
   var fd = new FormData();
   fd.append('title', title);
   fd.append('text', text);
   fd.append('file', file);

     $http.post(uploadUrl, obj, {
       transformRequest: angular.identity,
       headers: {'Content-Type': undefined}
     })
  .success(function(){
    blockUI.stop();
  })
  .error(function(error){
    toaster.pop('error', 'Errore', error);
  });
}
scyrizales
  • 134
  • 6
1

Using $resource in AngularJS you can do:

task.service.js

$ngTask.factory("$taskService", [
    "$resource",
    function ($resource) {
        var taskModelUrl = 'api/task/';
        return {
            rest: {
                taskUpload: $resource(taskModelUrl, {
                    id: '@id'
                }, {
                    save: {
                        method: "POST",
                        isArray: false,
                        headers: {"Content-Type": undefined},
                        transformRequest: angular.identity
                    }
                })
            }
        };
    }
]);

And then use it in a module:

task.module.js

$ngModelTask.controller("taskController", [
    "$scope",
    "$taskService",
    function (
        $scope,
        $taskService,
    ) {
    $scope.saveTask = function (name, file) {
        var newTask,
            payload = new FormData();
        payload.append("name", name);
        payload.append("file", file);
        newTask = $taskService.rest.taskUpload.save(payload);
        // check if exists
    }
}
regiea
  • 121
  • 1
  • 5
  • 18
0

Assume that we want to get a list of certain images from a PHP server using the POST method.

You have to provide two parameters in the form for the POST method. Here is how you are going to do.

app.controller('gallery-item', function ($scope, $http) {

    var url = 'service.php'; 

    var data = new FormData();

    data.append("function", 'getImageList');
    data.append('dir', 'all');

    $http.post(url, data, {
        transformRequest: angular.identity,
        headers: {'Content-Type': undefined}
      }).then(function (response) {

          // This function handles success
        console.log('angular:', response);

    }, function (response) {

        // this function handles error

    });
});

I have tested it on my system and it works.

knoxgon
  • 1,070
  • 2
  • 15
  • 31
Shubham
  • 167
  • 2
  • 9