Skip to content

Commit f73afeb

Browse files
committed
feat(with-storage-sync): add reset functionality and improve storage handling
1 parent 6256b3f commit f73afeb

File tree

12 files changed

+392
-422
lines changed

12 files changed

+392
-422
lines changed

apps/demo/src/app/todo-indexeddb-sync/synced-todo-store.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
import { patchState, signalStore, withMethods } from '@ngrx/signals';
1+
import { getState, patchState, signalStore, withMethods } from '@ngrx/signals';
22
import {
33
removeEntity,
44
setEntity,
55
updateEntity,
66
withEntities,
77
} from '@ngrx/signals/entities';
8-
import { AddTodo, Todo } from '../shared/todo.service';
8+
import { AddTodo, Todo, TodoService } from '../shared/todo.service';
99
import {
1010
withIndexeddb,
1111
withStorageSync,
1212
} from '@angular-architects/ngrx-toolkit';
13+
import { inject } from '@angular/core';
1314

1415
export const SyncedTodoStore = signalStore(
1516
{ providedIn: 'root' },
@@ -20,7 +21,7 @@ export const SyncedTodoStore = signalStore(
2021
},
2122
withIndexeddb()
2223
),
23-
withMethods((store) => {
24+
withMethods((store, todoService = inject(TodoService)) => {
2425
let currentId = 0;
2526
return {
2627
add(todo: AddTodo) {
@@ -38,12 +39,15 @@ export const SyncedTodoStore = signalStore(
3839
updateEntity({ id, changes: { finished: !todo.finished } })
3940
);
4041
},
42+
43+
reset() {
44+
const state = getState(store);
45+
46+
state.ids.forEach((id) => this.remove(Number(id)));
47+
48+
const todos = todoService.getData();
49+
todos.forEach((todo) => this.add(todo));
50+
},
4151
};
4252
})
43-
//withHooks({
44-
// onInit(store, todoService = inject(TodoService)) {
45-
// const todos = todoService.getData();
46-
// todos.forEach((todo) => store.add(todo));
47-
// },
48-
//})
4953
);

apps/demo/src/app/todo-indexeddb-sync/todo-indexeddb-sync.component.html

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<h2>StorageType:IndexedDB</h2>
2+
<button (click)="onClickReset()" mat-flat-button>reset</button>
23
<mat-table [dataSource]="dataSource" class="mat-elevation-z8">
3-
<h2>IndexedDB</h2>
44
<!-- Checkbox Column -->
55
<ng-container matColumnDef="finished">
66
<mat-header-cell *matHeaderCellDef></mat-header-cell>
77
<mat-cell *matCellDef="let row" class="actions">
88
<mat-checkbox
9-
(click)="$event.stopPropagation()"
109
(change)="checkboxLabel(row)"
10+
(click)="$event.stopPropagation()"
1111
[checked]="row.finished"
1212
>
1313
</mat-checkbox>
@@ -29,17 +29,17 @@ <h2>IndexedDB</h2>
2929

3030
<!-- Deadline Column -->
3131
<ng-container matColumnDef="deadline">
32-
<mat-header-cell mat-header-cell *matHeaderCellDef
33-
>Deadline</mat-header-cell
34-
>
35-
<mat-cell mat-cell *matCellDef="let element">{{
36-
element.deadline
37-
}}</mat-cell>
32+
<mat-header-cell *matHeaderCellDef mat-header-cell
33+
>Deadline
34+
</mat-header-cell>
35+
<mat-cell *matCellDef="let element" mat-cell
36+
>{{ element.deadline }}
37+
</mat-cell>
3838
</ng-container>
3939

4040
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
4141
<mat-row
42-
*matRowDef="let row; columns: displayedColumns"
4342
(click)="selection.toggle(row)"
43+
*matRowDef="let row; columns: displayedColumns"
4444
></mat-row>
4545
</mat-table>

apps/demo/src/app/todo-indexeddb-sync/todo-indexeddb-sync.component.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import { MatTableDataSource, MatTableModule } from '@angular/material/table';
55
import { SyncedTodoStore } from './synced-todo-store';
66
import { SelectionModel } from '@angular/cdk/collections';
77
import { Todo } from '../shared/todo.service';
8+
import { MatButton } from '@angular/material/button';
89

910
@Component({
1011
selector: 'demo-todo-indexeddb-sync',
11-
imports: [MatCheckboxModule, MatIconModule, MatTableModule],
12+
imports: [MatCheckboxModule, MatIconModule, MatTableModule, MatButton],
1213
templateUrl: './todo-indexeddb-sync.component.html',
1314
styleUrl: './todo-indexeddb-sync.component.scss',
1415
standalone: true,
@@ -33,4 +34,8 @@ export class TodoIndexeddbSyncComponent {
3334
removeTodo(todo: Todo) {
3435
this.todoStore.remove(todo.id);
3536
}
37+
38+
onClickReset() {
39+
this.todoStore.reset();
40+
}
3641
}

apps/demo/src/app/todo-storage-sync/synced-todo-store.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { patchState, signalStore, withHooks, withMethods } from '@ngrx/signals';
1+
import { getState, patchState, signalStore, withMethods } from '@ngrx/signals';
22
import {
33
removeEntity,
44
setEntity,
@@ -12,7 +12,10 @@ import { inject } from '@angular/core';
1212
export const SyncedTodoStore = signalStore(
1313
{ providedIn: 'root' },
1414
withEntities<Todo>(),
15-
withMethods((store) => {
15+
withStorageSync({
16+
key: 'todos',
17+
}),
18+
withMethods((store, todoService = inject(TodoService)) => {
1619
let currentId = 0;
1720
return {
1821
add(todo: AddTodo) {
@@ -30,15 +33,15 @@ export const SyncedTodoStore = signalStore(
3033
updateEntity({ id, changes: { finished: !todo.finished } })
3134
);
3235
},
36+
37+
reset() {
38+
const state = getState(store);
39+
40+
state.ids.forEach((id) => this.remove(Number(id)));
41+
42+
const todos = todoService.getData();
43+
todos.forEach((todo) => this.add(todo));
44+
},
3345
};
34-
}),
35-
withHooks({
36-
onInit(store, todoService = inject(TodoService)) {
37-
const todos = todoService.getData();
38-
todos.forEach((todo) => store.add(todo));
39-
},
40-
}),
41-
withStorageSync({
42-
key: 'todos',
4346
})
4447
);

apps/demo/src/app/todo-storage-sync/todo-storage-sync.component.html

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
<h2>StorageType:LocalStorage</h2>
2+
<button (click)="onClickReset()" mat-flat-button>reset</button>
13
<mat-table [dataSource]="dataSource" class="mat-elevation-z8">
24
<!-- Checkbox Column -->
35
<ng-container matColumnDef="finished">
46
<mat-header-cell *matHeaderCellDef></mat-header-cell>
57
<mat-cell *matCellDef="let row" class="actions">
68
<mat-checkbox
7-
(click)="$event.stopPropagation()"
89
(change)="checkboxLabel(row)"
10+
(click)="$event.stopPropagation()"
911
[checked]="row.finished"
1012
>
1113
</mat-checkbox>
@@ -27,17 +29,17 @@
2729

2830
<!-- Deadline Column -->
2931
<ng-container matColumnDef="deadline">
30-
<mat-header-cell mat-header-cell *matHeaderCellDef
31-
>Deadline</mat-header-cell
32-
>
33-
<mat-cell mat-cell *matCellDef="let element">{{
34-
element.deadline
35-
}}</mat-cell>
32+
<mat-header-cell *matHeaderCellDef mat-header-cell
33+
>Deadline
34+
</mat-header-cell>
35+
<mat-cell *matCellDef="let element" mat-cell
36+
>{{ element.deadline }}
37+
</mat-cell>
3638
</ng-container>
3739

3840
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
3941
<mat-row
40-
*matRowDef="let row; columns: displayedColumns"
4142
(click)="selection.toggle(row)"
43+
*matRowDef="let row; columns: displayedColumns"
4244
></mat-row>
4345
</mat-table>

apps/demo/src/app/todo-storage-sync/todo-storage-sync.component.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,17 @@ import { MatIconModule } from '@angular/material/icon';
44
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
55
import { SyncedTodoStore } from './synced-todo-store';
66
import { SelectionModel } from '@angular/cdk/collections';
7-
import { CategoryStore } from '../category.store';
87
import { Todo } from '../shared/todo.service';
8+
import { MatButton } from '@angular/material/button';
99

1010
@Component({
1111
selector: 'demo-todo-storage-sync',
12-
imports: [MatCheckboxModule, MatIconModule, MatTableModule],
12+
imports: [MatCheckboxModule, MatIconModule, MatTableModule, MatButton],
1313
templateUrl: './todo-storage-sync.component.html',
1414
styleUrl: './todo-storage-sync.component.scss',
1515
})
1616
export class TodoStorageSyncComponent {
1717
todoStore = inject(SyncedTodoStore);
18-
categoryStore = inject(CategoryStore);
1918

2019
displayedColumns: string[] = ['finished', 'name', 'description', 'deadline'];
2120
dataSource = new MatTableDataSource<Todo>([]);
@@ -34,4 +33,8 @@ export class TodoStorageSyncComponent {
3433
removeTodo(todo: Todo) {
3534
this.todoStore.remove(todo.id);
3635
}
36+
37+
onClickReset() {
38+
this.todoStore.reset();
39+
}
3740
}

libs/ngrx-toolkit/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ export * from './lib/with-undo-redo';
1919
export * from './lib/with-data-service';
2020
export * from './lib/with-pagination';
2121
export { withReset, setResetState } from './lib/with-reset';
22-
//export { withStorage } from './lib/storage-sync/features/with-storage';
22+
23+
export { withLocalStorage } from './lib/storage-sync/features/with-local-storage';
24+
export { withSessionStorage } from './lib/storage-sync/features/with-session-storage';
2325
export { withIndexeddb } from './lib/storage-sync/features/with-indexeddb';
2426
export { withStorageSync, SyncConfig } from './lib/with-storage-sync';
2527
export { withImmutableState } from './lib/immutable-state/with-immutable-state';
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
11
import { SessionStorageService } from '../internal/session-storage.service';
22

3-
export const withSessionStorage = () => {
4-
return () => SessionStorageService;
5-
};
3+
export const withSessionStorage = () => SessionStorageService;
Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,23 @@
11
import { Injectable } from '@angular/core';
22
import { StorageService } from './storage.service';
33

4-
export const dbName: string = 'ngrxToolkit' as const;
5-
64
export const keyPath: string = 'ngrxToolkitId' as const;
75

86
export const VERSION: number = 1 as const;
97

108
@Injectable({ providedIn: 'root' })
119
export class IndexedDBService implements StorageService {
12-
/**
13-
* open indexedDB
14-
* @param storeName
15-
*/
16-
private async openDB(storeName: string): Promise<IDBDatabase> {
17-
return new Promise((resolve, reject) => {
18-
const request = indexedDB.open(dbName, VERSION);
19-
20-
request.onupgradeneeded = () => {
21-
const db = request.result;
22-
23-
if (!db.objectStoreNames.contains(storeName)) {
24-
db.createObjectStore(storeName, { keyPath });
25-
}
26-
};
27-
28-
request.onsuccess = (): void => {
29-
resolve(request.result);
30-
};
31-
32-
request.onerror = (): void => {
33-
reject(request.error);
34-
};
35-
});
36-
}
37-
3810
/**
3911
* write to indexedDB
40-
* @param storeName
12+
* @param storeAndDbName
4113
* @param data
4214
*/
43-
async setItem(storeName: string, data: string): Promise<void> {
44-
const db = await this.openDB(storeName);
15+
async setItem(storeAndDbName: string, data: string): Promise<void> {
16+
const db = await this.openDB(storeAndDbName);
4517

46-
const tx = db.transaction(storeName, 'readwrite');
18+
const tx = db.transaction(storeAndDbName, 'readwrite');
4719

48-
const store = tx.objectStore(storeName);
20+
const store = tx.objectStore(storeAndDbName);
4921

5022
store.put({
5123
[keyPath]: keyPath,
@@ -67,20 +39,25 @@ export class IndexedDBService implements StorageService {
6739

6840
/**
6941
* read from indexedDB
70-
* @param storeName
42+
* @param storeAndDbName
7143
*/
72-
async getItem<T>(storeName: string): Promise<T> {
73-
const db = await this.openDB(storeName);
44+
async getItem(storeAndDbName: string): Promise<string | null> {
45+
const db = await this.openDB(storeAndDbName);
7446

75-
const tx = db.transaction(storeName, 'readonly');
47+
const tx = db.transaction(storeAndDbName, 'readonly');
7648

77-
const store = tx.objectStore(storeName);
49+
const store = tx.objectStore(storeAndDbName);
7850

7951
const request = store.get(keyPath);
8052

8153
return new Promise((resolve, reject) => {
8254
request.onsuccess = (): void => {
8355
db.close();
56+
// localStorage(sessionStorage) returns null if the key does not exist
57+
// Similarly, indexedDB should return null
58+
if (request.result === undefined) {
59+
resolve(null);
60+
}
8461
resolve(request.result?.['value']);
8562
};
8663

@@ -93,15 +70,15 @@ export class IndexedDBService implements StorageService {
9370

9471
/**
9572
* delete indexedDB
96-
* @param storeName
73+
* @param storeAndDbName
9774
* @returns
9875
*/
99-
async clear(storeName: string): Promise<void> {
100-
const db = await this.openDB(storeName);
76+
async clear(storeAndDbName: string): Promise<void> {
77+
const db = await this.openDB(storeAndDbName);
10178

102-
const tx = db.transaction(storeName, 'readwrite');
79+
const tx = db.transaction(storeAndDbName, 'readwrite');
10380

104-
const store = tx.objectStore(storeName);
81+
const store = tx.objectStore(storeAndDbName);
10582

10683
const request = store.delete(keyPath);
10784

@@ -117,4 +94,30 @@ export class IndexedDBService implements StorageService {
11794
};
11895
});
11996
}
97+
98+
/**
99+
* open indexedDB
100+
* @param storeAndDbName
101+
*/
102+
private async openDB(storeAndDbName: string): Promise<IDBDatabase> {
103+
return new Promise((resolve, reject) => {
104+
const request = indexedDB.open(storeAndDbName, VERSION);
105+
106+
request.onupgradeneeded = () => {
107+
const db = request.result;
108+
109+
if (!db.objectStoreNames.contains(storeAndDbName)) {
110+
db.createObjectStore(storeAndDbName, { keyPath });
111+
}
112+
};
113+
114+
request.onsuccess = (): void => {
115+
resolve(request.result);
116+
};
117+
118+
request.onerror = (): void => {
119+
reject(request.error);
120+
};
121+
});
122+
}
120123
}

0 commit comments

Comments
 (0)