Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Nick Miller
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Nick Miller

Applying A Cached Response To An AngularJS Resource

By
Published in Comments (7)

Earlier this week, I demonstrated my AngularJS class, DeferredWithUpdate.js, as a way to apply cached responses to an AngularJS deferred value. As a follow up to that, I wanted to quickly demonstrate how to apply a cached response to an AngularJS $resource reference.

The AngularJS $resource module provides an abstraction for communicating with a remote API. Basically, you define what kind of actions can be performed on a remote resource and what kind of data is expected to be returned and AngularJS takes care of everything else.

This is nice; but, what's really cool is how AngularJS handles the return value. Unlike a Deferred / Promise value, the $resource module returns Array and Object references. At first, these objects are empty. But, when the server returns with data, AngularJS subsequently "hydrates" these empty objects with the deserialized data. This allows the $resource response to be injected into the Controller's $scope before the server has responded.

In this demo, I wanted to take a look at applying a cached response to these naked Array and Object $resource responses without messing up the object references. To do this, I've created a service object that encapsulates my $resource instance and injects cached values before returning the $resource:

<!doctype html>
<html ng-app="Demo">
<head>
	<meta charset="utf-8" />
	<title>Applying A Cached Response To An AngularJS Resource</title>
</head>
<body ng-controller="ListController">

	<h1>
		Applying A Cached Response To An AngularJS Resource
	</h1>

	<p>
		You have {{ friends.length }} friend(s).
	</p>

	<ul>
		<li ng-repeat="friend in friends">
			{{ friend.id }} : {{ friend.name }}
		</li>
	</ul>


	<!--Load AngularJS and the Resource module. -->
	<script type="text/javascript" src="./angular.min.js"></script>
	<script type="text/javascript" src="./angular-resource.min.js"></script>
	<script type="text/javascript">


		// Create an application module for our demo.
		var Demo = angular.module( "Demo", [ "ngResource" ] );


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// I control the demo UI.
		Demo.controller(
			"ListController",
			function( $scope, friendService ) {

				// Get the list of friends from the server. This
				// returns an AngularJS resource which can be injected
				// directly into the scope.
				$scope.friends = friendService.query();

			}
		);


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// I provide the access to the API.
		Demo.service(
			"friendService",
			function( $resource, applyCacheToResource ) {

				// Define our AngularJS resource (which makes the
				// HTTP requests to our server for us).
				var resource = $resource( "./api.cfm" );

				// Imagine that we have some locally cached data that
				// we've stored from a previous request.
				var cachedResponse = [
					{
						id: 3,
						name: "Joanna"
					}
				];


				// Provide an API for the controllers.
				this.query = function() {

					// Get the resource reference (at this point,
					// it is an empty array or object reference).
					var results = resource.query();

					// Before we return the resource, let's inject
					// our own cache. Since the Resource *always*
					// updates on the next "tick", we know that we
					// are not going to corrupt the true response
					// from the server.
					return(
						applyCacheToResource( results, cachedResponse )
					);

				};

			}
		);


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// I apply a cached response to an existing resource without
		// breaking the original resource reference.
		Demo.value(
			"applyCacheToResource",
			function( resource, cache ) {

				// Check to see what type of value we're dealing with.
				// If it's an array, we want to splice-in the cache;
				// if it's an object, we want to extend the keys.
				if ( angular.isArray( resource ) ) {

					resource.splice.apply(
						resource,
						[ 0, 0 ].concat( cache )
					);

				} else {

					angular.extend( resource, cache );

				}

				// Return the updated resource (for easy of use).
				return( resource );

			}
		);


	</script>

</body>
</html>

As you can see, I have an array of friends that is getting rendered in the main UI (user interface). The friend data is being retrieved from the server-side API using an AngularJS resource. My data access layer - friendService - applies a cached value to the response before returning it to the controller.

The cached value is being applied to the $resource with another helper method: applyCacheToResource(). In order to maintain the correct object references, the cached value is being used to "hydrate" - rather than replace - the $resource response. This allows (potentially dirty) data to be rendered early without messing up the normal $resource request lifecycle.

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

Reader Comments

3 Comments

I have a question about:

resource.splice.apply(resource,[0,0].concat(cache));

It's not clear to my exactly why you are using
"[0,0].concat(cache)" instead of "resource.concat(cache)"

3 Comments

Please let me rephrase:

It's not clear to my exactly why you are using
"[0,0].concat(cache)" instead of
"[].concat(cache)" or
"[0].concat(cache)" or something else.

Am I missing something?

15,841 Comments

@Peter,

No problem.

Since other people may have the same question, I'll just expand on the question:

I can't use concat() because it returns a new array; and, I need to inject the cached content into the existing array reference. The splice() method, on the other hand, can augment/mutate an array in-place, which is what I need to do.

Now, in core JavaScript, the .splice() method has the signature:

splice( index, deleteCount [ insert1, insert2 ... insertN ] )

I don't want to delete any values - I want to to inject my cached collection, which would be the:

[ insert1, insert2 ... insertN ]

... part of the signature above. Now, in order to get the signature correct, I also need to supply the "index" and "deleteCount" part of the signature.

That's why I use .concat() to build the "signature" arguments:

[ 0, 0 ].concat( cache ) ==> [ 0, 0, cache1, cache2, ... cacheN ]

Once I have the right arguments, I can then use the .apply() method to use those as the invocation arguments for the .splice() method.

Hope that helps for anyone else.

1 Comments

Ben,
This is a great example and coming in very handy, however, I do have a question.

In my current code, I'm doing something like this:
$scope.barriers = Barrier.query(function() {
//success
}, function() {
//fail
});

How can I get a promise returned when the resource data has been loaded so I know that I have all the data?

2 Comments

If there's an updated data with id = 3 in the response, then there will be two objects with id = 3, am I right?

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