Applying A Cached Response To An AngularJS Resource

Posted October 12, 2012 at 4:15 PM by Ben Nadel

Tags: Javascript / DHTML

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.




Reader Comments

Feb 1, 2013 at 2:33 PM // reply »
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)"


Feb 1, 2013 at 2:37 PM // reply »
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?


Feb 1, 2013 at 2:44 PM // reply »
3 Comments

nm, got it. Thanks!


Feb 3, 2013 at 2:11 PM // reply »
11,246 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.


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
May 23, 2013 at 9:52 PM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Muhmmadibn Did you figure out a solution to launching PDFs? I am running into the same issues myself. There is no way to close the PDF or go back once you launch it. Thanks in advance! ... read »
May 23, 2013 at 6:06 PM
The Girl Who Broke My Heart, And Made Me A Better Person
Good day,ladies and gentle men, my name is Dr AMADI the great spell caster in Africa, i have help so many people for different kind of problems,who say there is no solution to problems on earth, that ... read »
May 23, 2013 at 4:26 PM
ColdFusion QueryAppend( qOne, qTwo )
@Heather, Glad people are still getting value out of this! ... read »
May 23, 2013 at 3:49 PM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, I meant the code at the bottom (not the video). I did try to experiment with an intermediary variable, like: value = users.id[ i ]; arrayContains( userIDs, value ); ... but t ... read »
May 23, 2013 at 11:06 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Are you talking about As Number: YES As String: YES As Java: YES? If so, that's with 3 different ways of referencing the constant 1, not users.id[1]. Query object references(*) are what seem ... read »
May 23, 2013 at 9:55 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Dan, According to the CF Admin, I'm running Java "1.6.0_45". As far as the DB column, in the database it's an INT. I'll see if I can dig into what CF sees it as. @WebManWalking, But h ... read »
May 23, 2013 at 9:49 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, I think the problem is that we're used to loose typing in ColdFusion, like JavaScript. If a value is a number but it's needed in an expression to be a string, noooo problem. I've encountered ... read »
May 23, 2013 at 9:47 AM
ColdFusion QueryAppend( qOne, qTwo )
You rock! Thank you, thank you, thank you!!! ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools