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() 2009 (Minneapolis, MN) with:

Using JSONP With $resource In AngularJS

By Ben Nadel on

The other day, I was tracking down an "Access Denied" error when I came across an AngularJS $resource that was making a Cross-Domain request. It seemed that this was only happening in IE9 (if memory serves me correctly), so I assumed that it was a limitation with CORS (Cross-Origin Resource Sharing) in older IE browsers. To slap some duct tape on the problem, I switched it from a regular GET request to a JSONP (JavaScript Object Notation with Padding) request. But, getting JSONP to work with the $resource was a bit of a hurdle.


 
 
 

 
  
 
 
 

Ultimately, the problem was really one of documentation. JSONP (or JSON with Padding) is mentioned in the $resource API; but, it's really only documented well in the $http API, which I have yet to dig into.

When I converted the $resource from a GET request to a JSONP request, I started off by simply switching the method to "JSONP". Coming from a jQuery background, I assumed that the "callback" parameter would get automatically generated and applied to the outgoing request. But, it did not.

Then, I tried adding an explicit "callback" parameter to the JSONP request configuration. But, this still didn't work. The network activity looked right; but, AngularJS was rejecting the request with an error.

Finally, I jumped over to the $http documentation and figured out where I was going wrong. Not only do I have to explicitly define my "callback" parameter in AngularJS, the callback parameter has to have the set value, "JSON_CALLBACK". Once I added this to the outgoing request configuration, the JSONP request started working.

It's an interesting approach. AngularJS looks at the outgoing URL and replaces the phrase "JSON_CALLBACK" with an auto-generated callback name. If you make the same JSONP request a few times in a row, you'll notice that the callback name increments in the URL:

?callback=angular.callbacks._0
?callback=angular.callbacks._1
?callback=angular.callbacks._2
?callback=angular.callbacks._3

As you can see, AngularJS generates functions that are accessible off the global angular namespace.

