Posting Form Data With $http In AngularJS
By default, when you go to post data in an AngularJS application, the data is serialized using JSON (JavaScript Object Notation) and posted to the server with the content-type, "application/json". But, if you want to post the data as a regular "form post," you can; all you have to do is override the default request transformation.
When you define an AJAX (Asynchronous JavaScript and XML) request in AngularJS, the $http service allows you to define a transform function for both the outgoing request and the incoming response. These are optional; and, by default, AngularJS provides transform functions that deal with JSON. This is a very flexible format because the post data can have an arbitrarily nested structure; but, it requires additional processing on the server.
If you want to post the data as a regular form post, two things need to happen:
- The content-type needs to be reported as "application/x-www-form-urlencoded".
- The data needs to be serialized using "key=value" pairs (much like a query string).
Both of these requirements can be fulfilled within a request transform function, which has access to the outgoing headers collection and the non-serialized data. The goal of the transform function is to update the headers (as needed) and to return the modified data that will be injected into the underlying XMLHttpRequest object.
I didn't find a serialization function in AngularJS that was designed for form-posts; so, I copied(ish) the .param() implementation in jQuery. In the following demo, I'm posting the data using this transform function. The server is then dumping out the content of the FORM scope and returning it in the result, which we are rending on the page.
<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />
<title>
Posting Form Data With $http In AngularJS
</title>
</head>
<body ng-controller="DemoController">
<h1>
Posting Form Data With $http In AngularJS
</h1>
<div ng-bind-html="cfdump">
<!-- To be populated with the CFDump from the server. -->
</div>
<!-- Initialize scripts. -->
<script type="text/javascript" src="../jquery/jquery-2.1.0.min.js"></script>
<script type="text/javascript" src="../angularjs/angular-1.2.4.min.js"></script>
<script type="text/javascript">
// Define the module for our AngularJS application.
var app = angular.module( "Demo", [] );
// -------------------------------------------------- //
// -------------------------------------------------- //
// I control the main demo.
app.controller(
"DemoController",
function( $scope, $http, transformRequestAsFormPost ) {
// I hold the data-dump of the FORM scope from the server-side.
$scope.cfdump = "";
// By default, the $http service will transform the outgoing request by
// serializing the data as JSON and then posting it with the content-
// type, "application/json". When we want to post the value as a FORM
// post, we need to change the serialization algorithm and post the data
// with the content-type, "application/x-www-form-urlencoded".
var request = $http({
method: "post",
url: "process.cfm",
transformRequest: transformRequestAsFormPost,
data: {
id: 4,
name: "Kim",
status: "Best Friend"
}
});
// Store the data-dump of the FORM scope.
request.success(
function( html ) {
$scope.cfdump = html;
}
);
}
);
// -------------------------------------------------- //
// -------------------------------------------------- //
// I provide a request-transformation method that is used to prepare the outgoing
// request as a FORM post instead of a JSON packet.
app.factory(
"transformRequestAsFormPost",
function() {
// I prepare the request data for the form post.
function transformRequest( data, getHeaders ) {
var headers = getHeaders();
headers[ "Content-type" ] = "application/x-www-form-urlencoded; charset=utf-8";
return( serializeData( data ) );
}
// Return the factory value.
return( transformRequest );
// ---
// PRVIATE METHODS.
// ---
// I serialize the given Object into a key-value pair string. This
// method expects an object and will default to the toString() method.
// --
// NOTE: This is an atered version of the jQuery.param() method which
// will serialize a data collection for Form posting.
// --
// https://github.com/jquery/jquery/blob/master/src/serialize.js#L45
function serializeData( data ) {
// If this is not an object, defer to native stringification.
if ( ! angular.isObject( data ) ) {
return( ( data == null ) ? "" : data.toString() );
}
var buffer = [];
// Serialize each key in the object.
for ( var name in data ) {
if ( ! data.hasOwnProperty( name ) ) {
continue;
}
var value = data[ name ];
buffer.push(
encodeURIComponent( name ) +
"=" +
encodeURIComponent( ( value == null ) ? "" : value )
);
}
// Serialize the buffer and clean it up for transportation.
var source = buffer
.join( "&" )
.replace( /%20/g, "+" )
;
return( source );
}
}
);
// -------------------------------------------------- //
// -------------------------------------------------- //
// I override the "expected" $sanitize service to simply allow the HTML to be
// output for the current demo.
// --
// NOTE: Do not use this version in production!! This is for development only.
app.value(
"$sanitize",
function( html ) {
return( html );
}
);
</script>
</body>
</html>
Notice that the call to the $http service is basically unchanged. The only difference, from a normal post, is that we are explicitly passing-in the "transformRequestAsFormPost" function as the "transformRequest" configuration option.
When we run the above code, we get the following page output:
As you can see, the outgoing request data was serialized for consumption as a regular form post.
NOTE: This does not use the "multipart/form-data" content type, which is primarily used for form posts that include binary file uploads.
In general, I like posting data as JSON. But, it does require some preprocessing on the server. Sometimes, it's nice to just to deal with normal form data that the server can consume automatically. It's nice that AngularJS is flexible enough to use both formats.
Want to use code from this post? Check out the license.
Reader Comments
Great post Ben. What's the advantage of using this over angular's built-in $http.post()?
@Cutter,
The $http.post() method is just a convenience method for the $http service - it still posts the data as a JSON body. This approach changes the actual serialization of the data.
Personally, I like the way AngularJS does it (JSON) - it's more flexible. But, it was just something I ran into while testing features. I like to document what I find :D
Am I crazy, or is that *way* overly complex for something that should be built in? I mean, a form post isn't some weird API - compared to a JSON packet, it is *more* common.
@Ray,
I have mixed feelings about this. When I look at how I have historically posted data with AngularJS, it is pretty much always a collection of name-value pairs that would work perfectly well as a Form post. But, I do have a number of places where the JSON object works nicely; the one that pops to mind is passing an array of IDs:
{ userIDs: [ 1, 2, 3, 4 ] }
This is one of those things that jQuery would serialize "ok" as a Form post; but, the approach to the serialization, even in the context of jQuery, has changed over time.... since there's no standard on what format to use with arrays.
Of course, could have just create a list:
{
userIDs: ids.join( "," )
}
... and then ColdFusion could use one of the many List functions, or even converted to an array with listToArray().
Anyway, all to say that most of my data is Form-post-ready; but not all of it. So, once you get the boilerplate in for the data-type conversion on the server-side, it is more flexible.
That said, if you've only ever dealt with Form data, it's definitely *confusing* as to why your data wont parse!! It definitely threw me through a loop when I first tried it in jQuery:
www.bennadel.com/blog/2207-posting-json-data-to-the-coldfusion-server-using-jquery.htm
@All,
On a related note, I did just add a quick post about *how* to parse the AngularJS data that gets posted using JSON:
www.bennadel.com/blog/2617-parsing-angularjs-request-data-on-the-server-using-coldfusion.htm
@raymon: this is also a solution to avoid jQuery $.params (http://scotch.io/tutorials/javascript/submitting-ajax-forms-the-angularjs-way) for example.
The fact is that PHP does not parse the JSON datas sent from angular into the $_POST. If you want angularjs $http.post() to work out of the box, you'll need some workarounds on the back-end.
Hey Ben, great post.
One question: Wouldn't you have problems with
if ( ! angular.isObject( data ) ) {
return( ( data == null ) ? "" : data.toString() );
}
snnipet?
On my tests, angular always send data as string "{\"name\":\"andre\",\"value\":\"1\"}",
which means return will be: "( data == null ) ? "" : data.toString()".
I have to change it by this and worked:
try {
data = JSON.parse(data);
} catch(e) {
return( ( data == null ) ? "" : data.toString() );
}
Am I missing something?
where is process.cfm
no result found just title?
@Sohaib,
process.cfm is just a ColdFusion script that outputs the form values.
Then Please guide me how I can or give email me that script. I also want to know. Whats the difference between above code and angularJS transformResponse?
Is angularJS has builtin functionality of transformRequest?
I we can achieve the above output using any angularJS function?
@Sohaib,
I don't think you understand. The whole point of this article is how to get Angular to POST form data in a name/value type method, how forms are normally posted. The server-side code is 100% inconsequential to that purpose. Ben showed an example in ColdFusion of simply echoing the form data back out to screen. You could do the same with PHP, Node, etc.
ok thanks.
I have writen the authenticate method in restfull api.
In my login form with angularJS. I get these values and hit the restfull api url to authenticate. here when i hit the url the angularJS encode these field data into json and send the values. but i do not want to encode these values into json.
Is it make any sense to do this?
If not please explain little bit
I believe then what Ben describes here is what you would need.
There is a small typo while setting the Content-Type header. You are setting 'Content-type' (mind the lowercase '-type').
This is causing issue as AngularJs $http will put Content-Type header to application/json and in this request transformer, you are setting Content-type and effectively ending up with some thing like, Content-Type=application/json; charset=UTF-8; application/x-www-form-urlencoded; charset=UTF-8
Thanks
@Manikanta,
Great! That little issue was freaking me out. On Firefox was working though.
Hi! Your example not working with insert object in data.
{
test: 'somevar'
array: [1,2,3],
insert: {test: 1, test2: 2}
}
I have dirty solution, maybe it's can help:
function myTransformRequest ( data, name ) {
var result = ''
, prefix = name || '';
if ( angular.isArray(data) ) {
if ( name ) {
prefix += '[';
}
for ( var key in data ) {
var val = data[key];
if ( angular.isObject(val) ) {
result += myTransformRequest(val, prefix + key + ']');
}else{
result += prefix + key + ']';
result += '='+val + '&';
}
}
return result;
}
if ( angular.isObject(data) ) {
if ( name ) {
prefix += '.';
}
for ( var key in data ) {
if ( angular.isFunction(data[key]) ) { break; }
if ( !data.hasOwnProperty(key) ) { break; }
if (key.charAt(0) === '$') {
break;
}
var val = data[key];
if ( angular.isObject(val) ) {
result += myTransformRequest(val, prefix + key);
}else{
result += prefix + key;
result += '='+val + '&';
}
}
return result;
}
return data;
}
@Manikanta,
Thanks for pointing it out! The original code works fine in firefox but I found the strange behaviour while testing Android browser.
Cheers
Found a small bug due to which above would not work with ASP.NET forms OR http handler or MVC web API post.
The
headers["Content-type"] = "application/x-www-form-urlencoded; charset=utf-8";
should be
headers["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8";
The "t" in "Content-type" should be capital "T"
:)
THANK YOU THANK YOU!
After 3 HOURS banging my head against the wall cussing at ColdFusion, I finally found your post. This has saved me and it's so easy to use!
True, what others say, you need to just fix the typo with the "Content-Type" but... thank you!!!
Hi Ben.
I've looking for something like you got, but i'm using it in a little form inside a hotspot for free internet access, as login form.
The backend uses common form data, so your approach is perfect for me, thanks.
Can i ask you something?
Can i use the funcion "fromJson()" to deserialize and transform the request?
(https://docs.angularjs.org/api/ng/function/angular.fromJson)
I'm new on Angular and i'm trying to not use jQuery within this form...
Thanks! ;)
How tall are you?
Hi Ben,
This is really helpful. Great work. But I am facing a strange problem, while requesting an GET api with "Content-type" = "application/x-www-form-urlencoded; which is intended to return a token, using the above code it is successfully calling the api but the response ain't getting into the success callback method of $http, but by observing the 'same' http request on fiddler, I am getting my intended response as json. Please help with this stuff.
Thanks
I was looking for "Posting Form Data With $http In AngularJS without jquery plugins". Below is my script..it may help the developer who are not interested to use jquery with angularJs.
angular.module('LMS.services', []).
factory('gcAPIservice', function($http) {
var gcAPI = {};
gcAPI.saveBookData = function(isbn, title, author, qty, price, status, comment) {
var datas = { isbn : isbn, title : title, author : author, qty : qty, price : price, status : status, comment : comment };
Object.toparams = function ObjecttoParams(obj) {
var p = [];
for (var key in obj) {
p.push(key + '=' + obj[key]);
}
return p.join('&');
};
return $http({
method: 'POST',
url: '/LMS/php/book.php?t='+timestamp,
data : Object.toparams(datas),
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
});
};
return gcAPI;
});
@Manikanta,
I had the same problem...@ben-nadel if you're still monitoring this, this article was extremely helpful but the Content-type typo caused a bit of problems. It should be Content-Type (caps "T"ype). Hopefully others will see this before pulling their hair out! :-)
Thanks for the article...very helpful.
Bug on Chrome:
The typo on the Content-type : lowecase 't' prevents the form from posting with the set Content-Type on Chrome.
Just incase someone else runs into this or I missed it on the comments above.
@Pankaj Kumar,
Dude... thank you very much this is exactly what I needed (Posting Form Data With $http In AngularJS without jquery plugins).
Thanks from Guatemala :)
Hi,
Thank you for the great solution.
One small thing I wanted to add:
on line 121 you can use:
angular.forEach(data, function(value, name) { ... })
and drop the following lines:
if (!data.hasOwnProperty( name ) ) {
continue;
}
var value = data[ name ];
Michael
Great Article! And great code.
Used it and modified a bit to adapt to a domain specific issue.
Thanks Ben!
@Michael,
Not sure it'll work as angular.foreach runs async as opposed to a for() loop and so the next line:
var source = buffer ...
won't have buffer fully ready by the time it runs.
@Nadav,
I am pretty sure that 'angular.forEach()' is synchronous.
Here is a plunker, check out the console:
http://plnkr.co/edit/c29H9Lmc2bQkzn7sj800?p=preview
Ben,
Thank you for this solution. Works great.
I use it with Mobile Angular UI
Hi,
I use this code and it work great on chrome.
But when i use firefox i've got "Bad Request" error.
What's the problem?
Thanx
Simone
in 2009 I was here for reading about css and some javascript posts on your blog and today I return here for reading how the way you works with Angularjs, You are good Ben :3
Hello, thanks for the article.
Question: i am running a simple http python server on my computer for testing. i did the following but my JSON file does not change , why is this ?
$http({
method: 'POST',
url: '../../resources/json/myJson.json',
data: "Put in something !!",
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
});
What about multiselect?
I ran into a problem when posting an array of values because they got submitted as a comma delimited list. If any of the values had commas in them, things fell apart.
To fix it, I added some quick tweaks to the serializeData function to check for arrays and output them as multiple parameters with the same name. It's available in jsFiddle. Note that a polyfill for isArray starts on line 4, and the code that checks for arrays is on line 24.
http://jsfiddle.net/tycahill/pxtmcran/
@Nugo, the changes I made for properly handling arrays should fix your multi-select problem. See http://jsfiddle.net/tycahill/pxtmcran/
Thanks Ben, for this and all your other blog posts. Very helpful and insightful for anyone learning Angular.
gracias cabeza, thanks =D
lean it thanks
I have write below code in html but getting error of "The page you are looking for cannot be displayed because an invalid method (HTTP verb) is being used."
in my html:
<form name="payment" action="{{vm.resource.authEndpoint+ '/Payment/SecondOpinionCasePayment'}}" method="post">
in html page source:
<form name="payment" action="" method="post" class="ng-pristine ng-invalid ng-invalid-required">
I am not understated that Why action is going black. what is alternate for form post?
Found nice stuff http://tutsnare.com/post-form-data-using-angularjs/ which post using form object in one go no need to data array creation for post one by one data
Hi, great article.
i wonder where is exactly json string after http.post
and
can i manage the data post in my dynamic page? in other words:
i'm using ruby on rails, i am not able to keep json string in my controller passed with http.post from angularjs
i write tutorial, usage of angularJS with asp.net please visit
http://aspmvcsnippets.com/AngularJS.aspx
Looks like angular 1.4.x has included a built-in serializer service called $httpParamSerializerJQLike to do this.
For an example (where I initially found out about it), see comment at stackoverflow at http://stackoverflow.com/a/30970229/1599447
Hello,
Do you know how to accomplish this in angular 2.0?
Can you put more pictures of yourself and point yourself out in the picture ?
d
I had same problem while using $http POST, $_POST was empty. I resolve it by using $httpParamSerializerJQLike since angularjs 1.4. instead of $.param provided by Jquery.
Here's the code js:
var connexionApp = angular.module('connexionApp', []);
var connexionCtrl = connexionApp.controller("connexionCtrl", [
'$scope'
,'$http'
,'$httpParamSerializerJQLike'
, function($scope, $http, $httpParamSerializerJQLike ){
try{
$scope.valider = function(){
$http({
method: 'POST',
url: '/checkuser.html',
data: $httpParamSerializerJQLike({
email: $scope.email
,password: $scope.password
}),
headers: {'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'}
})
.then(
function successCallback(response) {
console.log(response);
}
,function errorCallback(datas,status,headers,config) {
console.log("error status: " + status);
}
);
}
}catch(e){
console.log("exception error: " + e);
}
}]);