117

I have some old code that is making an AJAX POST request through jQuery's post method and looks something like this:

$.post("/foo/bar", requestData,
    function(responseData)
    {
        //do stuff with response
    }

requestData is just a javascript object with some basic string properties.

I'm in the process of moving our stuff over to use Angular, and I want to replace this call with $http.post. I came up with the following:

$http.post("/foo/bar", requestData).success(
    function(responseData) {
        //do stuff with response
    }
});

When I did this, I got a 500 error response from the server. Using Firebug, I found that this sent the request body like this:

{"param1":"value1","param2":"value2","param3":"value3"}

The successful jQuery $.post sends the body like this:

param1=value1&param2=value2&param3=value3

The endpoint I am hitting is expecting request parameters and not JSON. So, my question is is there anyway to tell $http.post to send up the javascript object as request parameters instead of JSON? Yes, I know I could construct the string myself from the object, but I want to know if Angular provides anything for this out of the box.

dnc253
  • 39,967
  • 41
  • 141
  • 157

13 Answers13

140

I think the params config parameter won't work here since it adds the string to the url instead of the body but to add to what Infeligo suggested here is an example of the global override of a default transform (using jQuery param as an example to convert the data to param string).

Set up global transformRequest function:

var app = angular.module('myApp');

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

That way all calls to $http.post will automatically transform the body to the same param format used by the jQuery $.post call.

Note you may also want to set the Content-Type header per call or globally like this:

$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';

Sample non-global transformRequest per call:

    var transform = function(data){
        return $.param(data);
    }

    $http.post("/foo/bar", requestData, {
        headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
        transformRequest: transform
    }).success(function(responseData) {
        //do stuff with response
    });
ocodo
  • 29,401
  • 18
  • 105
  • 117
Gloopy
  • 37,767
  • 15
  • 103
  • 71
  • I was wondering if there was something other than having a transformRequest function, but it sounds like there's not. Thanks for heads up about the jQuery param function. – dnc253 Aug 30 '12 at 14:49
  • Non-global per call method is working well for me, but when trying to set up globally via `$httpProvider.defaults`, then it not working, any clue about this ? – Dfr Dec 26 '12 at 08:27
  • 1
    WRT configuring it globally, I too am having issues. When I try to do it using the snippet given here, I get an error `Cannot read property "jquery" of undefined.` How do I fix this? PS. Per-call transformations work. – kshep92 Jan 21 '13 at 00:27
  • @kshep92 What is happening is that the transformRequest function is getting called on a request with no data so 'data' is undefined. I added a guard prior to 'return $.param(data);'. Insert this as the first line to the transformRequest function: 'if (data === undefined) return data;' See the edit I made to the answer. – Jere.Jones Feb 05 '13 at 15:05
  • In the local case, transformRequest erases the ones that Angular normally does right. In the non-global transformRequest how would one unshift or pull the function instead of over-writing? – NicolasMoise Feb 14 '14 at 19:41
  • @NicolasMoise you can try something like this: `transformRequest: [function(d) { return d; }, $http.defaults.transformRequest[0]]` replacing that first function with your own. If you don't change it globally the $http.defaults.transformRequest will be a one item array with their default transformRequest handling. See [this](https://github.com/angular/angular.js/blob/84ad0a037c3316d9dbe1f893628c77fcbd19ec1b/src/ng/http.js#L105) for reference. – Gloopy Feb 21 '14 at 00:56
  • @Gloopy I found a another solution `transformRequest: [function(){//my own function}].concat($http.defaults.transformRequest)` by using concat you can run your own function before (or after) the default transformRequest. This also preserves any functions you may have previously added to `$http.defaults.transformRequest`. In your example you would just be using the first function in your default transformations. – NicolasMoise Feb 21 '14 at 16:41
  • Not sure if it is just me, but I found by doing this $.param method, I am no longer able to use ":placeholder" in the URLs. – Archimedes Trajano Sep 02 '14 at 18:05
  • Angular 1.4.0 can no longer modify request headers using `transformRequest`: http://stackoverflow.com/a/30492592/584846 – Brent Washburne May 28 '15 at 14:26
  • 1
    as of Angular 1.4 you can use $httpParamSerializer instead of jQuery https://docs.angularjs.org/api/ng/service/$httpParamSerializer – theRemix Jun 03 '15 at 17:33
22

If using Angular >= 1.4, here's the cleanest solution I've found that doesn't rely on anything custom or external:

angular.module('yourModule')
  .config(function ($httpProvider, $httpParamSerializerJQLikeProvider){
    $httpProvider.defaults.transformRequest.unshift($httpParamSerializerJQLikeProvider.$get());
    $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
});

And then you can do this anywhere in your app:

$http({
  method: 'POST',
  url: '/requesturl',
  data: {
    param1: 'value1',
    param2: 'value2'
  }
});

And it will correctly serialize the data as param1=value1&param2=value2 and send it to /requesturl with the application/x-www-form-urlencoded; charset=utf-8 Content-Type header as it's normally expected with POST requests on endpoints.

Saeb Amini
  • 23,054
  • 9
  • 78
  • 76
17

From AngularJS documentation:

params – {Object.} – Map of strings or objects which will be turned to ?key1=value1&key2=value2 after the url. If the value is not a string, it will be JSONified.

So, provide string as parameters. If you don't want that, then use transformations. Again, from the documentation:

To override these transformation locally, specify transform functions as transformRequest and/or transformResponse properties of the config object. To globally override the default transforms, override the $httpProvider.defaults.transformRequest and $httpProvider.defaults.transformResponse properties of the $httpProvider.

