Skip to main content
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Larry Lyons
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Larry Lyons

Using JSONP With $resource In AngularJS

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

Want to use code from this post? Check out the license.

Reader Comments

2 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

15,640 Comments

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

2 Comments

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

1 Comments

Thanks Ben, really helpful article.

Just to confirm though, this will only work with GET methods, correct? there is no way to use jsonp for POST methods? Is there a work around to make POST Cross-Domain request?

Thanks!

1 Comments

Hi Ben,

Really a nice article. I have a same question as Vijay. Is there a way we can have static callback instead of dynamic callbacks

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel