Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2011 (Minneapolis, MN) with:

Posting Form Data With $http In AngularJS

By Ben Nadel on

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:


 
 
 

 
 Posting Form data in an AngularJS application. 
 
 
 

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.




Reader Comments

Great post Ben. What's the advantage of using this over angular's built-in $http.post()?

Reply to this Comment

@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

Reply to this Comment

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.

Reply to this Comment

@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:

http://www.bennadel.com/blog/2207-posting-json-data-to-the-coldfusion-server-using-jquery.htm

Reply to this Comment

@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.

Reply to this Comment

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?

Reply to this Comment

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?

Reply to this Comment

@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.

Reply to this Comment

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

Reply to this Comment

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

Reply to this Comment

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;
}

Reply to this Comment

@Manikanta,

Thanks for pointing it out! The original code works fine in firefox but I found the strange behaviour while testing Android browser.

Cheers

Reply to this Comment

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"

:)

Reply to this Comment

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!!!

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.