Giter Site home page Giter Site logo

angular-cached-resource's Introduction

Cached Resource

An AngularJS module to interact with RESTful server-side data sources, even when the browser is offline. Uses HTML5 localStorage under the hood. Closely mimics the behavior of the core ngResource module, which it requires as a dependency.

Build Status npm version mit license

Features

  • Provides a simple abstraction to retrieve and save objects from a RESTful server.
  • Comes with a set of intelligent defaults that let you get started right away.
  • Caches all requests, and returns them immediately from the cache if you request them again.
  • Remmebers writes to the server, and adds them to the cache too.
  • If a write fails, try it again periodically until it succeeds. (This even works if you refresh the page in between!)
  • If you query for multiple resources in one request, each one is cached separately so you can request it from the cache individually, too.
  • Works as a drop-in replacement for Angular's $resource module.

News

It looks like this sort of functionality might be built into the upcoming Angular 2.0. Check out the design document here.


A simple example

// Register your module with ngCachedResource
angular.module('myApp', ['ngCachedResource']);

// Define a resource
var Article = $cachedResource('article', '/articles/:id', {id: "@id"});

// GET requests:
var a1 = Article.get({id: 1});
a1.$promise.then(function() {
  console.log('From cache:', a1);
});
a1.$httpPromise.then(function() {
  console.log('From server:', a1);
});

// POST/PUT/PATCH/DELETE requests:
var a2 = new Article({id: 2}); // *Note: setting the bound param (`id` in this case) is *required*
a2.title = "This article will be saved eventually...";
a2.body = "Even if the browser is offline right now.";
a2.$save();
a2.$promise.then(function() {
  console.log('Article was successfully saved.');
});

Read the tutorial on the Bites from Good Eggs blog.

*Note: Internally, $cachedResource keeps track of writes by bound params to ensure that it doesn't duplicate writes. If your bound param is null (say you're relying on the server to generate ids), then every write will replace the previous one. To avoid this undesirable scenario, simply ensure the id is set on the client, and you can safely ignore it on the server.


Installing

Bower:

bower install angular-cached-resource

npm: (intended for use with browserify)

npm install angular-cached-resource

Manual Download:


Usage

Provides a factory called $cachedResource:

$cachedResource(cacheKey, url, [paramDefaults], [actions]);

Arguments

  • cacheKey, String
    An arbitrary key that will uniquely represent this resource in localStorage. When the resource is instanciated, it will check localStorage for any

  • url, String
    Exactly matches the API for the url param of the $resource factory.

  • paramDefaults, Object, (optional)
    Exactly matches the API for the paramDefaults param of the $resource factory.

  • actions, Object, optional
    Mostly matches the API for the actions param of the $resource factory. Takes an additonal cache param (Boolean, default true) that determines if this action uses caching.

Returns

A CachedResource "class" object. This is a swap-in replacement for an object created by the $resource factory, with the following additional properties:

  • Resource.$clearCache( [options] )
    Clears all items from the cache associated with this resource. Accepts one argument, described below.

    • options, Object, optional
      options may contain the following keys:
      • where, which will limit the resources that are cleared from the cache to only those whose keys are explicitly listed. where can be an Array or an Object. If it is an Object, it will be treated like an Array containing only the provided Object. The Array should contain Objects representing cache keys that should be removed. If where is provided, exceptFor must not be provided.
      • exceptFor, which will limit the resources that are cleared from the cache to all resources except for those whose keys are explicitly listed. Just like where, exceptFor can be an Array or an Object. If it is an Object, it will be treated like an Array containing only the provided Object. The Array should contain Objects representing cache keys that should be kept. If exceptFor is provided, where must not be provided.
      • isArray, a boolean. Default is false. If true, then the function will treat the where or exceptFor arguments as referring to Array cache key.
      • clearChildren, a boolean. Default is false. If true, and isArray is also true, then the function will clear the Array cache entry (or entries) as well as all of the instances that the Array points to.
      • clearPendingWrites, a boolean. Default is false. If true, then the function will also remove cached instances that have a pending write to the server.

In addition, the following properties exist on CachedResource "instance" objects:

  • resource.$promise
    For GET requests, if anything was already in the cache, this promise is immediately resolved (still asynchronously!) even as the HTTP request continues. Otherwise, this promise is resolved when the HTTP request responds.

  • resource.$httpPromise
    For all requests, this promise is resolved as soon as the corresponding HTTP request responds.

Clearing the cache

