Using RESTful Controllers In An AngularJS Resource

Posted October 17, 2012 at 8:23 AM by Ben Nadel

Tags: ColdFusion, Javascript / DHTML

As I blogged last week, AngularJS provides the $resource class as an abstraction layer between your client-side code and the server-side API. This makes performing CRUD-style operations across the network fairly easy. But what happens when you need to execute a command on a RESTful resource that falls outside the standard CRUD (ie. Create, Read, Update, Delete) methods? Luckily, AngularJS handles RESTful "controllers" quite nicely.

According to The REST API Design Rulebook by Mark Masse, there are four resource archetypes:

  • Document - A resource representation.
  • Collection - A collection of server-managed data.
  • Store - A collection of client-managed data.
  • Controller - An action to perform.

Document, Collection, and Store are all about representing resources. Controllers, on the other hand, are about mutating resources. You can already use HTTP verbs to mutate a resource; but, sometimes, run-of-the-mill CRUD operations don't quite make sense. Take, as an example, the following resources:

  • /messages
  • /messages/4

Given this Collection of messages (first resource) and an individual message (second resource), how would you go about clearing all messages? Or, how about archiving the given message? CRUD doesn't quite fit the bill. But, Controllers work perfectly for these types of operations:

  • /messages/clear-all
  • /messages/4/archive

Here, we're using the "clear-all" Controller and the "archive" Controller to mutate the Collection and the Document representations, respectively.

When we start using Controllers in our resources, we are now left with a URL schema that has more variability. AngularJS $resource's can handle this; but, you have to get a little "clever" with how you define your parameter bindings.

Pulling the messages concept from above into an AngularJS context, we'd have to define our resource using the following template:

  • /messages/:id/:controller

The problem with this is that there is now a slight ambiguity around the first URL parameter. Is ":id" referring to my Collection-based Controller? Or is it referring to my Document ID?

To clear up this ambiguity, AngularJS allows us to define multiple parameters in the same portion of the URL template:

  • /messages/:listController:id/:docController

Notice that the second item in my URL template contains two parameters:

  • :listController
  • :id

As long as I only use one of these at a time, AngularJS will construct the RESTful resource properly. To demonstrate, I've put together a demo that defines and then invokes the messages resource from above:

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  • <title>Using RESTful Controllers In AngularJS Resources</title>
  •  
  • <!--
  • Because we are working with Resources, we have to load both
  • AngularJS and the ngResource module.
  • -->
  • <script type="text/javascript" src="../angular-1.0.2/angular.js"></script>
  • <script type="text/javascript" src="../angular-1.0.2/angular-resource.js"></script>
  • <script type="text/javascript">
  •  
  •  
  • // Tell Angular to load the ngResource before loading our
  • // custom app module.
  • var app = angular.module( "Demo", [ "ngResource" ] );
  •  
  • // Run this when the app is ready.
  • app.run(
  • function( $resource ) {
  •  
  •  
  • // When defining the resource, we get a few actions
  • // out of the box, like get() and query(), based on
  • // standard HTTP verbs. But, we can also use RESTful
  • // controller to change resource state using an
  • // action that falls outside normal CRUD operations.
  • var messages = $resource(
  • "./api.cfm/messages/:listController:id/:docController",
  • {
  • id: "@id",
  • listController: "@listController",
  • docController: "@docController"
  • },
  • {
  • clear: {
  • method: "POST",
  • params: {
  • listController: "clear-all"
  • }
  • },
  • archive: {
  • method: "POST",
  • params: {
  • docController: "archive"
  • }
  • }
  • }
  • );
  •  
  • // Now that we have our resource defined, let's invoke
  • // it with various configurations.
  •  
  • // GET without ID.
  • messages.query();
  •  
  • // Post with our list controller.
  • messages.clear();
  •  
  • // GET with ID.
  • messages.get(
  • {
  • id: 4
  • }
  • );
  •  
  • // POST with our document "controller".
  • messages.archive(
  • {
  • id: 8
  • }
  • );
  •  
  • }
  • );
  •  
  •  
  • </script>
  • </head>
  • <body>
  • <!-- Left intentionally blank. -->
  • </body>
  • </html>

