536

In the code below, the AngularJS $http method calls the URL, and submits the xsrf object as a "Request Payload" (as described in the Chrome debugger network tab). The jQuery $.ajax method does the same call, but submits xsrf as "Form Data".

How can I make AngularJS submit xsrf as form data instead of a request payload?

var url = 'http://somewhere.com/';
var xsrf = {fkey: 'xsrf key'};

$http({
    method: 'POST',
    url: url,
    data: xsrf
}).success(function () {});

$.ajax({
    type: 'POST',
    url: url,
    data: xsrf,
    dataType: 'json',
    success: function() {}
});
Damjan Pavlica
  • 31,277
  • 10
  • 71
  • 76
mjibson
  • 16,852
  • 8
  • 31
  • 42
  • 1
    This was a very useful question. It allows me to send a payload as a string (by changing the Content-Type), which prevents me from having to deal with OPTIONS prior to POST/GET. – earthmeLon Jun 04 '14 at 15:52
  • I have a same question ,it's after i request the url,but I can't get the parameter which i submit – 黄伟杰 Jul 31 '16 at 15:50

21 Answers21

624

The following line needs to be added to the $http object that is passed:

headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}

And the data passed should be converted to a URL-encoded string:

> $.param({fkey: "key"})
'fkey=key'

So you have something like:

$http({
    method: 'POST',
    url: url,
    data: $.param({fkey: "key"}),
    headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
})

From: https://groups.google.com/forum/#!msg/angular/5nAedJ1LyO0/4Vj_72EZcDsJ

UPDATE

To use new services added with AngularJS V1.4, see

Community
  • 1
  • 1
mjibson
  • 16,852
  • 8
  • 31
  • 42
  • 3
    Is there a way for the json > url encoding of the data to happen automatically or to specify this happening for every POST or PUT method? – Dogoku Oct 31 '12 at 13:25
  • 51
    +1 @mjibson, For me even passing the headers was not working, until i saw your answer containing this: `var xsrf = $.param({fkey: "key"});` Thats stupid, why can't angular do it internally? – naikus Feb 04 '13 at 06:44
  • @naikus That doesn't sound like a bad idea. Submit a feature request to the angular folks? – mjibson Feb 05 '13 at 19:43
  • 13
    To closer follow $.ajax default behavior, charset should also be specified in the content type header - `headers: {Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}` – Imre Aug 07 '13 at 07:55
  • Note that this requires jquery, as opposed to Angular's minimal implementation. Maybe that's why they didn't implement this the jquery way…? – Sam Barnum Sep 17 '13 at 18:21
  • 25
    Instead of using jQuery's param function, just set the params property on the $http request and it will do what the jQuery.param method does as long as the Content-Type header is 'application/x-www-form-urlencoded' - http://stackoverflow.com/questions/18967307/jquery-ajax-request-works-same-angularjs-ajax-request-doesnt/18967966#18967966 – spig Nov 21 '13 at 19:52
  • 13
    @spig Yes, it will do what jQuery.param does, but, if you use the params property your properties will be encoded as part of the request URL instead of in the body - even if you have specified the application/x-www-form-urlencoded header. – stian Apr 15 '14 at 21:45
  • @mjibson: I tried the same, and send two list to the server. `data:$.param({"aList":aList},{"bList":bList}),` where aList and bList are javascript arrays. But, in request payload I am seeing `aList:[object Object]` `aList` has only one object in it, and I need to send two list to server, each contains some number of objects – Vivek Vardhan Mar 27 '15 at 07:30
  • 2
    As of 2016 it is possible to do it as described here: https://docs.angularjs.org/api/ng/service/$httpParamSerializerJQLike – Miroslaw Opoka Nov 17 '16 at 11:28
194

If you do not want to use jQuery in the solution you could try this. Solution nabbed from here https://stackoverflow.com/a/1714899/1784301

$http({
    method: 'POST',
    url: url,
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    transformRequest: function(obj) {
        var str = [];
        for(var p in obj)
        str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
        return str.join("&");
    },
    data: xsrf
}).success(function () {});
Community
  • 1
  • 1
Anthony
  • 2,050
  • 1
  • 12
  • 9
  • 7
    This method works for me in angular 1.2.x, and I think it is the best answer because it is elegant, it works in core angular and does not depend on any external libraries like jQuery. – gregtzar Dec 09 '13 at 23:19
  • 2
    I came across a problem when using this method inside of a $resource action. The form data was also including functions for $get, $save, etc. The solution was to alter the `for` statement a little to use `angular.forEach` instead. – Anthony Jan 08 '14 at 06:57
  • 10
    Note that in contrast to $.param() this method does not work recursively on arrays/objects. – MazeChaZer Nov 07 '14 at 15:56
  • 1
    I'd check that `obj[p]` is not _null_ or _undefined_. Otherwise you'll end up sending "null" or "undefined" string as the value. – tamir Jun 27 '16 at 08:19
  • 1
    I did not understand `transformRequest: function(obj)` As obj is undefined, Do we suppose to pass the xsrf? Like `transformRequest: function(xsrf)` – AkshayT Aug 09 '16 at 11:37