Since there is a 5 megabyte limit on localStorage in most browsers, you'll probably want to actively manage the resource instances that are stored. By default, this module never removes cache entries, so you'll have to do this by hand. Here are the ways that you can accomplish this:

  • localStorage.clear()
    Removes everything in localStorage. This will not break the behavior of this module, except that it will prevent any pending write from actually occurring.

  • $cachedResource.clearCache()
    Removes every single Angular Cached Resource cache entry that's currently stored in localStorage. It will leave all cache entries that were not created by this module. (Note: cache entries are namespaced, so if you add anything to localStorage with a key that begins with cachedResource://, it will get deleted by this call). It will also leave any resource instances that have a pending write to the server.

  • $cachedResource.clearUndefined()
    Removes every Angular Cached Resource cache entry corresponding to a resource that has not been defined since the page was loaded. This is useful if your API changes and you want to make sure that old entries are cleared away.

  • $cachedResource.clearCache({exceptFor: ['foo', 'bar']})
    Removes every Angular Cached Resource entry except for resources with the foo or bar keys, or resource instances that have a pending write to the server.

  • $cachedResource.clearCache({clearPendingWrites: true})
    Removes every Angular Cached Resource entry, including those that have a pending write to the server.

If you have a "class" object that you've created with $cachedResource, then you can also do the following:

  • CachedResource.$clearCache()
    Removes all entries from the cache associated with this particular resource class, except for resource instances that have a pending write to the server.

  • CachedResource.$clearCache({where: [{id: 1}, {id: 2}])
    Removes two entries from the cache associated with this particular resource class; the ones with an id of 1 and 2. (This assumes that paramDefaults has an id param.)

  • CachedResource.$clearCache({exceptFor: {id: 1})
    Removes all entries from the cache associated with this particular resource class, except for those with an id of 1. (This assumes that paramDefaults has an id param.)

  • CachedResource.$clearCache({exceptFor: {query: 'search string'}, isArray: true})
    Removes all entries from the cache except those that were returned by the provided query parameters.

  • CachedResource.$clearCache({clearPendingWrites: true})
    Removes all instances of CachedResource from the cache, including those that have a pending write to the server.


Details

Asking for a cached resource with get or query will do the following:

  1. If the request has not been made previously, it will immediately return a resource object, just like usual. The request will go through to the server, and when the server responds, the resource will be saved in a localStorage cache.

  2. If the request has already been made, it will immediately return a resource object that is pre-populated from the cache. The request will still attempt to go through to the server, and if the server responds, the cache entry will be updated.

Updating a CachedResource object will do the following:

  1. Add the resource update action to a queue.
  2. Immediately attempt to flush the queue by sending all the network requests in the queue.
  3. If a queued network request succeeds, remove it from the queue and resolve the promises on the associated resources (only if the queue entry was made after the page was loaded)
  4. If the queue contains requests, attempt to flush it once per minute OR whenever the browser sends a navigator.onOnline event.

What if localStorage doesn't exist, or if the browser is out of space?

In either of these cases, $cachedResource will make sure all of your requests still happen. Things end up working just like the $resource module, with none of the caching benefits.


Development

Please make sure you run the tests, and add to them unless it's a trivial change. Here is how you can run the tests:

npm install
npm test

License

MIT

angular-cached-resource's People

Contributors

allensh12 avatar benbuckman avatar dannynelson avatar demands avatar goodeggs-terraformer avatar jtomaszewski avatar makebbekus avatar randypuro avatar sdhull avatar serhalp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

angular-cached-resource's Issues

Setting expiry

Is there a way to set an expiry for a resource? I have data that will never be updated from the client, so it won't be invalidated, but may change on the server. It isn't important that resource is always perfectly up-to-date, but it would be great if I could specify an expiration time, at which point requests would hit the server.

$promise and $httpPromise not always defined on cached objects inside an Array

Currently, when you read a single value from the cache, $promise and $httpPromise are set on the object:

    instance = new CachedResource
      $promise:     cacheDeferred.promise
      $httpPromise: httpDeferred.promise

However, when you read an array value from the cache, it's more complicated, and currently $promise and $httpPromise are only set on the Array and not the objects in the array.

@serhalp and I took a pass at fixing this, but it's pretty nuanced. Here's our current thinking about read_array_cache.coffee:

Currently the order is:

  1. Flush writes
  2. Kick off an async HTTP request and sometime later resolve the array $promise and $httpPromise
  3. If we have local data, load it from the cache and resolve the array $promise

Because of the async nature of the HTTP request, there are a couple states where we'd need to manage promises on the individual objects in the arrays:

  1. We have local data so the Array $promise is resolved, but we're waiting on the array $httpPromise, so we want the objects in the array to have their own $promise resolved with the cached object and an $httpPromise that is not resolved
  2. When the HTTP data comes back from the server the Array $promise and $httpPromise are resolved, so we want the objects in the array to have $promise resolved (either it was already resolved with local data in step 3 or if there was no local data it now gets resolved with fresh data) and $httpPromise resolved with the modified data (server + local via modifyObjectInPlace)

Add an ability to ignore some params in array request while generating its' cache key

... or allow the user to define his custom function that generates the resource's cache key.

Example:

ArticlesResource = $cachedResource("ArticlesResource", BACKEND_URL + "/articles", {id: "@id"});

ArticlesResource.query()
// [1] articles will be cached as "cachedResource://ArticlesResource/array" 

ArticlesResource.query({begin_at: (new Date).getTime()})
// [2a] articles will be cached as "cachedResource://ArticlesResource/array?begin_at=1410262799851"

ArticlesResource.query({begin_at: (new Date).getTime()})
// [2b] articles will be cached as "cachedResource://ArticlesResource/array?begin_at=1410262799852"

However, I would want to cache both the requests as the same request, f.e. "cachedResource://ArticlesResource/array?future". I think an ability to generate cache key on your own would be useable also in some other situations.

Describe assumptions about backend

Assumptions we make include:

  • query responses will return an array of full objects (in other words, if you GET /widget and it returns [{id: 1, note: 'widget 1'}], then a GET for /widget/1 needs to respond {id: 1, note: 'widget 1'} also
  • POST endpoint will respond with the full, modified object (or nothing, that works too)
  • POST endpoints should be idempotent. Making the same request against them multiple times might happen, especially if internet connections are flakey

$cachedResource is not $resource

At the moment your $cachedResource is not a drop-in replacement for $resource, as it requires one to rewrite all the code where $resource calls get made. I suggest you mixin/enrich your functionality on the original $resource provider, enabling drop-in and later removal of your module without breaking code.

So in an ideal scenario one would just load your plugin, optionally with a configuration that contains things such as TTL per resource, and it would just work without code rewriting.

I don't have much time to do this myself, but I can advise and review ;)

Compiled module lacks trailing semicolon

The last line of the not minified compiled module lacks a trailing semicolon, which causes errors when concatenation is used:

},{"./modify_object_in_place":5,"./resource_cache_entry":10}]},{},[4])