Notice that my AngularJS resource accounts for two different controllers:

  • :listController - acting on our message collection.
  • :docController - acting on a specific message.

When I invoke the resource methods above, I end up making four requests to the following URLS:

  • GET http://.../api.cfm/messages
  • POST http://.../api.cfm/messages/clear-all
  • GET http://.../api.cfm/messages/4
  • POST http://.../api.cfm/messages/8/archive

As you can see AngularJS properly negotiated the appropriate RESTful URLs. Hella sweet!

If you are interested, here is my test API file:

api.cfm

  • <!--- Get the raw resourced path that was requested. --->
  • <cfset resourcePath = cgi.path_info />
  •  
  •  
  • <!---
  • NOTE: ColdFusion 10 seems to hang until I make a request to get
  • the POST body. Not sure why.
  • --->
  • <cfif ( cgi.request_method neq "GET" )>
  •  
  • <cfset requestBody = getHTTPRequestData().content />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • Identify the type of request that has come in based on the
  • pattern of the requested resource path.
  • --->
  • <cfif reFind( "^/messages$", resourcePath )>
  •  
  • <cfset response = "GET without ID." />
  •  
  • <cfelseif reFind( "^/messages/clear-all$", resourcePath )>
  •  
  • <cfset response = "POST with clear-all Controller." />
  •  
  • <cfelseif reFind( "^/messages/\d+$", resourcePath )>
  •  
  • <cfset response = "GET with ID." />
  •  
  • <cfelseif reFind( "^/messages/\d+/archive+$", resourcePath )>
  •  
  • <cfset response = "POST with archive controller" />
  •  
  • <cfelse>
  •  
  • <cfset response = "Hmm, couldn't match resource." />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • Serialize the response as JSON. AngularJS will know how to
  • parse this.
  • --->
  • <cfset serializedResponse = serializeJSON( response ) />
  •  
  • <!--- Add header for debugging the path. --->
  • <cfheader
  • name="X-Debug-Path"
  • value="#cgi.path_info#"
  • />
  •  
  • <!--- Stream response back to the client. --->
  • <cfcontent
  • type="application/json"
  • variable="#charsetDecode( serializedResponse, 'utf-8' )#"
  • />

For some reason, ColdFusion 10 was hanging on POST requests until I accessed the request body (via the getHttpRequestData() function). This only happened if the request body contained JSON (JavaScript Object Notation) data. If it was empty, the request did not hang. I don't know enough about request processing to know if that makes any sense. I can, however, confirm that ColdFusion 9 does not do this.


You Might Also Be Interested In:



Reader Comments

Nov 8, 2012 at 12:04 PM // reply »
64 Comments

First Suspect ...

https://bugbase.adobe.com/index.cfm?event=bug&id=3177732


Nov 8, 2012 at 12:06 PM // reply »
64 Comments

Argh ... Sorry wrong bug ... ;-)

https://bugbase.adobe.com/index.cfm?event=bug&id=3305486


Nov 27, 2012 at 7:39 AM // reply »
1 Comments

Thx for this post. It was very helpful; especially this: /messages/:listController:id/:docController. Didn't find any mention of multiple params in the same url portion in the angular docs or anywhere else


Dec 6, 2012 at 7:26 PM // reply »
1 Comments

I would love to see how you validate users' access to your rest resources.


Dec 8, 2012 at 8:47 PM // reply »
2 Comments

Hi Ben,

Is it possible to do CRUD interactions with Angular without REST services?

Best,
Pardeep.


Jan 11, 2013 at 8:54 PM // reply »
11,314 Comments

@Pardeep,

