@angular-libs/signal-storage 0.0.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @angular-libs/signal-storage
|
|
2
|
+
|
|
3
|
+
A simple and robust Angular library for managing state using Angular Signals with built-in persistence to `localStorage` and `sessionStorage`.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the library using npm:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @angular-libs/signal-storage
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Angular Signals Integration**: Seamlessly syncs your storage state with Angular Signals.
|
|
16
|
+
- **Persistent Storage**: Automatically saves to `localStorage` or `sessionStorage`.
|
|
17
|
+
- **Type-safe**: Built with TypeScript to ensure type safety for your stored data.
|
|
18
|
+
- **Lightweight**: Zero external dependencies other than Angular.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Here is a basic example of how to use `@angular-libs/signal-storage` in your Angular components or services.
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { Component, effect, inject } from '@angular/core';
|
|
26
|
+
import { SignalStorageService } from '@angular-libs/signal-storage';
|
|
27
|
+
|
|
28
|
+
// Define your storage state structure using an interface
|
|
29
|
+
interface AppStorage {
|
|
30
|
+
appTheme: 'light' | 'dark';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@Component({
|
|
34
|
+
selector: 'app-root',
|
|
35
|
+
standalone: true,
|
|
36
|
+
template: `
|
|
37
|
+
<div>
|
|
38
|
+
<p>Current Theme: {{ theme() }}</p>
|
|
39
|
+
<button (click)="toggleTheme()">Toggle Theme</button>
|
|
40
|
+
</div>
|
|
41
|
+
`,
|
|
42
|
+
})
|
|
43
|
+
export class AppComponent {
|
|
44
|
+
private storage = inject(SignalStorageService<AppStorage>);
|
|
45
|
+
|
|
46
|
+
// Initialize storage signal with a key and default value
|
|
47
|
+
theme = this.storage.getSignal('appTheme', 'light');
|
|
48
|
+
|
|
49
|
+
constructor() {
|
|
50
|
+
effect(() => {
|
|
51
|
+
console.log('Theme changed to:', this.theme());
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
toggleTheme() {
|
|
56
|
+
// To update, use the storage service's set or update methods
|
|
57
|
+
this.storage.set('appTheme', this.theme() === 'light' ? 'dark' : 'light');
|
|
58
|
+
|
|
59
|
+
// Alternatively, using update:
|
|
60
|
+
// this.storage.update('appTheme', theme => theme === 'light' ? 'dark' : 'light');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
MIT
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { signal } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
class SignalStorageService {
|
|
4
|
+
storage;
|
|
5
|
+
signals = new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Create a new SignalStorageService instance
|
|
8
|
+
* @param storage The storage to use (localStorage or sessionStorage). Defaults to localStorage.
|
|
9
|
+
*/
|
|
10
|
+
constructor(storage = localStorage) {
|
|
11
|
+
this.storage = storage;
|
|
12
|
+
if (typeof window !== 'undefined') {
|
|
13
|
+
window.addEventListener('storage', (event) => {
|
|
14
|
+
if (event.storageArea === this.storage &&
|
|
15
|
+
event.key &&
|
|
16
|
+
this.signals.has(event.key)) {
|
|
17
|
+
try {
|
|
18
|
+
const newValue = event.newValue ? JSON.parse(event.newValue) : null;
|
|
19
|
+
this.signals.get(event.key).set(newValue);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Ignore parse errors from external changes
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
get(key, defaultValue) {
|
|
29
|
+
const item = this.storage.getItem(key);
|
|
30
|
+
if (item === null) {
|
|
31
|
+
return defaultValue ?? null;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(item);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return defaultValue ?? null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
getSignal(key, defaultValue) {
|
|
41
|
+
if (!this.signals.has(key)) {
|
|
42
|
+
const initialValue = this.get(key, defaultValue);
|
|
43
|
+
this.signals.set(key, signal(initialValue));
|
|
44
|
+
}
|
|
45
|
+
return this.signals.get(key).asReadonly();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Set typed data in localStorage
|
|
49
|
+
* @param key The key to set
|
|
50
|
+
* @param value The value to store
|
|
51
|
+
*/
|
|
52
|
+
set(key, value) {
|
|
53
|
+
try {
|
|
54
|
+
this.storage.setItem(key, JSON.stringify(value));
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
console.error(`Error saving to storage for key "${String(key)}". Storage quota may be exceeded.`, e);
|
|
58
|
+
}
|
|
59
|
+
if (this.signals.has(key)) {
|
|
60
|
+
this.signals.get(key).set(value);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Update typed data based on current value using a callback
|
|
65
|
+
* @param key The key to update
|
|
66
|
+
* @param updateFn Callback function that receives the current value and returns the new value
|
|
67
|
+
*/
|
|
68
|
+
update(key, updateFn) {
|
|
69
|
+
const currentValue = this.get(key);
|
|
70
|
+
const newValue = updateFn(currentValue);
|
|
71
|
+
this.set(key, newValue);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Remove an item from localStorage
|
|
75
|
+
* @param key The key to remove
|
|
76
|
+
*/
|
|
77
|
+
remove(key) {
|
|
78
|
+
this.storage.removeItem(key);
|
|
79
|
+
if (this.signals.has(key)) {
|
|
80
|
+
this.signals.get(key).set(null);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if a key exists in localStorage
|
|
85
|
+
* @param key The key to check
|
|
86
|
+
* @returns true if the key exists, false otherwise
|
|
87
|
+
*/
|
|
88
|
+
has(key) {
|
|
89
|
+
return this.storage.getItem(key) !== null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Clear all localStorage
|
|
93
|
+
*/
|
|
94
|
+
clear() {
|
|
95
|
+
this.storage.clear();
|
|
96
|
+
for (const sig of this.signals.values()) {
|
|
97
|
+
sig.set(null);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/*
|
|
103
|
+
* Public API Surface of signal-storage
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Generated bundle index. Do not edit.
|
|
108
|
+
*/
|
|
109
|
+
|
|
110
|
+
export { SignalStorageService };
|
|
111
|
+
//# sourceMappingURL=angular-libs-signal-storage.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"angular-libs-signal-storage.mjs","sources":["../../../../projects/angular-libs/signal-storage/src/lib/signal-storage.ts","../../../../projects/angular-libs/signal-storage/src/public-api.ts","../../../../projects/angular-libs/signal-storage/src/angular-libs-signal-storage.ts"],"sourcesContent":["import { signal, WritableSignal, Signal } from '@angular/core';\n\nexport class SignalStorageService<T extends Record<string, any> = {}> {\n private storage: Storage;\n private signals = new Map<keyof T, WritableSignal<any>>();\n\n /**\n * Create a new SignalStorageService instance\n * @param storage The storage to use (localStorage or sessionStorage). Defaults to localStorage.\n */\n constructor(storage: Storage = localStorage) {\n this.storage = storage;\n if (typeof window !== 'undefined') {\n window.addEventListener('storage', (event: StorageEvent) => {\n if (\n event.storageArea === this.storage &&\n event.key &&\n this.signals.has(event.key as keyof T)\n ) {\n try {\n const newValue = event.newValue ? JSON.parse(event.newValue) : null;\n this.signals.get(event.key as keyof T)!.set(newValue);\n } catch {\n // Ignore parse errors from external changes\n }\n }\n });\n }\n }\n /**\n * Get typed data from SignalStorageService\n * @param key The key to retrieve\n * @returns The typed data or null if not found\n */\n get<K extends keyof T>(key: K): T[K] | null;\n /**\n * Get typed data from SignalStorageService with a default value\n * @param key The key to retrieve\n * @param defaultValue The default value to return if key not found\n * @returns The stored data or the default value\n */\n get<K extends keyof T>(key: K, defaultValue: T[K]): T[K];\n get<K extends keyof T>(key: K, defaultValue?: T[K]): T[K] | null {\n const item = this.storage.getItem(key as string);\n if (item === null) {\n return defaultValue ?? null;\n }\n try {\n return JSON.parse(item) as T[K];\n } catch {\n return defaultValue ?? null;\n }\n }\n\n /**\n * Get a reactive Angular signal for a key\n * @param key The key to retrieve\n * @param defaultValue The default value to return if key not found\n * @returns A read-only Signal containing the stored data\n */\n getSignal<K extends keyof T>(key: K): Signal<T[K] | null>;\n getSignal<K extends keyof T>(key: K, defaultValue: T[K]): Signal<T[K]>;\n getSignal<K extends keyof T>(key: K, defaultValue?: T[K]): Signal<any> {\n if (!this.signals.has(key)) {\n const initialValue = this.get(key, defaultValue as T[K]);\n this.signals.set(key, signal(initialValue));\n }\n return this.signals.get(key)!.asReadonly();\n }\n\n /**\n * Set typed data in localStorage\n * @param key The key to set\n * @param value The value to store\n */\n set<K extends keyof T>(key: K, value: T[K]): void {\n try {\n this.storage.setItem(key as string, JSON.stringify(value));\n } catch (e) {\n console.error(\n `Error saving to storage for key \"${String(key)}\". Storage quota may be exceeded.`,\n e,\n );\n }\n if (this.signals.has(key)) {\n this.signals.get(key)!.set(value);\n }\n }\n\n /**\n * Update typed data based on current value using a callback\n * @param key The key to update\n * @param updateFn Callback function that receives the current value and returns the new value\n */\n update<K extends keyof T>(key: K, updateFn: (currentValue: T[K] | null) => T[K]): void {\n const currentValue = this.get(key);\n const newValue = updateFn(currentValue);\n this.set(key, newValue);\n }\n\n /**\n * Remove an item from localStorage\n * @param key The key to remove\n */\n remove<K extends keyof T>(key: K): void {\n this.storage.removeItem(key as string);\n if (this.signals.has(key)) {\n this.signals.get(key)!.set(null);\n }\n }\n\n /**\n * Check if a key exists in localStorage\n * @param key The key to check\n * @returns true if the key exists, false otherwise\n */\n has<K extends keyof T>(key: K): boolean {\n return this.storage.getItem(key as string) !== null;\n }\n\n /**\n * Clear all localStorage\n */\n clear(): void {\n this.storage.clear();\n for (const sig of this.signals.values()) {\n sig.set(null);\n }\n }\n}\n","/*\n * Public API Surface of signal-storage\n */\n\nexport * from './lib/signal-storage';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@angular-libs/signal-storage",
|
|
3
|
+
"version": "0.0.1-beta.0",
|
|
4
|
+
"peerDependencies": {
|
|
5
|
+
"@angular/common": ">=18.0.0",
|
|
6
|
+
"@angular/core": ">=18.0.0"
|
|
7
|
+
},
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"tslib": "^2.3.0"
|
|
13
|
+
},
|
|
14
|
+
"sideEffects": false,
|
|
15
|
+
"module": "fesm2022/angular-libs-signal-storage.mjs",
|
|
16
|
+
"typings": "types/angular-libs-signal-storage.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
"./package.json": {
|
|
19
|
+
"default": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./types/angular-libs-signal-storage.d.ts",
|
|
23
|
+
"default": "./fesm2022/angular-libs-signal-storage.mjs"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Signal } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
declare class SignalStorageService<T extends Record<string, any> = {}> {
|
|
4
|
+
private storage;
|
|
5
|
+
private signals;
|
|
6
|
+
/**
|
|
7
|
+
* Create a new SignalStorageService instance
|
|
8
|
+
* @param storage The storage to use (localStorage or sessionStorage). Defaults to localStorage.
|
|
9
|
+
*/
|
|
10
|
+
constructor(storage?: Storage);
|
|
11
|
+
/**
|
|
12
|
+
* Get typed data from SignalStorageService
|
|
13
|
+
* @param key The key to retrieve
|
|
14
|
+
* @returns The typed data or null if not found
|
|
15
|
+
*/
|
|
16
|
+
get<K extends keyof T>(key: K): T[K] | null;
|
|
17
|
+
/**
|
|
18
|
+
* Get typed data from SignalStorageService with a default value
|
|
19
|
+
* @param key The key to retrieve
|
|
20
|
+
* @param defaultValue The default value to return if key not found
|
|
21
|
+
* @returns The stored data or the default value
|
|
22
|
+
*/
|
|
23
|
+
get<K extends keyof T>(key: K, defaultValue: T[K]): T[K];
|
|
24
|
+
/**
|
|
25
|
+
* Get a reactive Angular signal for a key
|
|
26
|
+
* @param key The key to retrieve
|
|
27
|
+
* @param defaultValue The default value to return if key not found
|
|
28
|
+
* @returns A read-only Signal containing the stored data
|
|
29
|
+
*/
|
|
30
|
+
getSignal<K extends keyof T>(key: K): Signal<T[K] | null>;
|
|
31
|
+
getSignal<K extends keyof T>(key: K, defaultValue: T[K]): Signal<T[K]>;
|
|
32
|
+
/**
|
|
33
|
+
* Set typed data in localStorage
|
|
34
|
+
* @param key The key to set
|
|
35
|
+
* @param value The value to store
|
|
36
|
+
*/
|
|
37
|
+
set<K extends keyof T>(key: K, value: T[K]): void;
|
|
38
|
+
/**
|
|
39
|
+
* Update typed data based on current value using a callback
|
|
40
|
+
* @param key The key to update
|
|
41
|
+
* @param updateFn Callback function that receives the current value and returns the new value
|
|
42
|
+
*/
|
|
43
|
+
update<K extends keyof T>(key: K, updateFn: (currentValue: T[K] | null) => T[K]): void;
|
|
44
|
+
/**
|
|
45
|
+
* Remove an item from localStorage
|
|
46
|
+
* @param key The key to remove
|
|
47
|
+
*/
|
|
48
|
+
remove<K extends keyof T>(key: K): void;
|
|
49
|
+
/**
|
|
50
|
+
* Check if a key exists in localStorage
|
|
51
|
+
* @param key The key to check
|
|
52
|
+
* @returns true if the key exists, false otherwise
|
|
53
|
+
*/
|
|
54
|
+
has<K extends keyof T>(key: K): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Clear all localStorage
|
|
57
|
+
*/
|
|
58
|
+
clear(): void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { SignalStorageService };
|