Check out our new training course on AngularJS for Flex Developers

Calling a ColdFusion CFC from AngularJS

I have been working on my HTML5/AngularJS book and ran into a problem integrating AngularJS with ColdFusion. I wanted to be able to reuse the same ColdFusion services in my Angular project as they did with my Flex project. This didn't work quite as elegantly as I thought, so I thought I'd blog about the problems, and my solution.

Setting up the CFC

ColdFusion allows you to easily create a web service inside a CFC. Each method can be set with an access of remote and that makes the method available to AMF, SOAP, and REST calls. The AngularJS code will use REST. I put together a simple CFC for demonstration purposes:

<cfcomponent displayName="TestService" hints="I am a Test Service for an AngularJS Article">
 <cffunction name="sum" returnformat="plain" access="remote" output="true" hint="I am a Sample CFC" >    
  <cfargument name="num1" type="numeric" required="true"  />         
  <cfargument name="num2" type="numeric" required="true"  />         
  <cfreturn arguments.num1 + arguments.num2 />
 </cffunction>
</cfcomponent>

The CFC starts with a cfcomponent tag.  It has a single method inside of it, named sum.  The sum method accepts two numeric arguments, adds them together, and returns the result.  This is pretty simple. 

A ColdFusion Sample for creating a Post Call

There are two ways to call a CFC method as a REST service, either through an HTTP get call or an HTTP post call.  This is well documented in the ColdFusion docs. The problem with get approach is that all the values are put in the URL; and URLs have a character limit depending upon the browser. As such, I prefer to make calls using an HTTP post.  The name of the method and the arguments must be specified as form parameter in order for ColdFusion to determine how to process the call.

Here is a sample ColdFusion template to test the call:

<cfhttp url="http://#cgi.SERVER_NAME#/com/dotComIt/samples/callingCFC/TestService.cfc" method="post" result="results" >
                <cfhttpparam name="method" value="sum" type="formfield">
                <cfhttpparam name="num1" value="1" type="formfield">
                <cfhttpparam name="num2" value="2" type="formfield">
</cfhttp>
<Cfdump var="#results.filecontent#" />

This uses the CFHTTP tag to make the remote call.  It specifies three separate form fields:

  • method: This is the name of the method to call on the URL, named sum.
  • num1: This is the first argument value of the CFC’s method, and it is hard coded to the value 1.
  • num2: This is the second argument value into the CFC method, and it is hard coded to the value 2.

If you run this template, you'll see that you get a result of 3.0 displayed to the screen.  So, we have created a CFC and proved the method can be executed using an HTTP Post call.  The next step is to make this call from AngularJS.

Setting up the AngularJS App

To make a post call from AngularJS, you can use $http.post(), which is a helper function to make post calls easier.  Before we jump into that we’ll need to set up our Angular app.  Start with a simple HTML page:

<html>
<head>
    <script src="/AngularApp/js/libraries/angular.js"></script>
</head>
<body ng-app="testCalls">
</body>
</html>

This page imports the Angular framework and creates a single app; named testCalls.  Start another script block in the header and use it to define the application.  We'll be putting more JavaScript in this block later:

<script>
 testCalls = angular.module('testCalls', []);
</script>

Next, we'll need a div for our content:

<div ng-controller="testCallsCtrl">
</div>

The content is just going to be buttons that trigger calls to the remote service and display the results.  The content will be filled in shortly.  Create the controller in your JavaScript block:

function testCallsCtrl($scope, $http){
}

For now the controller is empty.  If you load your app in a browser, and check the console you should see no errors yet, but you also have no functionality.

Calling the Service with a Parameter object, and why it didn’t work

Most of the documentation I read about AngularJS has people making post calls and sending an object as the parameter, so that was my first approach.  First, add the button to the HTML Div:

<input type="button" value="Sum Via Post Object" ng-click="onPostObject()" />
<b>Post No Header Result</b>: {{postResultObject}}<br/>