From what I understand, the $resource module (ie. ngResource) is a RESTful wrapper to the underlying $http service. If you want to, you can just reach down and use the $http service directly (very much akin to jQuery's $.ajax() method I believe).

From what I can tell, the $http service returns promise object ( $q ); $resource, on the other hand, returns empty object / array instances.


Jan 11, 2013 at 8:57 PM // reply »
11,314 Comments

@Atomi,

On the client side, we do security by fact of only certain user interfaces gets rendered for certain users. Beyond that, the client doesn't handle any "smarter" validation. On the server, we then authenticate the user on each request and make sure they have permissions to make the given request.

So, on the client, we have UI-based security; then on the server, we have full, detailed security.


Jan 23, 2013 at 2:29 PM // reply »
1 Comments

Very clever, this is exactly what I was trying to do, and I just didn't think about adding another optional parameter there. Thanks for getting me un-stuck~


Jan 23, 2013 at 3:06 PM // reply »
64 Comments

Okay Ben ... You got me hooked on Angular ...

I've been stalking the webs for the last week or so trying to get up to speed on Angular ...

One pattern I've noticed some of the more experienced NIO developers using is wrapping their module initialization into an anon function for encapsulation ...

===============================
https://gist.github.com/4612238
===============================

Personally I'm using Angular MV on the front with Spring 3 MC on the back ...

OOP through and through ...

I'm basically trying to emulate the pattern that LinkedIn is using by having a platform agnostic front-end via json and REST ...

Here's an awesome post on LinkedIn's engineering team discussing how they've embraced progressive advancement through layering all their technologies ...

http://engineering.linkedin.com/frontend/leaving-jsps-dust-moving-linkedin-dustjs-client-side-templates

Hope this ain't too spammy ;-)


Jan 24, 2013 at 12:54 AM // reply »
11,314 Comments

@Brad,

My pleasure! Glad to help.

@Edward,

As far as wrapping the code in an anonymous function, that's what I do as well when it comes to my real code. I take it is an opportunity to abbreviate the AngularJS library and my app module:

  • (function( ng, app ){
  • . . .
  • })( angular, myAppModule );

angular => ng
myAppmodule => app

I find this makes the subsequent code easier to write :)

I'll have to take a look at the LinkedIn stuff; we connect to an API that is pretty much a REST(ish) API, returns JSON. But, we have two hooks into it for different types of authentication (ex. Cookie vs. Basic Auth).


Jan 24, 2013 at 10:47 AM // reply »
64 Comments

@Ben,

Absolutely ... I'm a big fan of modularity and prototypical {real word?} objects. Great way to keep code manageable :-)


Apr 10, 2013 at 2:30 AM // reply »
1 Comments

Hey guys,

I've made a Service for AngularJS to be able to use RestFul backends easily. Please check https://github.com/mgonto/restangular


Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Jun 18, 2013 at 3:39 PM
Experimenting With The Amazon Simple Storage Service (S3) API Using ColdFusion
Hi Ben, THANKS! While not bleeding edge, it is new to me & I like learning new things every day! ... read »
Jun 18, 2013 at 12:30 PM
Disabling Auto-Correct And Auto-Capitalize Features On iPhone Inputs
Also spellcheck="false" should be mentioned as part of html5 specs ... read »
Jun 18, 2013 at 8:40 AM
Using Named Functions Within Self-Executing Function Blocks In Javascript
Hi Ben, you forgot to mention the most important thing for named self-executing functions - they can be referenced by name ONLY inside their execution context (which is parens in this case), it mean ... read »
dee
Jun 18, 2013 at 7:01 AM
My Safari Browser SQLite Database Hello World Example
hai ben, this program is really good i could understand the concept but i dint know how to save it and how to open it as you have done in the video can u give that details pls ... read »
Jun 18, 2013 at 6:04 AM
Clearing Inline CSS Properties With jQuery
Thanks a lot for for post! It helped me a lot... after being stuck since 24 hrs.. found solution from your post. Thanks again! ... read »
Jun 18, 2013 at 2:31 AM
SOTR 2013 - The Best Conference I Never Went To
I keep watching it, should keep me happily distracted until SotR14 ;) ... read »
Jun 17, 2013 at 9:45 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, As I was reading what you wrote, it occurred to me that maybe I do something similar to that in some of my client-side code. In an application I'm working on, there are a bunch of unrelated ... read »
Jun 17, 2013 at 9:36 PM
Object Thinking By David West
@Jonah, Please, don't feel bad at all. I appreciate all that you have contributed to the conversation. And, the more points of view I get, the more confident I am that I will some day, some how und ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools