Skip to content

Commit 99c8ce8

Browse files
authored
feat: separate localStorage and Map APIs (#100)
* feat: separate localStorage and Map APIs * feat: iterating keys()
1 parent e8b2aa9 commit 99c8ce8

36 files changed

+1703
-832
lines changed

README.md

Lines changed: 127 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,48 @@
11
# Async local storage for Angular
22

3-
Efficient local storage module for Angular apps and Progressive Wep apps (PWA):
4-
- **simplicity**: based on native `localStorage` API, with automatic JSON stringify/parse,
5-
- **perfomance**: internally stored via the asynchronous `IndexedDB` API,
6-
- **Angular-like**: wrapped in RxJS `Observables`,
3+
Efficient client-side storage module for Angular apps and Progressive Wep Apps (PWA):
4+
- **simplicity**: based on native `localStorage` API,
5+
- **perfomance**: internally stored via the asynchronous `indexedDB` API,
6+
- **Angular-like**: wrapped in RxJS `Observable`s,
77
- **security**: validate data with a JSON Schema,
88
- **compatibility**: works around some browsers issues,
99
- **documentation**: API fully explained, and a changelog!
1010
- **maintenance**: the lib follows Angular LTS and anticipates the next Angular version,
11-
- **reference**: 1st Angular library for local storage according to [ngx.tools](https://ngx.tools/#/search?q=local%20storage).
11+
- **reference**: 1st Angular library for client-side storage according to [ngx.tools](https://ngx.tools/#/search?q=local%20storage).
1212

1313
## By the same author
1414

1515
- [Angular schematics extension for VS Code](https://marketplace.visualstudio.com/items?itemName=cyrilletuzi.angular-schematics) (GUI for Angular CLI commands)
16-
- Other Angular libraries: [@ngx-pwa/offline](https://github.com/cyrilletuzi/ngx-pwa-offline) and [@ngx-pwa/ngsw-schema](https://github.com/cyrilletuzi/ngsw-schema)
16+
- Other Angular library: [@ngx-pwa/offline](https://github.com/cyrilletuzi/ngx-pwa-offline)
1717
- Popular [Angular posts on Medium](https://medium.com/@cyrilletuzi)
1818
- Follow updates of this lib on [Twitter](https://twitter.com/cyrilletuzi)
1919
- **[Angular onsite trainings](https://formationjavascript.com/formation-angular/)** (based in Paris, so the website is in French, but [my English bio is here](https://www.cyrilletuzi.com/en/web/) and I'm open to travel)
2020

2121
## Why this module?
2222

23-
For now, Angular does not provide a local storage module, and almost every app needs some local storage.
23+
For now, Angular does not provide a client-side storage module, and almost every app needs some client-side storage.
2424
There are 2 native JavaScript APIs available:
2525
- [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Storage/LocalStorage)
26-
- [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API)
26+
- [indexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API)
2727

2828
The `localStorage` API is simple to use but synchronous, so if you use it too often,
2929
your app will soon begin to freeze.
3030

31-
The `IndexedDB` API is asynchronous and efficient, but it's a mess to use:
32-
you'll soon be caught by the callback hell, as it does not support Promises yet.
31+
The `indexedDB` API is asynchronous and efficient, but it's a mess to use:
32+
you'll soon be caught by the callback hell, as it does not support `Promise`s yet.
3333

34-
Mozilla has done a very great job with the [localForage library](http://localforage.github.io/localForage/):
34+
Mozilla has done a very great job with the [`localForage` library](http://localforage.github.io/localForage/):
3535
a simple API based on native `localStorage`,
36-
but internally stored via the asynchronous `IndexedDB` for performance.
36+
but internally stored via the asynchronous `indexedDB` for performance.
3737
But it's built in ES5 old school way and then it's a mess to include into Angular.
3838

39-
This module is based on the same idea as localForage, but in ES6
40-
and additionally wrapped into [RxJS Observables](http://reactivex.io/rxjs/)
39+
This module is based on the same idea as `localForage`, but built in ES6+
40+
and additionally wrapped into [RxJS `Observable`s](http://reactivex.io/rxjs/)
4141
to be homogeneous with other Angular modules.
4242

4343
## Getting started
4444

45-
Install the same version as your Angular one via [npm](http://npmjs.com):
45+
Install the right version according to your Angular one via [`npm`](http://npmjs.com):
4646

4747
```bash
4848
# For Angular 7 & 8:
@@ -52,7 +52,19 @@ npm install @ngx-pwa/local-storage@next
5252
npm install @ngx-pwa/local-storage@6
5353
```
5454

55-
Now you just have to inject the service where you need it:
55+
### Upgrading
56+
57+
If you still use the old `angular-async-local-storage` package, or to update to new versions,
58+
see the **[migration guides](./MIGRATION.md).**
59+
60+
Versions 4 & 5, which are *not* supported anymore,
61+
needed an additional setup step explained in [the old module guide](./docs/OLD_MODULE.md).
62+
63+
## API
64+
65+
2 services are available for client-side storage, you just have to inject one of them were you need it.
66+
67+
### `LocalStorage`
5668

5769
```typescript
5870
import { LocalStorage } from '@ngx-pwa/local-storage';
@@ -65,77 +77,131 @@ export class YourService {
6577
}
6678
```
6779

68-
Versions 4 & 5 (only) need an additional setup step explained in [the old module guide](./docs/OLD_MODULE.md).
80+
This service API follows the
81+
[native `localStorage` API](https://developer.mozilla.org/en-US/docs/Web/API/Storage/LocalStorage),
82+
except it's asynchronous via [RxJS `Observable`s](http://reactivex.io/rxjs/):
6983

70-
### Upgrading
84+
```typescript
85+
class LocalStorage {
86+
length: Observable<number>;
87+
getItem(index: string, schema?: JSONSchema): Observable<unknown> {}
88+
setItem(index: string, value: any): Observable<true> {}
89+
removeItem(index: string): Observable<true> {}
90+
clear(): Observable<true> {}
91+
}
92+
```
7193

72-
If you still use the old `angular-async-local-storage` package, or to update to new versions,
73-
see the **[migration guides](./MIGRATION.md).**
94+
### `StorageMap`
7495

75-
## API
96+
```typescript
97+
import { StorageMap } from '@ngx-pwa/local-storage';
7698

77-
The API follows the [native localStorage API](https://developer.mozilla.org/en-US/docs/Web/API/Storage/LocalStorage),
78-
except it's asynchronous via [RxJS Observables](http://reactivex.io/rxjs/).
99+
@Injectable()
100+
export class YourService {
101+
102+
constructor(private storageMap: StorageMap) {}
103+
104+
}
105+
```
106+
107+
New since version 8 of this lib, this service API follows the
108+
[native `Map` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map),
109+
except it's asynchronous via [RxJS `Observable`s](http://reactivex.io/rxjs/).
110+
111+
It does the same thing as the `LocalStorage` service, but also allows more advanced operations.
112+
If you are familiar to `Map`, we recommend to use only this service.
113+
114+
```typescript
115+
class StorageMap {
116+
size: Observable<number>;
117+
get(index: string, schema?: JSONSchema): Observable<unknown> {}
118+
set(index: string, value: any): Observable<undefined> {}
119+
delete(index: string): Observable<undefined> {}
120+
clear(): Observable<undefined> {}
121+
has(index: string): Observable<boolean> {}
122+
keys(): Observable<string> {}
123+
}
124+
```
125+
126+
## How to
127+
128+
The following examples will show the 2 services for basic operations,
129+
then stick to the `StorageMap` API. But except for methods which are specific to `StorageMap`,
130+
you can always do the same with the `LocalStorage` API.
79131

80132
### Writing data
81133

82134
```typescript
83135
let user: User = { firstName: 'Henri', lastName: 'Bergson' };
84136

137+
this.storageMap.set('user', user).subscribe(() => {});
138+
// or
85139
this.localStorage.setItem('user', user).subscribe(() => {});
86140
```
87141

88142
You can store any value, without worrying about serializing. But note that:
89-
- storing `null` or `undefined` can cause issues in some browsers, so the item will be removed instead,
90-
- you should stick to JSON data, ie. primitive types, arrays and literal objects.
143+
- storing `null` or `undefined` makes no sense and can cause issues in some browsers, so the item will be removed instead,
144+
- you should stick to JSON data, ie. primitive types, arrays and *literal* objects.
91145
`Map`, `Set`, `Blob` and other special structures can cause issues in some scenarios.
92146
See the [serialization guide](./docs/SERIALIZATION.md) for more details.
93147

94148
### Deleting data
95149

96150
To delete one item:
97151
```typescript
152+
this.storageMap.delete('user').subscribe(() => {});
153+
// or
98154
this.localStorage.removeItem('user').subscribe(() => {});
99155
```
100156

101157
To delete all items:
102158
```typescript
159+
this.storageMap.clear().subscribe(() => {});
160+
// or
103161
this.localStorage.clear().subscribe(() => {});
104162
```
105163

106164
### Reading data
107165

108166
```typescript
167+
this.storageMap.get<User>('user').subscribe((user) => {
168+
console.log(user);
169+
});
170+
// or
109171
this.localStorage.getItem<User>('user').subscribe((user) => {
110-
user.firstName; // should be 'Henri'
172+
console.log(user);
111173
});
112174
```
113175

114-
Not finding an item is not an error, it succeeds but returns `null`.
176+
Not finding an item is not an error, it succeeds but returns:
177+
- `undefined` with `StorageMap`
178+
```typescript
179+
this.storageMap.get('notexisting').subscribe((data) => {
180+
data; // undefined
181+
});
182+
```
183+
- `null` with `LocalStorage`
115184
```typescript
116185
this.localStorage.getItem('notexisting').subscribe((data) => {
117186
data; // null
118187
});
119188
```
120189

121-
If you tried to store `undefined`, you'll get `null` too, as some storages don't allow `undefined`.
122-
123-
Note you'll only get *one* value: the `Observable` is here for asynchronicity but is not meant to
190+
Note you'll only get *one* value: the `Observable` is here for asynchrony but is not meant to
124191
emit again when the stored data is changed. And it's normal: if app data change, it's the role of your app
125192
to keep track of it, not of this lib. See [#16](https://github.com/cyrilletuzi/angular-async-local-storage/issues/16)
126193
for more context and [#4](https://github.com/cyrilletuzi/angular-async-local-storage/issues/4)
127194
for an example.
128195

129196
### Checking data
130197

131-
Don't forget it's client-side storage: **always check the data**, as it could have been forged or deleted.
198+
Don't forget it's client-side storage: **always check the data**, as it could have been forged.
132199

133200
You can use a [JSON Schema](http://json-schema.org/) to validate the data.
134201

135202
```typescript
136-
this.localStorage.getItem('test', { type: 'string' })
137-
.subscribe({
138-
next: (user) => { /* Called if data is valid or `null` */ },
203+
this.storageMap.get('test', { type: 'string' }).subscribe({
204+
next: (user) => { /* Called if data is valid or `undefined` or `null` */ },
139205
error: (error) => { /* Called if data is invalid */ },
140206
});
141207
```
@@ -144,15 +210,16 @@ See the [full validation guide](./docs/VALIDATION.md) to see how to validate all
144210

145211
### Subscription
146212

147-
You *DO NOT* need to unsubscribe: the `Observable` autocompletes (like in the `HttpClient` service).
213+
You *DO NOT* need to unsubscribe: the `Observable` autocompletes (like in the Angular `HttpClient` service).
148214

149-
But **you *DO* need to subscribe**, even if you don't have something specific to do after writing in local storage (because it's how RxJS Observables work).
215+
But **you *DO* need to subscribe**, even if you don't have something specific to do after writing in storage
216+
(because it's how RxJS `Observable`s work).
150217

151218
### Errors
152219

153220
As usual, it's better to catch any potential error:
154221
```typescript
155-
this.localStorage.setItem('color', 'red').subscribe({
222+
this.storageMap.set('color', 'red').subscribe({
156223
next: () => {},
157224
error: (error) => {},
158225
});
@@ -163,8 +230,8 @@ For read operations, you can also manage errors by providing a default value:
163230
import { of } from 'rxjs';
164231
import { catchError } from 'rxjs/operators';
165232

166-
this.localStorage.getItem('color').pipe(
167-
catchError(() => of('red'))
233+
this.storageMap.get('color').pipe(
234+
catchError(() => of('red')),
168235
).subscribe((result) => {});
169236
```
170237

@@ -177,27 +244,25 @@ Could only happen when in `localStorage` fallback:
177244

178245
Should only happen if data was corrupted or modified from outside of the lib:
179246
- `.getItem()`: data invalid against your JSON schema (`ValidationError` from this lib)
180-
- any method when in `indexedDB`: database store has been deleted (`DOMException` with name `'NotFoundError'`)
247+
- any method when in `indexedDB`: database store has been deleted (`DOMException` with name `NotFoundError`)
181248

182249
Other errors are supposed to be catched or avoided by the lib,
183250
so if you were to run into an unlisted error, please file an issue.
184251

185252
### `Map`-like operations
186253

187-
Starting *with version >= 7.4*, in addition to the classic `localStorage`-like API,
188-
this lib also provides some `Map`-like methods for advanced operations:
189-
- `.keys()` method
190-
- `.has(key)` method
191-
- `.size` property
254+
Starting *with version >= 8* of this lib, in addition to the classic `localStorage`-like API,
255+
this lib also provides a `Map`-like API for advanced operations:
256+
- `.keys()`
257+
- `.has(key)`
258+
- `.size`
192259

193260
See the [documentation](./docs/MAP_OPERATIONS.md) for more info and some recipes.
261+
For example, it allows to implement a multiple databases scenario.
194262

195-
### Collision
196-
197-
If you have multiple apps on the same *sub*domain *and* you don't want to share data between them,
198-
see the [prefix guide](./docs/COLLISION.md).
263+
## Support
199264

200-
## Angular support
265+
### Angular support
201266

202267
We follow [Angular LTS support](https://angular.io/guide/releases),
203268
meaning we support Angular >= 6, until November 2019.
@@ -207,22 +272,27 @@ This module supports [AoT pre-compiling](https://angular.io/guide/aot-compiler).
207272
This module supports [Universal server-side rendering](https://github.com/angular/universal)
208273
via a mock storage.
209274

210-
## Browser support
275+
### Browser support
211276

212-
[All browsers supporting IndexedDB](http://caniuse.com/#feat=indexeddb), ie. **all current browsers** :
277+
[All browsers supporting IndexedDB](https://caniuse.com/#feat=indexeddb), ie. **all current browsers** :
213278
Firefox, Chrome, Opera, Safari, Edge, and IE10+.
214279

215280
See [the browsers support guide](./docs/BROWSERS_SUPPORT.md) for more details and special cases (like private browsing).
216281

217-
## Interoperability
282+
### Collision
283+
284+
If you have multiple apps on the same *sub*domain *and* you don't want to share data between them,
285+
see the [prefix guide](./docs/COLLISION.md).
286+
287+
### Interoperability
218288

219-
For interoperability when mixing this lib with direct usage of native APIs or other libs like `localforage`
220-
(which doesn't make sense in most of cases),
289+
For interoperability when mixing this lib with direct usage of native APIs or other libs like `localForage`
290+
(which doesn't make sense in most cases),
221291
see the [interoperability documentation](./docs/INTEROPERABILITY.md).
222292

223-
## Changelog
293+
### Changelog
224294

225-
[Changelog available here](https://github.com/cyrilletuzi/angular-async-local-storage/blob/master/CHANGELOG.md), and [migration guides here](./MIGRATION.md).
295+
[Changelog available here](./CHANGELOG.md), and [migration guides here](./MIGRATION.md).
226296

227297
## License
228298

docs/BROWSERS_SUPPORT.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,32 @@
11
# Browser support guide
22

3-
This lib supports [all browsers supporting IndexedDB](http://caniuse.com/#feat=indexeddb), ie. **all current browsers** :
3+
This lib supports [all browsers supporting `indexedDB`](http://caniuse.com/#feat=indexeddb), ie. **all current browsers** :
44
Firefox, Chrome, Opera, Safari, Edge, and IE10+.
55

6-
Local storage is required only for apps, and given that you won't do an app in older browsers,
6+
Client-side storage is required only for apps, and given that you won't do an app in older browsers,
77
current browsers support is far enough.
88

9-
Even so, IE9 is supported but use native localStorage as a fallback,
9+
Even so, IE9 is supported but use native `localStorage` as a fallback,
1010
so internal operations are synchronous (the public API remains asynchronous-like).
1111

12-
This module is not impacted by IE/Edge missing IndexedDB features.
12+
This module is not impacted by IE/Edge missing `indexedDB` features.
1313

1414
It also works in tools based on browser engines (like Electron) but not in non-browser tools (like NativeScript, see
1515
[#11](https://github.com/cyrilletuzi/angular-async-local-storage/issues/11)).
1616

1717
## Browsers restrictions
1818

19-
Be aware that local storage is limited in browsers when in private / incognito modes. Most browsers will delete the data when the private browsing session ends.
20-
It's not a real issue as local storage is useful for apps, and apps should not be in private mode.
19+
Be aware that `indexedDB` usage is limited in browsers when in private / incognito modes.
20+
Most browsers will delete the data when the private browsing session ends.
21+
It's not a real issue as client-side storage is only useful for apps, and apps should not be in private mode.
2122

2223
In some scenarios, `indexedDB` is not available, so the lib fallbacks to (synchronous) `localStorage`. It happens in:
2324
- Firefox private mode (see [#26](https://github.com/cyrilletuzi/angular-async-local-storage/issues/26))
2425
- IE/Edge private mode
2526
- Safari, when in a cross-origin iframe (see
2627
[#42](https://github.com/cyrilletuzi/angular-async-local-storage/issues/42))
2728

29+
If these scenarios are a concern for you, it impacts what you can store.
30+
See the [serialization guide](./SERIALIZATION.md) for full details.
31+
2832
[Back to general documentation](../README.md)

docs/COLLISION.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
# Collision guide
22

3-
`localStorage` and `IndexedDB` are restricted to the same ***sub*domain**, so no risk of collision in most cases.
3+
**All client-side storages (both `indexedDB` and `localStorage`) are restricted to the same *sub*domain**,
4+
so there is no risk of collision in most cases.
45
*Only* if you have multiple apps on the same *sub*domain *and* you don't want to share data between them,
5-
you need to change the config.
6+
you need to add a prefix.
67

78
## Version
89

9-
This is the up to date guide about validation for version >= 8.
10-
The old guide for validation in versions < 8 is available [here](./COLLISION_BEFORE_V8.md).
10+
**This is the up to date guide about collision for version >= 8 of this lib.**
11+
12+
The old guide about collision in versions < 8 is available [here](./COLLISION_BEFORE_V8.md),
13+
but is not recommended as there was breaking changes in v8.
1114

1215
## Configuration
1316

0 commit comments

Comments
 (0)