This HTML Snippet will call the function onPostObject(), and through the use of binding display the results from the postResultObject variable. 

In your controller code, create the result variable:

$scope.postResultObject = 0;

Next, create a function to make the call:

$scope.onPostObject = function onPostObject(){<br />
}

Create an object to contain the three parameters:

var parameters = {
 num1 : 1,
 num2 : 2,
 method   : "sum"
}

This parameter object contains the method, sum, and values for the two input parameters to the method: num1, and num2.  As with our ColdFusion sample, both num1 and num2 are hardcoded values.  This is done for simplicity purposes of this sample.

Finally, use the $http.post() method to call the remote service:

$http.post('/com/dotComIt/samples/callingCFC/TestService.cfc', parameters).success(onPostObjectSuccess).error(onError);

The first argument of the post method is the endpoint of the service to call. In this case, it calls our TestService.cfc.  Then the parameter object is added.  On a success response from the server, an onPostObjectSuccess() method will be executed.  If the server has an error; then the onError() method is called.

The onError method is re-used for all our subsequent examples, so this is it:

var onError = function onError(data, status, headers, config){
 console.log('failure');
 console.log(data);
}

It simple logs the data to the console.  In a real app you would want to display information to the user, but that Is not needed here.  This is the onPostObjectSuccess() method:

var onPostObjectSuccess = function onPostObjectSuccess(data, status, headers, config){
 console.log('success');
 console.log(data);
 $scope.postResultObject = data;
}

This logs the returned data to the console, and uses it to set the controller’s postResultObject variable, which will in turn update the display.

If you set this up and try to execute, you will get unexpected results returned from the server. 

Instead of the number 3, you’ll see a bunch of HTML.  The ColdFusion server does not the parameter object as individual form variables, instead they are sent as a single object.  Since there is no method variable, ColdFusion does not try to execute the service method, but instead tries to load the CFC, which redirects to an Administrator login page.  The HTML of the login page is what is returned.

For comparison’s sake, this is what is shown in ServiceCapture for the call:

The ‘text’ is sent as one big JSON object.  That is not what we want.

Calling the Service with a Query String, and why it didn't work

The next approach I tried was to send the parameters along as if they were a query string.  First, let’s add the UI elements, the button and the place for the header:

<input type="button" value="Sum Via Post String" ng-click="onPostString()" />
<b>Post No Header Result</b>: {{postResultString}}<br/>

Back to the JavaScript block, and add the variable to hold the results from this:

$scope.postResultString = 0;

Next create the onPostString() method:

$scope.onPostString = function onPostString(){
 console.log('onPostStringNoHeader');
 parameters =    "num1" + '=' + 1 + '&'
 parameters +=   "num2" + '=' + 2 + "&"
 parameters +=   "method" + "=" + "sum" ;
 $http.post('/com/dotComIt/samples/callingCFC/TestService.cfc', parameters).success(onPostStringSuccess).error(onError);
}

The code uses string concatenation to create a parameter string, which will look like 'num1=1&num2=2&method=sum'. This is very similar to using a Query string.

The onPostStringSuccess() method just outputs the results to the console and updates the postResultString variable:

var onPostStringSuccess = function onPostStringSuccess(data, status, headers, config){
 console.log('success');
 console.log(data);
 $scope.postResultString = data;
}

If set this up and try it, the same thing will happen:

This is the same problem we previously saw.  ColdFusion doesn’t see the method name in the request and tries to launch the CFC in the CFC Explorer.  The page returned is asking for an administrator login.  This is the ServiceCapture information:

Although, we can see our parameter string as part of the call, there are no formal parameters attached to it; so ColdFusion can’t determine how to handle the call.

Calling the Service with a Query String, and getting expected results

The problem with the two previous calls is that the parameters are not being added to the call with the correct content type to create, what is essentially, a form post.  To solve this, we’ll need to specify the appropriate header.  First, add this to your HTML:

