2

I want to implement "smart" caching in my application. I want to always first return data from the cache (if none is available an empty object/array is returned), then always fetch the data from the server and replace the cached response with the updated server response. The objective is to always quickly show something to the user. I want to do it in an "angular" fashion, i.e - adher to the promise paradigm.

I found a solution that uses the $resource service (http://www.bennadel.com/blog/2432-Applying-A-Cached-Response-To-An-AngularJS-Resource.htm), but $resource pretty much sucks if you don't use only the 4-5 default REST methods that it offers. Its custom method functionality is severly lacking. I'd really like to use the low level $http service, since it gives me better control over my requests, while keeping my controllers oblivious to the whole caching functionlity (i.e - avoid fetching data from the cache first in the controller itself and then querying the service).

Has anyone dealt with this problem and has a better solution?

Thanks :-)

5
  • 1
    $http internally provides you features to cache the request if you want to cache the request just set cache:true and $http will cache your request and it will also behave in a promise way as you want Commented Jul 29, 2013 at 12:24
  • Not really, since it'll stop attempting to query the server once it has a relevant cache value. I want the cached data, for that 1 sec it'll take to get the fresh data from the server, and then I want to update the view with the fresh query data. But due to the single callback nature of angular's promise, I'm finding it difficult to implement. I assume I'll need to somehow keep a reference to the response sent back to the controller, so I can inject the server data once it's available, I'm just not sure how to do that exactly Commented Jul 29, 2013 at 12:34
  • so does it mean you want to show old data while request is in process and show the fresh data when the request is completed ?? Commented Jul 29, 2013 at 12:39
  • 1
    @Ajaybeniwal exactamundo :-) But I'm hoping to do it in an elegant, encapsulated manner (i.e not by making two requests inside the controller, or using events). Commented Jul 29, 2013 at 12:41
  • 2
    @TomTeman I think what you want is really a rather bad way of solving the problem. Why would you want to show the users stale data? I would much rather see a tiny loading indicator for a second than I would want to see data that isn't relevant. In what situation would you need this? You have an already populated list and want to refresh it? The data in the list won't change until $http gets it's .success() callback. Commented Aug 6, 2013 at 10:49

1 Answer 1

1

http://jsfiddle.net/G23h7/

I created two services to accomplish what I think you're trying to accomplish. The first provides the core functionality to take in a promise and an object (or array) and updates said object or array when the promise resolves. There's a GUI for you to play around with it.

The second service integrates that into $http. Basically you can do smartHttp.forArray(config) and smartHttp.forObj(config) in lieu of $http(config). If you end up using this and want to use the $http shortcut methods then that should be straightforward to implement. This is untested - so consider it as pseudocode. If you're instantly returning a cached value/dud value it doesn't really make sense to use a promise for the return value of your smartHttp service (unless you were trying to make the service interchangeable with $http). If you'd like it to be a promise for that or whatever reason you can change:

var general = function (obj, methodName) {
    // ...
    return obj;
};

to the following:

var general = function (obj, methodName) {
    // ...
    return $q.when(obj);
};

And then ask for the $q service, of course. The real issue here is equality between requests - I assume $http does that nicely; I made a naive key - you may want to change that (as long as you have simple requests/same order for everything I don't think it should matter).

myApp.factory('smartCache', function () {
    var service = {};

    service.forArray = function (array, promise, clear) {
        promise.then(function (promiseResult) {
            if (clear) {
                array.length = 0;
            }

            angular.forEach(promiseResult, function (promiseResultElement) {
                array.push(promiseResultElement);
            });
        });
    };

    service.forObj = function (obj, promise, clear) {
        promise.then(function (promiseResult) {
            if (clear) {
                for (var prop in obj) {
                    delete obj[prop];
                }           
            }

            for (var prop in promiseResult) {
                obj[prop] = promiseResult[prop];
            }
        });
    };

    return service;
});

myApp.factory('smartHttp', function ($http, smartCache, $cacheFactory) {   
    var cache = $cacheFactory('smartHttp');

    var service = {};

    var general = function (config, methodName, initialValue) {
        var obj;
        var key = JSON.stringify([ config.url, config.method, config.params, config.data ]);
        var cachedObj = cache.get(key);
        if (cachedObj !== undefined) {
            obj = cachedObj;
        } else {
            obj  = initialValue;
        }

        var promise = $http(config);
        var smartCachePromise = promise.then(function (result) {
            return result.data;
        });
        smartCache[methodName](obj, smartCachePromise, true);

        return obj;
    };

    service.forObj = function (config) {
        return general(config, 'forObj', {});
    }

    service.forArray = function (config) {
        return general(config, 'forArray', []);
    }


    return service;
});
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.