Invalidate $resource Cache After Post Request
Asked Answered
B

3

18

I am using $resource and caching the results of get requests. My problem is that, after post requests, the cache is not being invalidated.

Here is the return value from the service:

return $resource('http://url.com/api/url/:id', {}, {
'query' : {
      method : 'GET',
      isArray:true,
      cache : true
    },
'get' : {
  method : 'GET',
  cache : false
}  
})

Here is the save method I am using inside my controller. As you can see, I'm using the callback on the post request to recalculate the query/list of nouns.

var newNoun = new Noun($scope.noun);
newNoun.$save(function(x) {
  $scope.nouns = Noun.query();
});

I would like to invalidate the cache after calling post or another non-get method. How could I do this? Is this already built into $resource or do I need to implement it on my own?

Baras answered 4/8, 2014 at 11:15 Comment(4)
possible duplicate of How to refresh / invalidate $resource cache in AngularJSKristykristyn
I came across that question when researching how to do this. The problem is that I don't know how to integrate the code into the service/controller method. I tried a few approaches, but couldn't get any to work. Please could you let me know how you would do this?Baras
Would you like to invalidate cache only for each resource ID or and entire cache?Disconnection
I would like it to mirror the request that was made. For example, if I post to /nouns, it should invalidate a get request to /nouns. If I post to /nouns/5, it should invalidate a get request to /nouns/5 in cache.Baras
D
29

You could create a wrapper service to do the caching like you want, for example:

app.factory('cachedResource', function ($resource, $cacheFactory) {
  var cache = $cacheFactory('resourceCache');

  var interceptor = {
    response: function (response) {
      cache.remove(response.config.url);
      console.log('cache removed', response.config.url);
      return response;
    }
  };

  return function (url, paramDefaults, actions, options) {
    actions = angular.extend({}, actions, {
      'get':    { method: 'GET', cache: cache },
      'query':  { method: 'GET', cache: cache, isArray: true },
      'save':   { method: 'POST', interceptor: interceptor },
      'remove': { method: 'DELETE', interceptor: interceptor },
      'delete': { method: 'DELETE', interceptor: interceptor },
    });

    return $resource(url, paramDefaults, actions, options);
  };
});

Then replace any $resource with cachedResource.

Example plunker: http://plnkr.co/edit/lIQw4uogcoMpcuHTWy2U?p=preview

Disconnection answered 4/8, 2014 at 18:0 Comment(2)
+1 Brilliant! Works like a charm. I'd go as far as adding a 'update': {method: 'PUT', interceptor: interceptor} to the list of actions. (I had to put the +1 in code cause apparently it was going to distract you from my second sentence...wtf)Irk
I think the interceptor should also delete the entry for the query URL or else query returns old information after you save/remove/delete entries.Quintan
R
5

While @runTarm's answer above is great, it does not allow actions to be easily customized from the inheriting service, e.g. the following would not be possible:

app.factory('Steps', function (CachedResource) {
    return CachedResource('/steps/:stepId', {}, {
        save: { method: 'POST', params: { stepId: '@stepId' } }
    });
});

In this case, this definition of save would be replaced by the one present in CachedResource.

Solution

But it can be fixed easily from Angular 1.4 by replacing

actions = angular.extend({}, actions, {

with

actions = angular.merge({}, actions, {

so that both objects are deep-merged.

Even better solution

In the above scenario, action options defined in CachedResource would be preferred over custom configuration in inheriting services. To fix that, switch the order of arguments passed to merge:

actions = angular.merge({}, { /* default options get, query, etc. */ }, actions);

With this solution, the following will work as expected (i.e. use DESTROY instead of default DELETE when calling remove):

app.factory('Steps', function (CachedResource) {
    return CachedResource('/steps/:stepId', {}, {
        remove: { method: 'DESTROY' }
    });
}); 
Riendeau answered 21/12, 2015 at 16:39 Comment(1)
using this method, how would you add say an update method, that still invalidates the cache for item? I'm thinking you have to expose the interceptor somehow to do it.Jaco
R
1

$resource is using the default cache for $http.

You can access it using: $cacheFactory.get('$http')

You can remove a key value pair, using the returned caches remove({string} key) method.


E.g.:

var key = '...the key you want to remove, e.g. `/nouns/5`...';
$cacheFactory.get('$http').remove(key);
Redbreast answered 4/8, 2014 at 13:20 Comment(1)
Thanks for the answer. Is there a good way to integrate that into the service so it's done automatically? Or do you need to implement it in the callback each time it's used?Baras

© 2022 - 2024 — McMap. All rights reserved.