<input type="button" value="sum Via Post String w/ Header" ng-click="onPostStringWithHeader()" />
<b>Post Result With Header</b>: {{postResultStringWithHeader}}<br/>

This code snippet will call a method on the controller, and then display the results.  It works just like our previous two samples.

Create the postResultStringWithHeader variable in the Angular Controller:

$scope.postResultStringWithHeader = 0;

Next, create the method for making the remote call:

$scope.onPostStringWithHeader = function onPostStringWithHeader(){
 parameters =    "num1" + '=' + 1 + '&'
 parameters +=   "num2" + '=' + 2 + "&"
 parameters +=   "method" + "=" + "sum" ;
 $http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
 $http.post('/com/dotComIt/samples/callingCFC/TestService.cfc', parameters).success(onPostStringWithHeaderSuccess).error(onError);
}

This code is almost, identical to the previous approach.  The most important change is that it sets the header for the HTTP calls to ‘application/x-www-form-urlencoded’.  This is the secret sauce that made the remote calls magically work.  This is the result method:

var onPostStringWithHeaderSuccess = function onPostStringWithHeaderSuccess(data, status, headers, config){
 console.log('success');
 console.log(data);
 $scope.postResultStringWithHeader = data;
}

The result method displays the returned data to the console and stores it for display in the HTML template.

These are the successful results, displayed in the browser:

This is the request from service capture:

You can see that this request has a formal parameter tab as part of the call.  When this request gets to the server, ColdFusion is able to correctly discern the request and determine which method should be called and how to handle the parameters.  We have achieved success.

Final Thoughts

A more in depth solution to what is going on under the hood is written up here. It also provides additional details on handling arrays and objects, which are important to many aspects of development.  I have spent a couple days researching the different methods to communicate between Angular and ColdFusion without rewriting code, or adding JQuery Dependencies, and this has been the most elegant.

Sign up for DotComIt's Monthly Techincal Newsletter

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
Mark Fuqua's Gravatar I have been looking at angular a bit...pretty sure that is a direction I will be going. Can't wait to grab your book...any idea when it will be published? Are you going to use ColdFusion in the examples? Flex and ColdFusion work so well together, the transition will be tough.
# Posted By Mark Fuqua | 10/1/13 6:48 AM
Jeffry Houser's Gravatar @Mark

I'm targeting to have it finished by end of this year, which means it would come out early next year. For various reasons progress is going much slower than I had hoped.

Be sure to get on my mailing list if you're not already on it, by signing up at http://www.lifeafterflex.com
# Posted By Jeffry Houser | 10/1/13 7:36 AM
Gareth Arch's Gravatar Have you thought about a true REST implementation? On CF10 (if that's available), they have the built in REST services. If you're on a previous version of CF, I've had some great success with Taffy from Adam Tuttle. Very easy to add and does most things necessary for RESTful services. This would allow for GET, POST, PUT, and DELETE pretty easily, and would be less of a hack to get it to work (POSTing all of your information in the body rather than via URL variables).
# Posted By Gareth Arch | 10/1/13 11:59 AM
Jeffry Houser's Gravatar @Gareth I have not thought about a true REST implementation. My intent is to use services that were already created and in use.

I suppose I don't understand what is the difference between the service I created and "true REST".

I called the item I'm sending to the parameters argument of the post() method a query string; I'm not quite sure if they are "url variables" under the hood, as they are not appended to the URL as part of the request.

The in depth solution I linked to supports objects and arrays. It is a pretty cool implementation.
# Posted By Jeffry Houser | 10/1/13 12:52 PM
Salvatore Fusto's Gravatar Hi,
i'm trying to develop a local app, that will become a chrome application/extension, using angularjs.
my angular app in on my dev laptop, and backend is on my network server: this 2 pc have different IP's.
my problem is that if i try my remote cfc from a different domain but on the server, all is ok, but if i try to access the webservice from my dev pc with $http service, i always get a CORS error.
function myCtrl($scope, $http) {
var params = {method: "getArtists"};
$http.get('http://192.168.1.250:8500/remote/remoteCFC.cfc',params).
success(function(data){console.log('OK')}).
error(function(data,status){console.log('Error!');console.log(status);console.log(data)});
}
the error function display a "0" on console.
can you help me?
thanck you
# Posted By Salvatore Fusto | 11/13/13 3:23 AM
Jeffry Houser's Gravatar Salvatore,