A simple workaround is to specify this module as last file.

CachedResource saves resolve to same object when called in $q.all

I have an Accounts service which is an angular-cached-resource:

angular.module('accounts').factory('Accounts', ['$cachedResource',
    function($cachedResource) {
        return $cachedResource('accounts', 'accounts/:accountId', { accountId: '@_id'
        }, {
            update: {
                method: 'PUT'
            }
        });
    }
]);

My createAll function takes an array and tries to post each element to the remote REST api:

$scope.createAll = function (newAccounts) {
        var promises = newAccounts.map(function(newAccount) {
            var account = new Accounts(newAccount);
            return account.$save();
        });

        $q.all(promises).then(function(results) {
          results.forEach(function(result) {
            console.log(result);
            result.$promise.then(function(promise_result) {
              console.log(promise_result);
            });
          });
        });
    };

Below is the karma test with example data. I'm expecting two POST requests made to the REST API since there are two elements in the array.

        it('$scope.createAll() should send multiple posts to XHR', inject(function(Accounts) {
            var newAccounts = [
            {
                name: 'New Account'
            },
            {
                name: 'New Account 2'
            }];

            var sampleAccountResponse1 = new Accounts({
                _id: '525cf20451979dea2c000001',
                name: 'New Account'
            });

            var sampleAccountResponse2 = new Accounts({
                _id: '525cf20451979dea2c000002',
                name: 'New Account 2'
            });

            $httpBackend.expectPOST('accounts', newAccounts[0]).respond(sampleAccountResponse1);
            $httpBackend.expectPOST('accounts', newAccounts[1]).respond(sampleAccountResponse2);

            // Run controller functionality
            scope.createAll(newAccounts);
            $httpBackend.flush();
        }));

The issue is that I only get one Post request, and the promises on the Cached Resources resolve to the same object. Test results for the karma test:

LOG: CachedResource{name: 'New Account', $resolved: false, $promise: Object{then
: function (callback, errback, progressback) { ... }, catch: function (callback)
 { ... }, finally: function (callback) { ... }}}
LOG: CachedResource{name: 'New Account 2', $resolved: false, $promise: Object{th
en: function (callback, errback, progressback) { ... }, catch: function (callbac
k) { ... }, finally: function (callback) { ... }}}