83

I took a few of the other answers and made something a bit cleaner, put this .config() call on the end of your angular.module in your app.js:

.config(['$httpProvider', function ($httpProvider) {
  // Intercept POST requests, convert to standard form encoding
  $httpProvider.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";
  $httpProvider.defaults.transformRequest.unshift(function (data, headersGetter) {
    var key, result = [];

    if (typeof data === "string")
      return data;

    for (key in data) {
      if (data.hasOwnProperty(key))
        result.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key]));
    }
    return result.join("&");
  });
}]);
kzar
  • 2,981
  • 2
  • 17
  • 13
58

As of AngularJS v1.4.0, there is a built-in $httpParamSerializer service that converts any object to a part of a HTTP request according to the rules that are listed on the docs page.

It can be used like this:

$http.post('http://example.com', $httpParamSerializer(formDataObj)).
    success(function(data){/* response status 200-299 */}).
    error(function(data){/* response status 400-999 */});

Remember that for a correct form post, the Content-Type header must be changed. To do this globally for all POST requests, this code (taken from Albireo's half-answer) can be used:

$http.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";

To do this only for the current post, the headers property of the request-object needs to be modified:

var req = {
 method: 'POST',
 url: 'http://example.com',
 headers: {
   'Content-Type': 'application/x-www-form-urlencoded'
 },
 data: $httpParamSerializer(formDataObj)
};

$http(req);
Mitja
  • 1,969
  • 30
  • 35
  • How can we do the same on a custom $resource factory ? – stilllife Oct 09 '15 at 10:14
  • Note: I upgrade an app from Angular 1.3 to 1.5. It changed the headers in the transformRequest. For some reason, the method above doesn't work for me, Angular adds double quotes around the URL-encoded string. Solved with `transformRequest: $httpParamSerializer, data: formDataObj`. Thanks for the solution. – PhiLho Jun 24 '16 at 14:14
24

You can define the behavior globally:

$http.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";

So you don't have to redefine it every time:

$http.post("/handle/post", {
    foo: "FOO",
    bar: "BAR"
}).success(function (data, status, headers, config) {
    // TODO
}).error(function (data, status, headers, config) {
    // TODO
});
wesbos
  • 25,839
  • 30
  • 106
  • 143
Albireo
  • 10,977
  • 13
  • 62
  • 96
  • 46
    Your example is so wrong... All that you're modifying is the header. The data themselves will still be JSON-encoded, and unreadable by older servers that cannot read JSON. – alexk Sep 10 '13 at 13:38
  • http://victorblog.com/2012/12/20/make-angularjs-http-service-behave-like-jquery-ajax/ -- here is a good example where you override the $http default header, as well as convert the object to serialized form data. – Federico Apr 24 '14 at 21:36
20

As a workaround you can simply make the code receiving the POST respond to application/json data. For PHP I added the code below, allowing me to POST to it in either form-encoded or JSON.

//handles JSON posted arguments and stuffs them into $_POST
//angular's $http makes JSON posts (not normal "form encoded")
$content_type_args = explode(';', $_SERVER['CONTENT_TYPE']); //parse content_type string
if ($content_type_args[0] == 'application/json')
  $_POST = json_decode(file_get_contents('php://input'),true);

//now continue to reference $_POST vars as usual
James Bell
  • 889
  • 2
  • 10
  • 16
  • this is one of the good example of server side fix, Because the real problem on this issue is on the server side API.. bravo – Vignesh Apr 21 '15 at 07:08
16

These answers look like insane overkill, sometimes, simple is just better:

$http.post(loginUrl, "userName=" + encodeURIComponent(email) +
                     "&password=" + encodeURIComponent(password) +
                     "&grant_type=password"
).success(function (data) {
//...
Serj Sagan
  • 28,927
  • 17
  • 154
  • 183
9

You can try with below solution

$http({
        method: 'POST',
        url: url-post,
        data: data-post-object-json,
        headers: {'Content-Type': 'application/x-www-form-urlencoded'},
        transformRequest: function(obj) {
            var str = [];
            for (var key in obj) {
                if (obj[key] instanceof Array) {
                    for(var idx in obj[key]){
                        var subObj = obj[key][idx];
                        for(var subKey in subObj){
                            str.push(encodeURIComponent(key) + "[" + idx + "][" + encodeURIComponent(subKey) + "]=" + encodeURIComponent(subObj[subKey]));
                        }
                    }
                }
                else {
                    str.push(encodeURIComponent(key) + "=" + encodeURIComponent(obj[key]));
                }
            }
            return str.join("&");
        }
    }).success(function(response) {
          /* Do something */
        });
tmquang6805
  • 266
  • 1
  • 4
  • 9
8

Create an adapter service for post:

services.service('Http', function ($http) {

    var self = this

    this.post = function (url, data) {
        return $http({
            method: 'POST',
            url: url,
            data: $.param(data),
            headers: {'Content-Type': 'application/x-www-form-urlencoded'}
        })
    }

}) 

Use it in your controllers or whatever:

ctrls.controller('PersonCtrl', function (Http /* our service */) {
    var self = this
    self.user = {name: "Ozgur", eMail: null}

    self.register = function () {
        Http.post('/user/register', self.user).then(function (r) {
            //response
            console.log(r)
        })
    }

})
Ozgur
  • 3,738
  • 17
  • 34
  • $.param only in jquery abi. http://jsfiddle.net/4n9fao9q/27/ $httpParamSerializer is Angularjs equivalent. – Dexter May 17 '17 at 01:15
7

There is a really nice tutorial that goes over this and other related stuff - Submitting AJAX Forms: The AngularJS Way.

Basically, you need to set the header of the POST request to indicate that you are sending form data as a URL encoded string, and set the data to be sent the same format

$http({
  method  : 'POST',
  url     : 'url',
  data    : $.param(xsrf),  // pass in data as strings
  headers : { 'Content-Type': 'application/x-www-form-urlencoded' }  // set the headers so angular passing info as form data (not request payload)
});

Note that jQuery's param() helper function is used here for serialising the data into a string, but you can do this manually as well if not using jQuery.

robinmitra
  • 604
  • 8
  • 16
  • 1
    The moderators simply deleted my previous answer because I didn't provide details of the actually implementation mentioned in the link. It would have been better if they had instead asked me first to provide further details, instead of deleting it, as I was already editing my answer to provide the details as seen in this answer! – robinmitra Jul 02 '14 at 12:36
  • The `$.param` do the magic. perfect solution for who has jQuery+AngularJS based app. – dashtinejad Jul 02 '16 at 09:55
7
var fd = new FormData();
    fd.append('file', file);
    $http.post(uploadUrl, fd, {
        transformRequest: angular.identity,
        headers: {'Content-Type': undefined}
    })
    .success(function(){
    })
    .error(function(){
    });

Please checkout! https://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs

aul
  • 91
  • 1
  • 2
4

For Symfony2 users:

If you don't want to change anything in your javascript for this to work you can do these modifications in you symfony app:

Create a class that extends Symfony\Component\HttpFoundation\Request class:

<?php

namespace Acme\Test\MyRequest;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\ParameterBag;

class MyRequest extends Request{


/**
* Override and extend the createFromGlobals function.
* 
* 
*
* @return Request A new request
*
* @api
*/
public static function createFromGlobals()
{
  // Get what we would get from the parent
  $request = parent::createFromGlobals();

  // Add the handling for 'application/json' content type.
  if(0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/json')){

    // The json is in the content
    $cont = $request->getContent();

    $json = json_decode($cont);

    // ParameterBag must be an Array.
    if(is_object($json)) {
      $json = (array) $json;
  }
  $request->request = new ParameterBag($json);

}

return $request;

}

}

Now use you class in app_dev.php (or any index file that you use)

// web/app_dev.php

$kernel = new AppKernel('dev', true);
// $kernel->loadClassCache();
$request = ForumBundleRequest::createFromGlobals();

// use your class instead
// $request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
carmel
  • 902
  • 7
  • 24
  • this was really usefull to me, the new createFromGlobals is working now perfectly. I don't know why you got a downvote, but I removed it. – Richard May 22 '14 at 08:44
3

Just set Content-Type is not enough, url encode form data before send. $http.post(url, jQuery.param(data))

Merlin Ran
  • 403
  • 4
  • 8
3

I'm currently using the following solution I found in the AngularJS google group.

$http
.post('/echo/json/', 'json=' + encodeURIComponent(angular.toJson(data)), {
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    }
}).success(function(data) {
    $scope.data = data;
});

Note that if you're using PHP, you'll need to use something like Symfony 2 HTTP component's Request::createFromGlobals() to read this, as $_POST won't automatically loaded with it.

Aditya M P
  • 5,127
  • 7
  • 41
  • 72
2

AngularJS is doing it right as it doing the following content-type inside the http-request header:

Content-Type: application/json

If you are going with php like me, or even with Symfony2 you can simply extend your server compatibility for the json standard like described here: http://silex.sensiolabs.org/doc/cookbook/json_request_body.html

The Symfony2 way (e.g. inside your DefaultController):

$request = $this->getRequest();
if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) {
    $data = json_decode($request->getContent(), true);
    $request->request->replace(is_array($data) ? $data : array());
}
var_dump($request->request->all());

The advantage would be, that you dont need to use jQuery param and you could use AngularJS its native way of doing such requests.

Michael
  • 1,922
  • 1
  • 19
  • 17
2

Complete answer (since angular 1.4). You need to include de dependency $httpParamSerializer

var res = $resource(serverUrl + 'Token', { }, {
                save: { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
            });

            res.save({ }, $httpParamSerializer({ param1: 'sdsd', param2: 'sdsd' }), function (response) {

            }, function (error) { 

            });
Sebastián Rojas
  • 2,776
  • 1
  • 25
  • 34
1

In your app config -

$httpProvider.defaults.transformRequest = function (data) {
        if (data === undefined)
            return data;
        var clonedData = $.extend(true, {}, data);
        for (var property in clonedData)
            if (property.substr(0, 1) == '$')
                delete clonedData[property];

        return $.param(clonedData);
    };

With your resource request -

 headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
vivex
  • 2,496
  • 1
  • 25
  • 30
0

This isn't a direct answer, but rather a slightly different design direction:

Do not post the data as a form, but as a JSON object to be directly mapped to server-side object, or use REST style path variable

Now I know neither option might be suitable in your case since you're trying to pass a XSRF key. Mapping it into a path variable like this is a terrible design:

http://www.someexample.com/xsrf/{xsrfKey}

Because by nature you would want to pass xsrf key to other path too, /login, /book-appointment etc. and you don't want to mess your pretty URL

Interestingly adding it as an object field isn't appropriate either, because now on each of json object you pass to server you have to add the field

{
  appointmentId : 23,
  name : 'Joe Citizen',
  xsrf : '...'
}

You certainly don't want to add another field on your server-side class which does not have a direct semantic association with the domain object.

In my opinion the best way to pass your xsrf key is via a HTTP header. Many xsrf protection server-side web framework library support this. For example in Java Spring, you can pass it using X-CSRF-TOKEN header.

Angular's excellent capability of binding JS object to UI object means we can get rid of the practice of posting form all together, and post JSON instead. JSON can be easily de-serialized into server-side object and support complex data structures such as map, arrays, nested objects, etc.

How do you post array in a form payload? Maybe like this:

shopLocation=downtown&daysOpen=Monday&daysOpen=Tuesday&daysOpen=Wednesday

or this:

shopLocation=downtwon&daysOpen=Monday,Tuesday,Wednesday

Both are poor design..

gerrytan
  • 40,313
  • 9
  • 84
  • 99
0

This is what I am doing for my need, Where I need to send the login data to API as form data and the Javascript Object(userData) is getting converted automatically to URL encoded data

        var deferred = $q.defer();
        $http({
            method: 'POST',
            url: apiserver + '/authenticate',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            transformRequest: function (obj) {
                var str = [];
                for (var p in obj)
                    str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
                return str.join("&");
            },
            data: userData
        }).success(function (response) {
            //logics
            deferred.resolve(response);
        }).error(function (err, status) {
           deferred.reject(err);
        });

This how my Userdata is

var userData = {
                grant_type: 'password',
                username: loginData.userName,
                password: loginData.password
            }
Shubham
  • 465
  • 6
  • 15
-1

The only thin you have to change is to use property "params" rather than "data" when you create your $http object:

$http({
   method: 'POST',
   url: serviceUrl + '/ClientUpdate',
   params: { LangUserId: userId, clientJSON: clients[i] },
})

In the example above clients[i] is just JSON object (not serialized in any way). If you use "params" rather than "data" angular will serialize the object for you using $httpParamSerializer: https://docs.angularjs.org/api/ng/service/$httpParamSerializer

Rafal Zajac
  • 1,613
  • 1
  • 16
  • 13
  • 2
    By using params instead of data, Angular places the data in the URL parameters instead of the request body. This is not what's expected from a form post. – cmenning Jul 22 '15 at 16:18
-3

Use AngularJS $http service and use its post method or configure $http function.

Shivang Gupta
  • 3,139
  • 1
  • 25
  • 24