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.$clearCache(** [options] **)**<br>
|
118 | Clears all items from the cache associated with this resource. Accepts one
|
119 | argument, described below.
|
120 |
|
121 | - **options**, `Object`, *optional*<br>
|
122 | `options` may contain the following keys:
|
123 | - `where`, which will limit the resources that are cleared from the cache
|
124 | to only those whose keys are explicitly listed. `where` can be an `Array`
|
125 | or an `Object`. If it is an `Object`, it will be treated like an `Array`
|
126 | containing only the provided `Object`. The `Array` should contain `Objects`
|
127 | representing cache keys that should be removed. If `where` is provided,
|
128 | `exceptFor` must not be provided.
|
129 | - `exceptFor`, which will limit the resources that are cleared from the
|
130 | cache to all resources except for those whose keys are explicitly listed.
|
131 | Just like `where`, `exceptFor` can be an `Array` or an `Object`. If it is an
|
132 | `Object`, it will be treated like an `Array` containing only the provided
|
133 | `Object`. The `Array` should contain `Object`s representing cache keys that
|
134 | should be kept. If `exceptFor` is provided, `where` must not be provided.
|
135 | - `isArray`, a boolean. Default is `false`. If `true`, then the function will
|
136 | treat the `where` or `exceptFor` arguments as referring to `Array` cache key.
|
137 | - `clearChildren`, a boolean. Default is `false`. If `true`, and `isArray` is
|
138 | also `true`, then the function will clear the `Array` cache entry (or entries)
|
139 | as well as all of the instances that the `Array` points to.
|
140 | - `clearPendingWrites`, a boolean. Default is `false`. If `true`, then
|
141 | the function will also remove cached instances that have a pending
|
142 | write to the server.
|
143 |
|
144 |
|
145 | In addition, the following properties exist on CachedResource "instance" objects:
|
146 |
|
147 | - **resource.$promise**<br>
|
148 | For GET requests, if anything was already in the cache, this
|
149 | promise is immediately resolved (still asynchronously!) even as the HTTP request
|
150 | continues. Otherwise, this promise is resolved when the HTTP request responds.
|
151 |
|
152 | - **resource.$httpPromise**<br>
|
153 | For all requests, this promise is resolved as soon as the
|
154 | corresponding HTTP request responds.
|
155 |
|
156 | ### Clearing the cache
|
157 |
|
158 | Since there is a 5 megabyte limit on localStorage in most browsers, you'll
|
159 | probably want to actively manage the resource instances that are stored. By
|
160 | default, this module never removes cache entries, so you'll have to do this by
|
161 | hand. Here are the ways that you can accomplish this:
|
162 |
|
163 | - **[localStorage.clear()][localStorageClear]**<br>
|
164 | Removes everything in localStorage. This will not break the behavior of
|
165 | this module, except that it will prevent any pending write from actually
|
166 | occurring.
|
167 |
|
168 | - **$cachedResource.clearCache()**<br>
|
169 | Removes every single Angular Cached Resource cache entry that's currently
|
170 | stored in localStorage. It will leave all cache entries that were not created
|
171 | by this module. (Note: cache entries are namespaced, so if you add anything
|
172 | to localStorage with a key that begins with `cachedResource://`, it will get
|
173 | deleted by this call). It will also leave any resource instances that have a
|
174 | pending write to the server.
|
175 |
|
176 | - **$cachedResource.clearUndefined()**<br>
|
177 | Removes every Angular Cached Resource cache entry corresponding to a resource
|
178 | that has not been defined since the page was loaded. This is useful if your
|
179 | API changes and you want to make sure that old entries are cleared away.
|
180 |
|
181 | - **$cachedResource.clearCache({exceptFor: ['foo', 'bar']})**<br>
|
182 | Removes every Angular Cached Resource entry except for resources with the
|
183 | `foo` or `bar` keys, or resource instances that have a pending write to the
|
184 | server.
|
185 |
|
186 | - **$cachedResource.clearCache({clearPendingWrites: true})**<br>
|
187 | Removes every Angular Cached Resource entry, including those that have a
|
188 | pending write to the server.
|
189 |
|
190 | If you have a "class" object that you've created with `$cachedResource`, then
|
191 | you can also do the following:
|
192 |
|
193 | - **CachedResource.$clearCache()**<br>
|
194 | Removes all entries from the cache associated with this particular resource
|
195 | class, except for resource instances that have a pending write to the server.
|
196 |
|
197 | - **CachedResource.$clearCache({where: [{id: 1}, {id: 2}])**<br>
|
198 | Removes two entries from the cache associated with this particular resource
|
199 | class; the ones with an `id` of 1 and 2. (This assumes that `paramDefaults`
|
200 | has an `id` param.)
|
201 |
|
202 | - **CachedResource.$clearCache({exceptFor: {id: 1})**<br>
|
203 | Removes all entries from the cache associated with this particular resource
|
204 | class, except for those with an `id` of 1. (This assumes that
|
205 | `paramDefaults` has an `id` param.)
|
206 |
|
207 | - **CachedResource.$clearCache({exceptFor: {query: 'search string'}, isArray: true})**<br>
|
208 | Removes all entries from the cache except those that were returned by the
|
209 | provided query parameters.
|
210 |
|
211 | - **CachedResource.$clearCache({clearPendingWrites: true})**<br>
|
212 | Removes all instances of CachedResource from the cache, including those that
|
213 | have a pending write to the server.
|
214 |
|
215 | ------
|
216 |
|
217 | ## Details
|
218 |
|
219 | **Asking for a cached resource with `get` or `query` will do the following:**
|
220 |
|
221 | 1. If the request has not been made previously, it will immediately return a
|
222 | `resource` object, just like usual. The request will go through to the
|
223 | server, and when the server responds, the resource will be saved in a
|
224 | localStorage cache.
|
225 |
|
226 | 2. If the request has already been made, it will immediately return a
|
227 | `resource` object that is pre-populated from the cache. The request will
|
228 | still attempt to go through to the server, and if the server responds, the
|
229 | cache entry will be updated.
|
230 |
|
231 | **Updating a CachedResource object will do the following:**
|
232 |
|
233 | 1. Add the resource update action to a queue.
|
234 | 2. Immediately attempt to flush the queue by sending all the network requests
|
235 | in the queue.
|
236 | 3. If a queued network request succeeds, remove it from the queue and resolve
|
237 | the promises on the associated resources (only if the queue entry was made
|
238 | after the page was loaded)
|
239 | 4. If the queue contains requests, attempt to flush it once per minute OR
|
240 | whenever the browser sends a [navigator.onOnline][onOnline] event.
|
241 |
|
242 | **What if localStorage doesn't exist, or if the browser is out of space?**
|
243 |
|
244 | In either of these cases, `$cachedResource` will make sure all of your requests
|
245 | still happen. Things end up working just like the `$resource` module, with
|
246 | none of the caching benefits.
|
247 |
|
248 | ------
|
249 |
|
250 | ## Development
|
251 |
|
252 | Please make sure you run the tests, and add to them unless it's a trivial
|
253 | change. Here is how you can run the tests:
|
254 |
|
255 | ```
|
256 | npm install
|
257 | npm test
|
258 | ```
|
259 |
|
260 | ------
|
261 |
|
262 | ## License
|
263 |
|
264 | [MIT](https://github.com/goodeggs/angular-cached-resource/blob/master/LICENSE.md)
|
265 |
|
266 | [travis-badge]: https://travis-ci.org/goodeggs/angular-cached-resource.svg?branch=master
|
267 | [travis-link]: https://travis-ci.org/goodeggs/angular-cached-resource
|
268 |
|
269 | [angular]: http://angularjs.org/
|
270 | [ngResource]: http://docs.angularjs.org/api/ngResource/service/$resource
|
271 | [localStorage]: http://www.w3.org/TR/webstorage/#the-localstorage-attribute
|
272 | [localStorageClear]: http://www.w3.org/TR/webstorage/#dom-storage-clear
|
273 | [onOnline]: https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine.onLine
|