1 | Cached Resource
|
2 | ===============
|
3 |
|
4 | An [AngularJS][angular] module to interact with RESTful server-side data
|
5 | sources, even when the browser is offline. Uses HTML5
|
6 | [localStorage][localStorage] under the hood. Closely mimics the behavior of
|
7 | the core [ngResource][ngResource] module, which it requires as a dependency.
|
8 |
|
9 | [![Build Status][travis-badge]][travis-link]
|
10 |
|
11 | ## Features
|
12 |
|
13 | * Provides a simple abstraction to retrieve and save objects from a RESTful
|
14 | server.
|
15 | * Comes with a set of intelligent defaults that let you get started right away.
|
16 | * Caches all requests, and returns them immediately from the cache if you
|
17 | request them again.
|
18 | * Remmebers writes to the server, and adds them to the cache too.
|
19 | * If a write fails, try it again periodically until it succeeds. (This even
|
20 | works if you refresh the page in between!)
|
21 | * If you query for multiple resources in one request, each one is cached
|
22 | separately so you can request it from the cache individually, too.
|
23 | * Works as a drop-in replacement for [Angular's $resource][ngResource] module.
|
24 |
|
25 | ## News
|
26 |
|
27 | It looks like this sort of functionality might be built into the upcoming Angular 2.0.
|
28 | [Check out the design document here](https://docs.google.com/document/d/1DMacL7iwjSMPP0ytZfugpU4v0PWUK0BT6lhyaVEmlBQ/edit#).
|
29 |
|
30 | ----
|
31 |
|
32 | ## A simple example
|
33 |
|
34 | ```javascript
|
35 |
|
36 | // Register your module with ngCachedResource
|
37 | angular.module('myApp', ['ngCachedResource']);
|
38 |
|
39 | // Define a resource
|
40 | var Article = $cachedResource('article', '/articles/:id', {id: "@id"});
|
41 |
|
42 | // GET requests:
|
43 | var a1 = Article.get({id: 1});
|
44 | a1.$promise.then(function() {
|
45 | console.log('From cache:', a1);
|
46 | });
|
47 | a1.$httpPromise.then(function() {
|
48 | console.log('From server:', a1);
|
49 | });
|
50 |
|
51 | // POST/PUT/PATCH/DELETE requests:
|
52 | var a2 = new Article({id: 2});
|
53 | a2.title = "This article will be saved eventually...";
|
54 | a2.body = "Even if the browser is offline right now.";
|
55 | a2.$save();
|
56 | a2.$promise.then(function() {
|
57 | console.log('Article was successfully saved.');
|
58 | });
|
59 | ```
|
60 |
|
61 | Read the [tutorial on the Bites from Good Eggs
|
62 | blog](http://bites.goodeggs.com/open_source/angular-cached-resource/).
|
63 |
|
64 | -------
|
65 |
|
66 | ## Installing
|
67 |
|
68 | **Bower:**
|
69 |
|
70 | ```bash
|
71 | bower install angular-cached-resource
|
72 | ```
|
73 |
|
74 | **npm:** (intended for use with [browserify](http://browserify.org/))
|
75 |
|
76 | ```bash
|
77 | npm install angular-cached-resource
|
78 | ```
|
79 |
|
80 | **Manual Download:**
|
81 |
|
82 | - development: [angular-cached-resource.js](https://raw.githubusercontent.com/goodeggs/angular-cached-resource/master/angular-cached-resource.js)
|
83 | - production: [angular-cached-resource.min.js](https://raw.githubusercontent.com/goodeggs/angular-cached-resource/master/angular-cached-resource.min.js)
|
84 |
|
85 | ---
|
86 | ## Usage
|
87 | Provides a factory called `$cachedResource`:
|
88 |
|
89 | ```js
|
90 | $cachedResource(cacheKey, url, [paramDefaults], [actions]);
|
91 | ```
|
92 |
|
93 | ### Arguments
|
94 |
|
95 | - **cacheKey**, `String`<br>
|
96 | An arbitrary key that will uniquely represent this resource in localStorage.
|
97 | When the resource is instanciated, it will check localStorage for any
|
98 |
|
99 | - **url**, `String`<br>
|
100 | Exactly matches the API for the `url` param of the [$resource][ngResource]
|
101 | factory.
|
102 |
|
103 | - **paramDefaults**, `Object`, _(optional)_<br>
|
104 | Exactly matches the API for the `paramDefaults` param of the
|
105 | [$resource][ngResource] factory.
|
106 |
|
107 | - **actions**, `Object`, _optional_<br>
|
108 | Mostly matches the API for the `actions` param of the
|
109 | [$resource][ngResource] factory. Takes an additonal `cache` param (Boolean,
|
110 | default `true`) that determines if this action uses caching.
|
111 |
|
112 | ### Returns
|
113 |
|
114 | A CachedResource "class" object. This is a swap-in replacement for an object
|
115 | created by the `$resource` factory, with the following additional properties:
|
116 |
|
117 | - **Resource.$clearAll(** [options] **)**<br>
|
118 | Clears all items from the cache associated with this resource. Accepts one
|
119 | argument, described below. This mechanism is slated to change in the next
|
120 | minor version of Cached Resource; take a look at
|
121 | [issue #8](https://github.com/goodeggs/angular-cached-resource/issues/8)
|
122 | for more details.
|
123 |
|
124 | - **options**, `Object`, *optional*<br>
|
125 | `options` may contain the following keys:
|
126 | - `exceptFor`, which will limit the resources that are cleared from the
|
127 | cache. `exceptFor` can be an `Array` or an `Object`. If it is an
|
128 | `Array`, the function will remove every cache item except for the
|
129 | ones whose keys match the provided keys. If it is an `Object`, the
|
130 | function will remove every cache item except for those returned by a
|
131 | query that matches the provided parameters.
|
132 | - `clearPendingWrites`, a boolean. Default is `false`. If `true`, then
|
133 | the function will also remove cached instances that have a pending
|
134 | write to the server.
|
135 |
|
136 |
|
137 | In addition, the following properties exist on CachedResource "instance" objects:
|
138 |
|
139 | - **resource.$promise**<br>
|
140 | For GET requests, if anything was already in the cache, this
|
141 | promise is immediately resolved (still asynchronously!) even as the HTTP request
|
142 | continues. Otherwise, this promise is resolved when the HTTP request responds.
|
143 |
|
144 | - **resource.$httpPromise**<br>
|
145 | For all requests, this promise is resolved as soon as the
|
146 | corresponding HTTP request responds.
|
147 |
|
148 | ### Clearing the cache
|
149 |
|
150 | Since there is a 5 megabyte limit on localStorage in most browsers, you'll
|
151 | probably want to actively manage the resource instances that are stored. By
|
152 | default, this module never removes cache entries, so you'll have to do this by
|
153 | hand. Here are the ways that you can accomplish this:
|
154 |
|
155 | - **[localStorage.clear()][localStorageClear]**<br>
|
156 | Removes everything in localStorage. This will not break the behavior of
|
157 | this module, except that it will prevent any pending write from actually
|
158 | occurring.
|
159 |
|
160 | - **$cachedResource.clearAll()**<br>
|
161 | Removes every single Angular Cached Resource cache entry that's currently
|
162 | stored in localStorage. It will leave all cache entries that were not created
|
163 | by this module. (Note: cache entries are namespaced, so if you add anything
|
164 | to localStorage with a key that begins with `cachedResource://`, it will get
|
165 | deleted by this call). It will also leave any resource instances that have a
|
166 | pending write to the server.
|
167 |
|
168 | - **$cachedResource.clearUndefined()**<br>
|
169 | Removes every Angular Cached Resource cache entry corresponding to a resource
|
170 | that has not been defined since the page was loaded. This is useful if your
|
171 | API changes and you want to make sure that old entries are cleared away.
|
172 |
|
173 | - **$cachedResource.clearAll({exceptFor: ['foo', 'bar']})**<br>
|
174 | Removes every Angular Cached Resource entry except for resources with the
|
175 | `foo` or `bar` keys, or resource instances that have a pending write to the
|
176 | server.
|
177 |
|
178 | - **$cachedResource.clearAll({clearPendingWrites: true})**<br>
|
179 | Removes every Angular Cached Resource entry, including those that have a
|
180 | pending write to the server.
|
181 |
|
182 | If you have a "class" object that you've created with `$cachedResource`, then
|
183 | you can also do the following:
|
184 |
|
185 | - **CachedResource.$clearAll()**<br>
|
186 | Removes all entries from the cache associated with this particular resource
|
187 | class, except for resource instances that have a pending write to the server.
|
188 |
|
189 | - **CachedResource.$clearAll({exceptFor: [{id: 1}])**<br>
|
190 | Removes all entries from the cache associated with this particular resource
|
191 | class, except for those with an `id` of 1. (This assumes that
|
192 | `paramDefaults` has an `id` param.)
|
193 |
|
194 | - **CachedResource.$clearAll({exceptFor: {query: 'search string'}})**<br>
|
195 | Removes all entries from the cache except those that were returned by the
|
196 | provided query parameters.
|
197 |
|
198 | - **CachedResource.$clearAll({clearPendingWrites: true})**<br>
|
199 | Removes all instances of CachedResource from the cache, including those that
|
200 | have a pending write to the server.
|
201 |
|
202 | ------
|
203 |
|
204 | ## Details
|
205 |
|
206 | **Asking for a cached resource with `get` or `query` will do the following:**
|
207 |
|
208 | 1. If the request has not been made previously, it will immediately return a
|
209 | `resource` object, just like usual. The request will go through to the
|
210 | server, and when the server responds, the resource will be saved in a
|
211 | localStorage cache.
|
212 |
|
213 | 2. If the request has already been made, it will immediately return a
|
214 | `resource` object that is pre-populated from the cache. The request will
|
215 | still attempt to go through to the server, and if the server responds, the
|
216 | cache entry will be updated.
|
217 |
|
218 | **Updating a CachedResource object will do the following:**
|
219 |
|
220 | 1. Add the resource update action to a queue.
|
221 | 2. Immediately attempt to flush the queue by sending all the network requests
|
222 | in the queue.
|
223 | 3. If a queued network request succeeds, remove it from the queue and resolve
|
224 | the promises on the associated resources (only if the queue entry was made
|
225 | after the page was loaded)
|
226 | 4. If the queue contains requests, attempt to flush it once per minute OR
|
227 | whenever the browser sends a [navigator.onOnline][onOnline] event.
|
228 |
|
229 | **What if localStorage doesn't exist, or if the browser is out of space?**
|
230 |
|
231 | In either of these cases, `$cachedResource` will make sure all of your requests
|
232 | still happen. Things end up working just like the `$resource` module, with
|
233 | none of the caching benefits.
|
234 |
|
235 | ------
|
236 |
|
237 | ## Development
|
238 |
|
239 | Please make sure you run the tests, and add to them unless it's a trivial
|
240 | change. Here is how you can run the tests:
|
241 |
|
242 | ```
|
243 | npm install
|
244 | npm test
|
245 | ```
|
246 |
|
247 | ------
|
248 |
|
249 | ## License
|
250 |
|
251 | [MIT](https://github.com/goodeggs/angular-cached-resource/blob/master/LICENSE.md)
|
252 |
|
253 | [travis-badge]: https://travis-ci.org/goodeggs/angular-cached-resource.svg?branch=master
|
254 | [travis-link]: https://travis-ci.org/goodeggs/angular-cached-resource
|
255 |
|
256 | [angular]: http://angularjs.org/
|
257 | [ngResource]: http://docs.angularjs.org/api/ngResource/service/$resource
|
258 | [localStorage]: http://www.w3.org/TR/webstorage/#the-localstorage-attribute
|
259 | [localStorageClear]: http://www.w3.org/TR/webstorage/#dom-storage-clear
|
260 | [onOnline]: https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine.onLine
|