LOG: CachedResource{name: 'New Account', $resolved: true, $promise: Object{then:
 function (callback, errback, progressback) { ... }, catch: function (callback)
{ ... }, finally: function (callback) { ... }}, _id: '525cf20451979dea2c000001'}
LOG: CachedResource{name: 'New Account', $resolved: true, $promise: Object{then:
 function (callback, errback, progressback) { ... }, catch: function (callback)
{ ... }, finally: function (callback) { ... }}, _id: '525cf20451979dea2c000001'}

PhantomJS 1.9.8 (Windows 7) Accounts Controller Tests $scope.createAll() should
send multiple posts to XHR FAILED
        Error: Unsatisfied requests: POST accounts

If I run the same test with simply posting through the $http service, it works.

$scope.createAll = function (newAccounts) {
        var promises = newAccounts.map(function(newAccount) {
            var account = new Accounts(newAccount);
            return $http({
             url   : 'accounts',
             method: 'POST',
             data  : account
            });
            //return account.$save();
        });

        $q.all(promises).then(function(results) {
          results.forEach(function(result) {
            console.log(result);
            //result.$promise.then(function(promise_result) {
            //  console.log(promise_result);
            //});
          });
        });
    };
LOG: Object{data: Object{_id: '525cf20451979dea2c000001', name: 'New Account', $
cache: true, toJSON: function () { ... }, $params: function () { ... }, $$addToC
ache: function (dirty) { ... }, $save: function () { ... }, $remove: function ()
 { ... }, $delete: function () { ... }, $update: function () { ... }}, status: 2
00, headers: function (name) { ... }, config: Object{method: 'POST', transformRe
quest: [...], transformResponse: [...], url: 'accounts', data: CachedResource{na
me: ...}, headers: Object{Accept: ..., Content-Type: ...}}, statusText: ''}

LOG: Object{data: Object{_id: '525cf20451979dea2c000002', name: 'New Account 2',
 $cache: true, toJSON: function () { ... }, $params: function () { ... }, $$addT
oCache: function (dirty) { ... }, $save: function () { ... }, $remove: function
() { ... }, $delete: function () { ... }, $update: function () { ... }}, status:
 200, headers: function (name) { ... }, config: Object{method: 'POST', transform
Request: [...], transformResponse: [...], url: 'accounts', data: CachedResource{
name: ...}, headers: Object{Accept: ..., Content-Type: ...}}, statusText: ''}

Please correct me if I'm using something wrong, I'm new to this module. I'd gladly do some more in-depth testing if needed. I'd hate to have to revert to $http, this module has been really great so far, thanks for the awesome work put into it.

$httpPromise doesn't work on writes

When you write to a CachedResource, the library should populate the resource with an $httpPromise that gets resolved when the write is complete.

Add single query to detect presence of offline data

In an app that supports offline mode, it is arguably good practice/UX to visually indicate that there is offline data.