Refer to documentation for more details.

Infeligo
  • 11,715
  • 8
  • 38
  • 50
  • I saw the params in the documentation, and like Gloopy mentions, I need it in the body, and not on the URL. I was wondering if there was some option or something I was missing to do the parameters instead of JSON, but it sounds like I just need to use the transformRequest property. – dnc253 Aug 30 '12 at 14:46
15

Use jQuery's $.param function to serialize the JSON data in requestData.

In short, using similar code as yours:

$http.post("/foo/bar",
$.param(requestData),
{
    headers:
    {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    }
}
).success(
    function(responseData) {
        //do stuff with response
    }
});

For using this, you have to include jQuery in your page along with AngularJS.

Sagar Bhosale
  • 328
  • 2
  • 4
7

Note that as of Angular 1.4, you can serialize the form data without using jQuery.

In the app.js:

module.run(function($http, $httpParamSerializerJQLike) {
  $http.defaults.transformRequest.unshift($httpParamSerializerJQLike);
});

Then in your controller:

$http({
    method: 'POST',
    url: myUrl',
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    data: myData
});
Ram
  • 3,092
  • 10
  • 40
  • 56
Thomas Graziani
  • 131
  • 3
  • 6
  • This answer is great. It addresses the 2 main problems with Post from Angular. The header must be set correctly and you have to serialize the json data. If you don't need IE8 support use 1.4+ or later. – mbokil Feb 22 '16 at 21:32
  • I just implemented this and it resolves issues I was having with post, but this also changes how patch works and appears to have broken all of my uses of $http.patch(). – Mike Feltman Apr 07 '16 at 20:48
5

I have problems as well with setting custom http authentication because $resource cache the request.

To make it work you have to overwrite the existing headers by doing this

var transformRequest = function(data, headersGetter){
  var headers = headersGetter();
  headers['Authorization'] = 'WSSE profile="UsernameToken"';
  headers['X-WSSE'] = 'UsernameToken ' + nonce
  headers['Content-Type'] = 'application/json';
};

return $resource(
  url,
    {
    },
    {
      query: {
        method: 'POST',
        url: apiURL + '/profile',
        transformRequest: transformRequest,
        params: {userId: '@userId'}
      },
    }
);

I hope I was able to help someone. It took me 3 days to figure this one out.

sudo bangbang
  • 27,127
  • 11
  • 75
  • 77
Frank Marcelo
  • 91
  • 1
  • 2
  • I guess you just saved me 3 days of work. Thanks!!! I am still trying to figure out if I can intercept request call somehow so that I can inject a custom header for every call. – marcoseu Jun 06 '13 at 17:41
5

This might be a bit of a hack, but I avoided the issue and converted the json into PHP's POST array on the server side:

$_POST = json_decode(file_get_contents('php://input'), true);
TimoSolo
  • 7,068
  • 5
  • 34
  • 50
4

Modify the default headers:

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

Then use JQuery's $.param method:

var payload = $.param({key: value});
$http.post(targetURL, payload);
Zags
  • 37,389
  • 14
  • 105
  • 140
3
   .controller('pieChartController', ['$scope', '$http', '$httpParamSerializerJQLike', function($scope, $http, $httpParamSerializerJQLike) {
        var data = {
                TimeStamp : "2016-04-25 12:50:00"
        };
        $http({
            method: 'POST',
            url: 'serverutilizationreport',
            headers: {'Content-Type': 'application/x-www-form-urlencoded'},
            data: $httpParamSerializerJQLike(data),
        }).success(function () {});
    }
  ]);
Rohit Luthra
  • 1,256
  • 17
  • 27
2

Quick adjustment - for those of you having trouble with the global configuration of the transformRequest function, here's the snippet i'm using to get rid of the Cannot read property 'jquery' of undefined error:

$httpProvider.defaults.transformRequest = function(data) {
        return data != undefined ? $.param(data) : null;
    }
kshep92
  • 841
  • 1
  • 11
  • 22
1

You can also solve this problem without changing code in server, changing header in $http.post call and use $_POST the regular way. Explained here: http://victorblog.com/2012/12/20/make-angularjs-http-service-behave-like-jquery-ajax/

vikramaditya234
  • 1,278
  • 2
  • 18
  • 37
0

I found many times problematic behavior of this whole. I used it from express (without typings) and the bodyParser (with the dt~body-parser typings).

I didn't try to upload a file, instead simply to interpret a JSON given in a post string.

The request.body was simply an empty json ({}).

After a lot of investigation finally this worked for me:

import { json } from 'body-parser';
...
app.use(json()); <-- should be defined before the first POST handler!

It may be also important to give the application/json content type in the request string from the client side.

peterh
  • 11,875
  • 18
  • 85
  • 108
  • I am sorry for the "and sacrifice a black hen"-style answer, what is unfortunately common in the current stage of the typescript/node/angular environment. – peterh Sep 02 '16 at 14:43
0

Syntax for AngularJS v1.4.8 + (v1.5.0)

       $http.post(url, data, config)
            .then(
                    function (response) {
                        // success callback
                    },
                    function (response) {
                        // failure callback
                    }
            );

Eg:

    var url = "http://example.com";

    var data = {
        "param1": "value1",
        "param2": "value2",
        "param3": "value3"
    };

    var config = {
        headers: {
            'Content-Type': "application/json"
        }
    };

    $http.post(url, data, config)
            .then(
                    function (response) {
                        // success callback
                    },
                    function (response) {
                        // failure callback
                    }
            );
pvrforpranavvr
  • 2,708
  • 2
  • 24
  • 34