If I had to guess; a web page shouldn't be able to make calls to a domain different than its own because this leads to cross site scripting attacks.

I'm not sure if Chrome Extensions exist under the same limitations as a web page may.

The work around is to use JSONP.

If that doesn't help; you may consider posting a detailed question on StackOverflow.

If that doesn't work; I am available for remote consulting and we can jump on a screen share to see if we can figure out the issue.
# Posted By Jeffry Houser | 11/13/13 5:20 AM
Salvatore Fusto's Gravatar Jeffry, thanks for your response: i too realized this after reading and googling.
a little question: in my remote cfc, how can i set the returnformat of the cfc methods, to correcly invoke them with json?
regards
# Posted By Salvatore Fusto | 11/13/13 10:51 AM
Jeffry Houser's Gravatar Salvatore ,

I'm not sure I understand the question; but you can set the returnFormat of the CFC method using a returnFormat property on the cffunction tag. I generally return a string and do my own JSON conversions. So, instead of returning 'JSON' I return a string that represents a JSON object.
# Posted By Jeffry Houser | 11/13/13 11:55 AM
Salvatore Fusto's Gravatar Jeffry, excuse my poor English.
really i asked for the type of data to be returned from cfc, not how set it by returnFormat attribute in cffunction.
OT: in chrome apps/etxs, you can avoid the web site limitation in ajax call: any chome exts is described by a manifest.json fine, where, among other, you can set this entry
..
"permission" : ["http://www.jeffryhouser.com";, "http://www.mysite.com";],
to make ajax call to only the two ones specified from different domains.
if you want we can debate on this.
thanks a lot and regards.
# Posted By Salvatore Fusto | 11/14/13 1:52 AM
Jeffry Houser's Gravatar Salvatore,

I'm sorry. I do not understand how their could be a difference between 'the type of data to be returned from a CFC' and using the 'returnFormat' attribute of the CFFunction tag. Prmarily, a CFC is just a container for cffunctions; and it does not return data itself.

I'm not sure what else to tell you.
# Posted By Jeffry Houser | 11/14/13 8:24 AM
Salvatore Fusto's Gravatar Jeffry, these misunderstandings are due to my poor english!
i intended: i know the returnFormat attribute to set returned data type, my question was: what kind of data are to set i.e. returnformat="?????????"
but i've solved: i'm able to fetch data, images too, produced by a remote CFC on a different domain, and consumed by an angular app on my laptop pc, with another ip, using JSONP.
thank a lot and regards.
# Posted By Salvatore Fusto | 11/14/13 8:57 AM
James Carey's Gravatar Thanks Jeffry for your helpful code. It's a nice example of AngularJS and ColdFusion working together.
# Posted By James Carey | 1/3/14 3:52 PM
Brad P.'s Gravatar Thank you for sharing, I'm looking forward t your book. When will it be out?
# Posted By Brad P. | 3/17/14 1:25 PM
Jeffry Houser's Gravatar Brad,

It is in the hands of a copy editor now. I'm hoping Late April or Early may. Be sure to sign for the announcement list at www.LifeAfterFlex.com .
# Posted By Jeffry Houser | 3/17/14 1:39 PM
All Content Copyright 2005, 2006, 2007, 2008, 2009 Jeffry Houser. May not be reused without permission
BlogCFC was created by Raymond Camden. This blog is running version 5.9.2.002.

pure garcinia cambogia

payday loans

seo services

seo services