To pull this post together, I put together a simple little demo that makes a JSONP request for a list of friends and then renders the list on the page:

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Using JSONP With $resource In AngularJS
  • </title>
  •  
  • <style type="text/css">
  •  
  • a[ ng-click ] {
  • color: red ;
  • cursor: pointer ;
  • text-decoration: underline ;
  • }
  •  
  • </style>
  • </head>
  • <body ng-controller="DemoController">
  •  
  • <h1>
  • Using JSONP With $resource In AngularJS
  • </h1>
  •  
  • <p>
  • I have the most awesome friends!
  • </p>
  •  
  • <!-- Show when data is loading. -->
  • <p ng-if="isLoading">
  • <em>Loading data...</em>
  • </p>
  •  
  • <!-- Show when data has finished loading. -->
  • <div ng-if="! isLoading">
  •  
  • <ul>
  • <li ng-repeat="friend in friends">
  • {{ friend.name }}
  • </li>
  • </ul>
  •  
  • <p>
  • <a ng-click="refresh()">Refresh List</a>
  • </p>
  •  
  • </div>
  •  
  •  
  • <!-- Load scripts. -->
  • <script type="text/javascript" src="../jquery/jquery-2.1.0.min.js"></script>
  • <script type="text/javascript" src="../angular-1.2.16/angular.min.js"></script>
  • <script type="text/javascript" src="../angular-1.2.16/angular-resource.min.js"></script>
  • <script type="text/javascript">
  •  
  • // Define our AnuglarJS module.
  • var app = angular.module( "Demo", [ "ngResource" ] );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I controll the demo.
  • app.controller(
  • "DemoController",
  • function( $scope, $resource ) {
  •  
  • // I determine if the page is currently in a loading state.
  • $scope.isLoading = false;
  •  
  • // I hold the list of friends to render.
  • $scope.friends = [];
  •  
  • // When defining the JSONP-oriented resource, you need to define the
  • // request such that it contains the string "JSON_CALLBACK". When you
  • // do this, AngularJS will replace said string on a per-request basis
  • // with a new and unique callback instance.
  • var resource = $resource(
  • "api.cfm",
  • {
  • callback: "JSON_CALLBACK"
  • },
  • {
  • getFriends: {
  • method: "JSONP",
  • isArray: true
  • }
  • }
  • );
  •  
  • // Get the list of friends.
  • loadRemoteData();
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I re-request the data from the server (using JSONP).
  • $scope.refresh = function() {
  •  
  • loadRemoteData();
  •  
  • };
  •  
  •  
  • // ---
  • // PRIVATE METHODS.
  • // ---
  •  
  •  
  • // I load the remote data.
  • function loadRemoteData() {
  •  
  • $scope.isLoading = true;
  •  
  • resource.getFriends().$promise.then(
  • function( friends ) {
  •  
  • $scope.isLoading = false;
  • $scope.friends = friends;
  •  
  • },
  • function( error ) {
  •  
  • // If something goes wrong with a JSONP request in AngularJS,
  • // the status code is always reported as a "0". As such, it's
  • // a bit of black-box, programmatically speaking.
  • alert( "Something went wrong!" );
  •  
  • }
  • );
  •  
  • }
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, the $resource instance is defined with the default parameter, callback: "JSON_CALLBACK". I'm then binding to the underlying $promise object of the response which will be resolved when the JSONP callback is invoked.

On a side-note, if a JSONP request fails, it's a little bit of a blackbox in AngularJS. Remember, JSONP requests aren't AJAX (Asynchronous JavaScript and XML) requests - they're Script-tag requests. This means that they either load or they error; and, when they error, AngularJS reports the status code as a "0", regardless of what the server returns.

NOTE: jQuery seems to be able to access the proper status code in Firefox. As such, I assume the issue is one of cross-browser normalization? Or maybe a bug in AngularJS? I'm not sure.

While it's not really relevant to this blog post, here is the API page that I was hitting with my JSONP request. As you can see, it simply takes the data and passes it back as JavaScript code that invokes the requested callback.

  • <cfscript>
  •  
  • // I am the name of the JavaScript method to invoke with the response data.
  • param name="url.callback" type="string";
  •  
  • // I am here to simulate HTTP latency (and to make the demo more interesting).
  • sleep( 1000 );
  •  
  • data = [
  • {
  • "id" = 1,
  • "name" = "Kim"
  • },
  • {
  • "id" = 2,
  • "name" = "Heather"
  • },
  • {
  • "id" = 3,
  • "name" = "Tricia"
  • }
  • ];
  •  
  • // When serializing the data, create an actual line of JavaScript code:
  • response = "#url.callback#( #serializeJson( data )# )";
  •  
  • </cfscript>
  •  
  • <!--- Reset the output buffer and return the byte array. --->
  • <cfcontent
  • type="text/javascript; charset=utf-8"
  • variable="#charsetDecode( response, 'utf-8' )#"
  • />

Ultimately, I probably shouldn't use the $resource service with a JSONP request. $resource provided a level of abstraction that doesn't really add much value with the limited functionality of a JSONP request. I used it primarily because it was already in place; and, because I'm not super familiar with the underlying $http service... yet.

Tweet This Great article by @BenNadel - Using JSONP With $resource In AngularJS Thanks my man — you rock the party that rocks the body!



Reader Comments

Hi Ben,

Any idea how to remove the auto-generate feature of JSON_CALLBACK. I would like to cache the service urls on the server. It is not working as angular auto creates the angular callbacks dynamically for each call.

Appreciate your help!

Thanks,
Vijay

Reply to this Comment

@Vijay,

I am not sure what auto-generate feature you are talking about? Do you mean that you want to cache the JSONP response locally? If so, it looks like you should be able to provide the "cache" configuration when defining the $resource. That said, I haven't tried it myself.

Reply to this Comment

Hi Ben,

Thanks for getting back.

From your blog, "It's an interesting approach. AngularJS looks at the outgoing URL and replaces the phrase "JSON_CALLBACK" with an auto-generated callback name. If you make the same JSONP request a few times in a row, you'll notice that the callback name increments in the URL:

?callback=angular.callbacks._0
?callback=angular.callbacks._1"

As you mentioned, the JSON_CALLBACK is been auto generated by angular. But in my case, I would like the Service URL to be cached in the server side.

let say, http://api.x.com/service?id=123 would be cached if I turn the cache ON. But as the angular adds up the dynamic callbacks, the cache feature is not working in the server as the Url may vary on each request

http://api.x.com/service?id=123&callback=angular.callbacks_0
http://api.x.com/service?id=123&callback=angular.callbacks_1
http://api.x.com/service?id=123&callback=angular.callbacks_2 ....

So, I would like to know any alternative that would help me to enable the cache on server. I came across an approach where they suggest to use a custom function that resembles the callback method name.

http://jsfiddle.net/pMGgR/

But I am not sure how efficient this is and how it handles parallel request.

Appreciate your help!

Thanks,
Vijay

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.