Categories
AngularJS

Make AngularJS $http service behave like jQuery.ajax()

There is much confusion among newcomers to AngularJS as to why the $http service shorthand functions ($http.post(), etc.) don’t appear to be swappable with the jQuery equivalents (jQuery.post(), etc.) even though the respective manuals imply identical usage. That is, if your jQuery code looked like this before:

You may find that the following doesn’t exactly work for you with AngularJS out of the box:

The problem you may encounter is that your server does not appear to receive the { foo: 'bar' } params from the AngularJS request.

The difference is in how jQuery and AngularJS serialize and transmit the data. Fundamentally, the problem lies with your server language of choice being unable to understand AngularJS’s transmission natively—that’s a darn shame because AngularJS is certainly not doing anything wrong. By default, jQuery transmits data using Content-Type: x-www-form-urlencoded and the familiar foo=bar&baz=moe serialization. AngularJS, however, transmits data using Content-Type: application/json and { "foo": "bar", "baz": "moe" } JSON serialization, which unfortunately some Web server languages—notably PHP—do not unserialize natively.

Thankfully, the thoughtful AngularJS developers provided hooks into the $http service to let us impose x-www-form-urlencoded on all our transmissions. There are many solutions people have offered thus far on forums and StackOverflow, but they are not ideal because they require you to modify either your server code or your desired usage pattern of $http. Thus, I present to you the best possible solution, which requires you to change neither server nor client code but rather make some minor adjustments to $http‘s behavior in the config of your app’s AngularJS module:

[box type=”info”]Do not use jQuery.param() in place of the above homegrown param() function; it will cause havoc when you try to use AngularJS $resource because jQuery.param() will fire every method on the $resource class passed to it! (This is a feature of jQuery whereby function members of the object to parametrize are called and the return values are used as the parametrized values, but for our typical use case in AngularJS it is detrimental since we typically pass “real” object instances with methods, etc.)[/box]

Now you can go forward with using $http.post() and other such methods as you would expect with existing server code that expects x-www-form-urlencoded data. Here are a few sample frames of the final result for your day-to-day, end-to-end code (i.e. what you hoped and dreamed for):

The HTML template

The client code (AngularJS)

The server code (PHP)

Other notes

So you may wonder now, is it possible for PHP to read the JSON request from stock AngularJS? Why, of course, by reading the input to PHP and JSON decoding it:

Obviously the downside to this is that the code is a little less intuitive (we’re used to $_POST, after all), and if your server-side handlers are already written to rely on $_POST, you will now have to change server code. If you have a good framework in place, you can probably effect a global change such that your input reader will transparently detect JSON requests, but I digress.

Thank you for reading. I hope you enjoyed my first POST. (Get it?!)

[box type=”info”]EDIT March 12, 2014: Moved the param() function def up a level to help interpreter only create one such instance of that function.[/box]

102 replies on “Make AngularJS $http service behave like jQuery.ajax()”

I located you’re blog via Yahoo and I have to say. A Massive Thank you very much, I believed your post was incredibly educational I’ll revisit to see what additional great information I can recieve here.

I do not even know how I ended up here, but I thought this post was great. I do not know who you are but definitely you are going to a famous blogger if you are not already 😉 Cheers!

Wonderful! Thank you very much! Just a little note: on the success callback under “The client code (AngularJS)”, the callback accepts a function. I was getting errors, so I changed the code to this:
//
$http.post(‘/endpoint’, { foo: ‘bar’ }).success( function(response){
$scope.response = response;
$scope.loading = false;
});
//
Thanks again!

Hey man, this is wonderful!.

My data looks like this:
[[“abc”, “true”, 23], [“zxv”, “false”, 14]]
But jQuery.param converted to
undefined=&undefined=&undefined=
So, I had to replace “jQuery.param(data)” with “JSON.stringify(data)”, and then everything worked like charm.
Thanks a lot!

That’s interesting. The top-level construct would definitely need to be an object (key-value map) in order to parametrize correctly with $.param(); a nameless array couldn’t be converted to urlencoded format. What Web server are you using?

Hi, I tried both the angularjs and jquery codes above, and even implemented your client-side and server side code to test, but all I’m getting as a response is still HTTP/1.1 200 (I’m testing on a local server). And I don’t have access to the PHP server (but I did get a copy of the files for testing).

Here is the response info from Firefox: http://pastebin.com/5xZZDYng
Here is the current HTML code: http://pastebin.com/yFkFM6uX
Here is the current client-side code: http://pastebin.com/1NmLfsyz
Here are the server codes (expires tomorrow): http://pastebin.com/n0q57BcZ
I currently have the jquery code from above installed in an angular module.

I’ve scoured plenty of stackoverflow threads related to this issue already and your post was the most recently helpful. I’m new to angularjs (and I love it so far) so any advice would be greatly appreciated. Thanks Zeke!

Hi, Alf, thanks for commenting. I need to see your myApp module code especially to see that the $http transformRequest is working as it should.

Hey! Sorry to get back so late; for some reason I thought I would get an email notification for replies. So I’ve been building my app on top of Brian Ford’s angular express seed template, and I never thought about touching the myApp module, so there’s nothing in there related to PostCtrl: http://pastebin.com/ztYPmGZS

Check out the biggest code block in the post. You need to put that stuff in your module next to the $routeProvider and $locationProvider things you have in there already. That configuration stuff is the “driving force” here, what makes this come together.

Thanks for this post! Exactly what I needed to know. I’ll be sure to bookmark this post so others in my team know about it 🙂

Odd that Angular doesn’t send the data as post variables by default that developers are used to in jQuery.

You can do it on a per-request basis using low-level $http call (not ideal, of course):


$http({
method: 'post',
transformRequest: function(data) { ... }
});

If you go this route, it would probably be best to create a base module that all your other app modules inherit from, then in there override the $http service to implement appropriate transformRequest based on the request method. It’s not pretty, but it should be possible to do at a sufficiently low level so as to not clutter your app code!

Thank for this post. It helps me a lot !

I just have to make a small hack to make it work with array fields.
I changed line 18 to “var name, value, fullSubName, subName, subValue, innerObj, i;” : I added declaration of variable subName, to make it a scoped variable, not a global one, avoiding to mess up recursion.

Maybe I missed something ?

If I understand this post correctly, the module should work with arrays of objects (e.g., [[…],[…],[…]])??

I added the module to my code this morning. Single objects now work fine, but arrays of objects still aren’t ‘readable’ by my .NET back-end API.

Thanks!

Not sure about passing arrays of objects to .NET, but I noticed you put [[...],[...],[...]] which would actually be array of arrays, whereas array of objects would be like [{...},{...},{...}].

Sorry Zeke…meant to say [{…},{…},{…}]. Definitely arrays of objects. More explicitly, something like an array of email recipients:

[
{
listingId: 1234,
firstName: ‘Fred’,
lastName: ‘Smith’
emailAddress: ‘[email protected]
},
{
listingId: 1234,
firstName: ‘John’,
lastName: ‘Doe’
emailAddress: ‘[email protected]
},
{
listingId: 1234,
firstName: ‘Agent’,
lastName: ‘Smith’
emailAddress: ‘[email protected]
}
]

Thanks!

I found this which may interest you: http://haacked.com/archive/2010/04/15/sending-json-to-an-asp-net-mvc-action-method-argument.aspx/

I would recommend going that route of enabling the .NET server to accept JSON input considering the complex structure of your input. JSON is much more well defined for that structure than x-www-form-urlencoded input would be. I know, for example, in PHP your input would look like the following but I don’t think there is a particular standard languages implement so .NET could be wildly different (also, this x-www-form-urlencoded format is kind of bloated and difficult to read for this complexity):

emailRecips[0][listingId]=1234&emailRecips[0][firstName]=Fred&emailRecips[0][lastName]=Smith...

etc.

I’m sorry for the newbie request, but can you add an example using a format like the one featured in Angular Seed Project?? I can’t find a proper place to add the code you suggest and I don’t want to start with bad practices (I’ve just started learning Angular).
(for example, the syntax goes like this: angular.module(‘myapp’, [‘myapp.filters’, ‘myapp.services’, ‘myapp.directives’, ‘amyapp.controllers’]).config([‘$routeProvider’, function($routeProvider){}]);
Does it go inside config function? Or do I have to write a new chunk of code?

Hi, no problem. Yes, it goes inside the config function (this snippet taken from Angular Seed plus my “NOTE” comments):

Thanks a ton- you are a life saver. PHP’s inablity to understand native json is one thing — but I saw this exact same problem with webgo (http://webgo.io). Well, whatever gives.

Or… you can just tweak your server-side code and parse JSON yourself.

If you use PHP, you’ll need only one line of code for that:

$requestData = json_decode(file_get_contents(‘php://input’));

That’s noticeably less tweaking than you need with Angular.

I indicated how to do that at the end of the post. In not every case will a person have the option to change the server, in which case the client changes will have to be made. In regards to the comment of yours I rejected, your quote of “you should always use the big snippet above” was taken out of context (re-read if you don’t understand). Thanks for commenting anyway!

No, ZEKE, you don’t understand me. I repeat: if you are going to use this workaround, you will write it each time in each AngularJS app. I’ve tried this way also and I’m pretty sure now that it’s dumb way.
For some very rare cases, when you are developer in project and you can’t change server-side code – it’s a usable workaround.

That’s true, but you will just as well have to write the server-side fix for any new server app. I’m not sure how that is any different. If you think you need to write this more than once for any given client app, you’re mistaken; AngularJS allows modules to inherit from other modules. Therefore, you can write a single base module and have other modules in your app inherit from that thereby only having to put this client-side code in once.

“and if your server-side handlers are already written to rely on $_POST”
then write $_POST instead of $params.
And don’t forget check content type before it.

Thank You; this is exactly what I needed.

I couldn’t find any other contact information. Is there a formal license behind this code? ( Apache License? BSD License? Something else?).

I’m in the process of writing a book on AngularJS for Flash Platform Developers and I used this approach to help reuse server side code between the Flash App and the AngularJS app.

No formal license — free to use, modify, put in the book, etc. It’d be cool if you put my name in a footnote somewhere!

You are the MAN!!! I have been trying to get a POST to work with Grails Spring Security plugin. It would never get a ‘j_username’ parameter from my POST. I converted your code to .coffee and it worked immediately. THANK YOU SO MUCH FOR THIS!

Hey. Very useful post. Thanks for taking the time to write it. To really get into the angular spirit it would great if this came with a test. Just a thought!

I went the other way and dumped any JSON content received in PHP into the $_POST variable:

$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);

//continue to access $_POST vars as usual from here on

So now I can POST to the same PHP data pump with JSON via Angular or Form-Encoded via JQuery

Thank you for your input. 😉 I agree that this is probably the simplest way to do it; most frameworks will have a logical place to stuff this bit in, too, so it becomes transparent for normal use throughout your server code.

Holly shit, honestly, i am lost for words I cannot tell you how grateful I am i have been stuck on this problem for days literally. All the way from Africa A big thank you. Thank you so much, sooo soo much. Holly shit I am so happy

Thanks a lot for this post.. saved a lot of headache…
I think the Angularjs team should look at ur post and add these changes directly into Angularjs..

Thanks for sharing!
Is there any way I can make $http.get work the same as $http.post?
I Mean:

$http.get(‘/endpoint’, {p: ‘adocica’}) will GET /endpoint?p=adocica; and
$http.post(‘/endpoint’, {p: ‘adocica’}) will POST /endpoint with DATA=”p=adocica”

They should work like that by default (params for POST in body, params for GET in URL). Is it not working for you?

Sorry for that, i got problem with $httpProvider.
in console says, ‘injector ….’
now i’m with angular 1.2.13
can you help me to fix this?
thanks

It shouldn’t be a problem with that version. Can I see your full module code? Typically that error message occurs when you try to inject something that doesn’t exist. Note that in Angular 1.2.x, $sanitize is now in a separate module. Also, $resource has always been in a separate module and is frequently the cause of this error. See the following for all AngularJS auxiliary modules for your version, 1.2.13: http://code.angularjs.org/1.2.13/

small improvement to the transformer.

$httpProvider.defaults.transformRequest = [
function(data, headersGetter)
{
/**
* The workhorse; converts an object to x-www-form-urlencoded serialization.
* @param {Object} obj
* @return {String}
*/

var headers = headersGetter();

if(headers["Content-Type"] != "application/x-www-form-urlencoded;charset=utf-8"){
return data;
}

There is a bug in the code. when given fields with undefined values, ‘&’s are being appended onto the end of the string.

I have a working solution, but haven’t posted it to github yet, it uses your code, cept i turned query into an array, and then did some array manipulation via underscore and underscore contrib to turn it into a string.


if(angular.isObject(data) && String(data) !== '[object File]'){
var query_array = param(data);
return _.chain(query_array)
.flatten(true)
.map(function(query_pair){
var att_name = query_pair[0];
var att_val = query_pair[1];
return att_name + "=" + att_val;
})
.interpose("&")
.flatten()
.value()
.join("");
}
else{
return data;
}

param function line 29 replace fullSubName = name + ‘[‘ + subName + ‘]’; with fullSubName = subName + ‘[‘ + name + ‘]’; changed the query similar to $.param. It would help where we have input arrays e.g

[{“title”: “First”}, {“title”: “Second”}] convert into

Before 0[title]=First&1[title]=Second
After title[0]=First&title[1]=Second

Hi, thanks a lot for this, I have create a separate module that I can use in any projects where this is needed, as opposed to pasting into the main app config.

I had some problems when using minification, even when using ngmin before so.

To fix it, I changed the code as follows:

Instead of :

angular.module(‘MyModule’, [], function($httpProvider) {

I put:

angular.module(‘MyModule’, []);

.config(function($httpProvider) {

This will then work properly with ngmin and survive minification. Bit of an edge case but it might help someone out there!

Thanks for this tip. I just started using this module today, and the minification issue showed up pretty quick.

I found your blog post extremely useful and integrated your code into my development app. I had a question to ask. For large requests, i have read about using multipart/form-data. Can this code be tweaked to send requests with this header?

Thank you for sharing

Yours sincerely,
Arnab

Good question… I’m not sure off hand if modern browsers will support that, but you can theoretically replace “x-www-form-urlencoded” in the code above with multipart/form-data, then modify the transformRequest function to encode the data into multipart/form-data format instead of urlencoded.

Almost two years since this post was made and yet this is the one that helped me save me countless hours of possibly desperate scrambling.
Thank you so much for this solution! 😀

Thanks for this, saved me lots of headaches.

One question, is it possible to return an object in the success method? Arrays work just fine, but an object just returns empty.

in angular one can use $http.post and in zend framework 1 one can use
$body = $this->getRequest()->getRawBody();
$data = Zend_Json::decode($body);

without modifying any code in angular

I stumbled across this post today, and it fixed an issue I’ve been struggling with for a day and a half. Thanks!!

That said, I’m now calling a REST API that is expecting a .NET ‘List’ of the simple types I was successful at sending to earlier (thanks to this post).

Should the fix here (i.e., ‘MyModule’) also work with arrays of objects?

Thanks!

Thanks a ton man! I m new to Angular JS and I was like stuck for last 2 days because of this issue. This has certainly been an life saver for me.

Leave a Reply to Danocloud Cancel reply

Your email address will not be published.