Whilst you can do: " MyResource.$writes.queue.length" for each different cached resource, this is a bit ugly (see my updated to #24 that $writes.count appears not to work)

I think it would be nice if you could do $cachedResource.numOfflineUpdates() and it return a number of the total write queues across all models.

If it is thought worthwhile I am happy to have a go at implementing.

$resolved added to the PUT request

Angular-cached-resource adds the $resolved field, when making a POST or PUT request for the second time. Then mongoDB gives me an error: 'Modified field name may not start with $'. How can the $resolved field be removed from a request?

Handling error responses from the server

What is the correct way to handle a 500 response with a cached resource? Currently, each time the client loads the request is retried. The error is due to invalid form data, so no amount of retrying will help the situation. My best guess for how to handle this is to delete the cached write request on 500 response.

.then function only get's called the first time when using $promise

When using $promise the .then() function only gets called a single time. The second time (when the actual data from the server returns) the scope gets updated but the .then function is not called.

Which means all extra business logic in this function is not added which returns half results compared when using $httpPromise (which does not use the cache at all afaik).

Pre-process the data before write into the localStorage

I would like the ability to pre process the data before write them into the localStorage.

The case such as the following.

My backend api might return me some date value in string format, and I would like convert them into js date object, then store them into the localStorage. so next time I get the data from the localStorage, then I don't need to convert them again.

PATCH method should be included

Custom actions may only have the methods GET, POST, PUT, and DELETE. The PATCH method should be included (Rails 4 uses PATCH instead of PUT).

Needs to be added here and in the README (line 24).

Error when cacheKey contains multiple underscores

There is a strange bug when you add a custom cache key with multiple single underscores. It took me ages to track it down so hopefully it will help someone else.

The following cache key works: EXAMPLE_ONEUNDERSCORE__TWOUNDERSCORES
However, this one doesn't: EXAMPLE_ONEUNDERSCORE_ONEUNDERSCORE

There seems to be something strange going on with the underscores when you have more than one single underscores.

Write actions don't play nice with protractor (maybe?)

It seems that $cachedResource write actions are not properly hooking into Protractor's knowledge of when requests are complete. This means that, sometimes, for integration tests, actions that result in a $cachedResource write are resolving before the write is actually complete.

Add option for use offline cache only

It would be nice to have an option to switch off passing the request to the server when the resource is always cached. In case of resource is already cached just return this cached data. If the resource isn't cached at this moment the promise rejects.
This could be used to avoid unnecessary data transfer if you know the resource hasn't changed.

For example I developing a mobile web application where all the content get downloaded once and following operating completely with offline data even when internet connection is available. I do this for don't charge the users traffic volume to much. Finally the app communicates with the server first when the user wishes to upload the changes.

400-responses should not be treated as though the resource is offline

This one's kind of a biggie. If the server responds with a 4xx status, that should indicate that there was a problem with the request.

We shouldn't re-queue writes in this case, we should do something else.

Reads will still come from the cache, but we should make sure the $httpPromise fails. (I think it already does but we're definitely not testing this particular case.)

"instance doesn't have any boundParams" error with array

I'm having very mysterious problems with this.

I have 2 API backend urls:
http://ktapi.jydev.fi/v2/f/servicedictionary/privateservices/joutsa
http://ktapi.jydev.fi/v2/f/servicedictionary/categorystruct/joutsa

Using the exactly same code at factory :

var SidenavFactory = function ($cachedResource, configs) {
return $cachedResource("nav", "http://ktapi.jydev.fi/v2/f/servicedictionary/categorystruct/" +     configs.apiParam, { id: "@id" });
};

SidenavFactory.$inject = ['$cachedResource', 'configs'];

Where configs.apiParam == "joutsa" and id parameter is unused and only there because it's not optional for cached resource.

The first api url works just fine, but when I replace the url with the second without changing anything else it stops working and this error hits console:
ngCachedResource 'nav' instance doesn't have any boundParams. Please, make sure you specified them in your resource's initialization, f.e.{id: "@id"}, or it won't be cached.

And the returned array is different. it contains the data (and network shows the call to have returned 200 OK and all data is fine and the JSON validates from both URLs)

image
The first object is from the failing url and second is the working one.

What could possbily be causing this?

Thank you!

Programmatically adding items to the cache

Pretty much the opposite of #8. It would be really useful (esp. in unit tests) to add items to the cache programmatically.

Perhaps something like:

var Resource = $cachedResource('derp', '/derp/:id', { id: '@id' });

// class method
Resource.$addToCache({herp: true, id: 1234}); // returns resource instance

// instance method
var item = new Resource({herp: true, id: 1234});
item.$$addToCache()

Using $$ instead of $ to namespace this as a "special" action that doesn't involve HTTP and can't be overridden.

$cachedResource fire an OPTION on load, which fail and repeat each X seconds.

Well this is kind of an odd case, but I get a cycling option firing each X seconds (each 20s I'd say), which fail because... I didn't make any actual call.

This is the error :

 OPTIONS http://myAPI.com/ angular.js:9814 

(anonymous function)angular.js:9615
sendReq angular.js:9331
$get.serverRequest angular.js:13170
processQueue angular.js:13186 
(anonymous function ) angular.js:14383 
$get.Scope.$eval angular.js:14199 
$get.Scope.$digest angular.js:14488 
$get.Scope.$apply angular.js:16214 
(anonymous function) angular.js:4902 
completeOutstandingRequest angular.js:5282
(anonymous function)

XMLHttpRequest cannot load http://myAPI.com/. Invalid HTTP status code 405 (index):1 

Notice that this is an option call.
Here is the code snippet creating the loop:

        Acaci: $cachedResource('acaci', API + ':ep/:s/:id/:extra',  {}, {
          get: {
            method:'GET',
            timeout: 1500,
            params: {
              s:'@s',
              ep:'@ep',
              id:'@id',
              extra:'@extra'
            }
          },
          create: {
            method:'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            params: {
              s:'@s',
              ep:'@ep',
              id:'@id',
              extra:'@extra'
            }
          },
          erase: {
            method:'DELETE',
            headers: {
              'Content-Type': 'application/json'
            },
            params: {
              s:'@s',
              ep:'@ep',
              id:'@id',
              extra:'@extra'
            }
          },
          update: {
            method:'PUT',
            headers: {
              'Content-Type': 'application/json'
            },
            params: {
              s:'@s',
              ep:'@ep',
              id:'@id',
              extra:'@extra'
            }
          }
        }),

Replacing cachedResource by ressource stops the Option cycle, that's why I think this is from angular-cached-resource.

This happen on any page of the app, right on load.

Bad url building when PUTting nested resources

Hi!, first of all, thank you for this marvelous module. I've set up my application with a setting to use cache and if it's active I interchange $resource with $cachedResource. So far so good, the performance improvements are notorious.

Nevertheless I have a problem with nested resources. I have setup a Contact "Groups" resource like this:

Groups = $resource('/groups/:id', {id: '@id'}, {
    saveByContact : {
        url : '/contacts/:contactId/groups/:groupId',
        method: 'PUT'   
    }
);

To alter the set of Groups a Contact belongs to, I need to PUT the array of Groups into the /groups endpoint of the contact, so I do something like:

Groups.saveByContact({contactId: contact.id}, arrGroups);

In $resource all is good and it ends up PUTting at the following route:

/contacts/536a9710dbb4d04f5a8b4568/groups with a body of [{"id":"536a90f0dbb4d0015a8b4569","name":"myGroup"},{"id":"53712c57dbb4d094588b458c","name":"myOtherGroup"}]

But, if I use $cachedResource, it ends up PUTting at:

/contacts/groups with a body of {"contactId":"536a9710dbb4d04f5a8b4568"}

Any ideas?

deep copy of a cached resource

Great module btw! Good work guys!

I use angular.copy() to put a resource in a temp variable to revert change made on it later on.
It doesn't work, with this code:

itemTemp = angular.copy(item);
console.log(item, itemTemp)
item = itemTemp;
item.$update(); // PUT METHOD

I get this in the console:

CachedResource {id: 1}, Object {id: 1}
XMLHttpRequest cannot load ...

Use transformRequest to update/upload files within the resource

Trying to upload some images with the resource, using directives to set the proper FormData and binding the files to the cachedResource object. The problem is, cachedResource write the JSON as string to the cache and then uses/parses the cached value to do the real request, not the initial object, it loses the File and FileList object properties, returning simple Objects that can't be parsed properly for a multipart/form-data.

I tried to create and Object by hand back from the values, no success.

I am trying to find some light, since I don't want to fallback to direct http request or the core $resource.

Code examples:

    function transformRequest (data, headers) {
      if (data === undefined) {
        return data
      }

      var fd = new FormData();

      angular.forEach(data, function(value, key) {
        if (value instanceof FileList) {
          angular.forEach(value, function(file, index) {
            fd.append(key + '_' + index, file);
          })
        } else if (value instanceof Array || value instanceof Object) {
          fd.append(key, JSON.stringify(value))
        } else {
          fd.append(key, value);
        }
      });

      return fd;
    }

    var User = $cachedResource('user', path, { id: '@_id' }, {
      update: {
        method:'PUT',
        transformRequest: transformRequest,
        headers: { 'Content-Type': undefined }
      }
    })

The directive that sets that binds the FileList to the user object:

angular.module('constructApp')
  .directive('filesModel', function ($parse) {
    return {
      restrict: 'A',
      link: function(scope, element, attrs){
        var model = $parse(attrs.filesModel)
          , modelSetter = model.assign

        element.bind('change', function(){
          scope.$apply(function(){
            modelSetter(scope, element[0].files)
          })
        })
      }
    }
  })
<form  name="form" novalidate ng-submit="saveUser()">

  <span class="profile-user">Edit Profile</span>

  <div class="from-group">
    <input type="file" files-model="user.files" class="from-control">
  </div>

  <div class="form-group double-input">
    <input type="text" ng-model="user.name" class="form-control" required>
  </div>

  <div class="text-right">
    <button class="btn btn-border" type="submit" ng-disabled="form.$invalid">Save</button>
  </div>

</form>

How to use $clearCache

Hi everyone!

I've started using angular cached resource and it works very well except for the clearing, that doesn't seem to work for me. I've tried very simples things, but none worked.

var test = $cachedResource('Example', 'myURL',{Id: 'unid'},
{ query: {
method: 'GET',
isArray:true,
}
});
var kel= test.query({count: 3});

When I run this code I get this in my local storage:
cachedResource://Example?Id=C1257902002AE240C1257B2B005C4B48
cachedResource://Example?Id=C1257902002AE240C1257B2B005C4B4A
cachedResource://Example?Id=C1257902002AE240C1257B2B005C4B4B

And if I try to add this, nothing happens:

kel.$httpPromise.then(function(data) {
test.$clearCache({where: {Id: "C1257902002AE240C1257B2B005C4B48"}});
});

I've also tried with "exceptFor", but I get the same result.

Am I missing something?
Thanks in advance,
Sami

Does not work for query.

I'm using a simple 'API' (basically just json files) which I query using query. When I go offline there is only a 404 request and no data gets loaded although it seems to be saved to the local storage. Here is how my service looks like:

angular.module('myMod')
    .service('Responses', function ($cachedResource, cacheKey, apiUrl) {
        'use strict';

        return $cachedResource(cacheKey, apiUrl + 'data/responses.json', {id: '@id'});
    });

Do I do something wrong?

Document logging

Logging from this module can be enabled. Uses Angular's $log service. Might want to document this feature somewhere.

Introspect write queue

It'd be pretty useful to see if there are any pending writes on the write queue, and maybe interact with them. A proposed API:

User.$writes.count // => Number, count of the writes remaining in the queue
User.$writes.promise // => Will resolve when all of the pending writes are complete
User.$writes.flush() // => Will attempt to make all outstanding requests again

This definitely needs more thought.

Access response headers from $promise and $httpPromise

Is it currently possible to access the response headers from either of these promises upon completion? I am trying to pass pagination information via headers, however I'm failing at figuring out how to access that information when using a cached resource.

Objects as request parameters don't properly serialize in cache keys

When you make a request like this:

var Derp = $cachedResource('derp', 'http://herp.com/derp/:id')
Derp.query({where: {createdAt: { $lt: 5 } } })

rght now we're ending up with keys like this:

cachedResource://derp/array?query=[Object object]

when we should have something like this:

cachedResource://derp/array?query={where:{createdAt:{$lt:5}}}

We need to be careful that the serialization scheme is deterministic (alphabetize keys, etc) so that the same query always uses the same resource.

Lowercase actions method are not taken into account.

Hi,

I have just seen something wrong, at my first start with the project.
I just replaced the $resource object by the $cachedResource and it went wrong...
Just because I had some custom actions methods in lowercase.

I'm not arguing on which one is the best or correct form, but you might want to ensure the compatibility ;)

Resources without boundParams get a "default" cache key

(and maybe a warning that can be disabled).

From a comment on the bites blog:

Dear Max, I am having such a problem:

ngCachedResource instance [object Object] doesn't have any boundParams. Please, make sure you
specified them in your resource's initialization, f.e. {id: "@id"}, or it won't be cached.

Why is that error happens? The requests are done but seemingly no caching happening. My URL
does not have any bound Params so I left the field empty, like so:

// URL has no params
var containerUrl = ConstantValueService.get('webApiUri') + '/api/containertype';
// trying to get it cached
return $cachedResource('sw_containerTypes',containerUrl, {},
{
// rest methods
getAll: {...}}

My response:

Hi Askar, sorry you're having difficulties with our module!

$cachedResource uses the bound parameters of the objects returned by an endpoint as a cache key (that's how it differentiates between objects!).

In your use case, do you only ever call getAll? Does the getAll endpoint have "isArray: true" set? If so, then you should be fine setting a bound parameter even though the parameter does not show up in the URL; set it to anything that could be a unique identifier returned by the getAll endpoint, like {id: '@id"}.

I've added an issue on github describing your use case; it seems like we could relatively easily allow for "localy defined" default cache keys, to alleviate this problem.

Hope that helps!

~Max

The proposed solution would only work for isArray cache keys, and is not amazingly ideal, but it would make the module useful for more people.

Discrepancy between ngResource and ngCachedResource API

I noticed their is a slight difference between the ngResource and ngCachedResource api…

Instance methods for ngResource return a promise:

instance.save().then ->

Whereas instance methods on ngCachedResource return a property $promise which has a promise:

instance.save().$promise.then ->

Thanks!

Support Angular 1.3

I'd like to give angular 1.3 a try, but it looks like there are some upgrade issues. I've found that the resource's $promise object is empty. For example

User.get().$promise.then
undefined

I investigated a little and found that the promise gets clobbered here:

angular.extend(instance, cacheEntry.value);

Cached array responses should update in place when http response arrives

Imagine you have a cached resource with some form of identifying characteristic like an id:

var CachedResource = $cachedResource('example', '/example/:id', { id: '@id' })

Then you query CachedResource and get back something from cache right away:

var list = CachedResource.query(); // [ { id: 1, name: 'One' }, { id: 2, name: 'Two' } ]
var cachedItem = list[0];  // { id: 1, name: 'One' }

But lets say the response comes back as something different:

list.$httpPromise.then(function() {
  list; // [ { id: 1, name: 'Uno' }, { id: 2, name: 'Dos' } ]
});

It'd be great if BOTH of these expressions reflected the response from the server:

list.$httpPromise.then(function() {
  list[0].name; // 'Uno'
  cachedItem.name; // 'One'
});

Unable to edit the cachedResource Response.

First of all thank you for this awesome module ;).

I was trying to install it, my that was quick to do!

Unfortunately here is the broken workflow I have, if I am doing anything wrong please let me know:
step 1 - nothing cached*

Acaci: new $cachedResource('acaci', API + ':ep/:s/:id/:extra',  {
          get: {
            method:'GET',
            params: {
              s:'@s',
              ep:'@ep',
              id:'@id',
              extra:'@extra'
            }
          },
          create: {
            method:'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            params: {
              s:'@s',
              ep:'@ep',
              id:'@id',
              extra:'@extra'
            }
          },
          erase: {
            method:'DELETE',
            params: {
              s:'@s',
              ep:'@ep',
              id:'@id',
              extra:'@extra'
            }
          },
          update: {
            method:'PUT',
            headers: {
              'Content-Type': 'application/json'
            },
            params: {
              s:'@s',
              ep:'@ep',
              id:'@id',
              extra:'@extra'
            }
          }
        })
---- Do a get query on Acaci ---
- Then parse the result, update object
- send back the final object to my controller
- print.

===> Everything is ok

step 2 -- cached version, right after doing the 1st one (just refreshed the page)

---- Do a get query on Acaci ---
- Then parse the result, update object
- send back the final object to my controller
- print.

==> the object is === to the Api query, nothing is updated. And I can't edit it at all actually.

Add a `cache` option, to disable caching for certain resource actions.

When creating resource classes, users can specify a "cache" option for an action that will explicitly disable the cache behavior for that action:

$cachedResource('issues', '/issues/:id', {id: '@id'}, {
  remove: { method: 'DELETE', cache: false }
});

By default, cache is assumed to be true.

This is the same issue as #3, but something happened to that pull request and I still think this feature is needed. Implementation spike exists here (by @jtomaszewski).

Programmatically clearing the cache

There should be a way to clear out values that we don't need anymore from the cache, to save space.

Seems like the API we've decided on is a function on the resource class: Resource.$clearCache([options]). The options argument is an optional object with the following attributes:

  • where: (Object or Array) Clear all object (or objects if an Array) in cache referenced by the provided options.
  • exceptFor: (Object or Array) Clear all object (or objects if an Array) in cache except for the ones referenced by the provided options.
  • isArray: (Boolean, default: false) Assume that the options in where or exceptFor refer to the attributes of an array query. This option is meaningless if where or exceptFor is not set.
  • clearChildren: (Boolean, default: false) Clear all the objects referenced by an array query, along with the array. This option is meaningless if isArray is false.
  • clearPendingWrites: (Boolean, default: false) Clear even the objects in cache that are associated with a pending write.

If neither where or exceptFor are set, $clearCache() will empty the entire cache (except for writes, unless clearPendingWrites is set).

Newly created objects are not queriable/editable offline

If you're offline, and you create an Article (as per the readme)

And then you do: 'Article.query()' then the newly created item isn't in the list. If you're building an offlline App this is probably not the desired behaviour.

There are of course issues here that if you include that data in any queries, in that the most REST servers want to allocate their own ID (which we don't know yet). So the only option might be a temp ID which we would need to remap at a later point.

Further issues exist if that temp ID is then referenced in another object. E.g. you create a new Author, get a 'temp ID' and then create an Article with that temp authorID. You could resolve this but you'd need to create the objects on the server in the same order, and then clean up/remap the IDs after each create.

Allow use of IndexedDB

Perhaps some feature detection decides whether to use IndexedDB (preferred) and then localStorage (fallback). Means that the cache needs to work asynchronously. (We're most of the way there already).

Why avoid localStorage?

For storing larger amounts of data, $localStorage should be avoided, since browsers will load the entire contents of an origin’s localStorage from disk each time the page is loaded. See: The Performance of localStorage Revisited

Getting boundParams error

Getting a boundParams error even though I'm using this cached resource:

$cachedResource('categories', API.url + '/categories/:id', { id: '@id' })

screen shot 2014-12-20 at 20 29 27

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.