@verisoft/store 19.0.0-1 → 20.0.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 +418 -1
- package/fesm2022/verisoft-store.mjs.map +1 -1
- package/index.d.ts +312 -10
- package/package.json +5 -5
- package/lib/binding-state/binding.actions.d.ts +0 -43
- package/lib/binding-state/binding.effects.d.ts +0 -45
- package/lib/detail-state/detail.actions.d.ts +0 -52
- package/lib/detail-state/detail.effects.d.ts +0 -52
- package/lib/detail-state/detail.models.d.ts +0 -27
- package/lib/detail-state/detail.reducer.d.ts +0 -4
- package/lib/table-state/actions.d.ts +0 -48
- package/lib/table-state/effects.d.ts +0 -25
- package/lib/table-state/models.d.ts +0 -9
- package/lib/table-state/reducers.d.ts +0 -3
package/README.md
CHANGED
|
@@ -1,4 +1,421 @@
|
|
|
1
|
-
# store
|
|
1
|
+
# @verisoft/store
|
|
2
|
+
|
|
3
|
+
NgRx-based state management library for Verisoft Angular applications, providing standardized patterns for handling application state, including table/list views, detail forms, and entity relationships.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @verisoft/store
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Detail State Management**: Handle entity detail forms with validation and persistence
|
|
14
|
+
- **Table State Management**: Manage paginated lists with filtering, sorting, and selection
|
|
15
|
+
- **Binding State Management**: Handle entity relationships and associations
|
|
16
|
+
- **Type-Safe**: Full TypeScript support with generic types
|
|
17
|
+
- **NgRx Integration**: Built on @ngrx/store and @ngrx/effects
|
|
18
|
+
|
|
19
|
+
## State Modules
|
|
20
|
+
|
|
21
|
+
### Detail State
|
|
22
|
+
|
|
23
|
+
Manages individual entity details, forms, and CRUD operations.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import {
|
|
27
|
+
createDetailReducers,
|
|
28
|
+
createInitDetailAction,
|
|
29
|
+
createLoadDetailSuccessAction,
|
|
30
|
+
createSaveDetailAction,
|
|
31
|
+
DetailState
|
|
32
|
+
} from '@verisoft/store';
|
|
33
|
+
|
|
34
|
+
// State interface
|
|
35
|
+
interface UserDetailState extends DetailState<User> {
|
|
36
|
+
// Additional state properties if needed
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Create reducer
|
|
40
|
+
const userDetailReducer = createDetailReducers<User>(
|
|
41
|
+
'userDetail', // Repository name
|
|
42
|
+
INITIAL_USER_DETAIL_STATE
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Actions usage
|
|
46
|
+
export class UserDetailComponent {
|
|
47
|
+
constructor(private store: Store) {}
|
|
48
|
+
|
|
49
|
+
loadUser(id: string) {
|
|
50
|
+
this.store.dispatch(
|
|
51
|
+
createInitDetailAction('userDetail')({ obj: id })
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
saveUser() {
|
|
56
|
+
this.store.dispatch(
|
|
57
|
+
createSaveDetailAction('userDetail')()
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Table State
|
|
64
|
+
|
|
65
|
+
Manages paginated lists with filtering, sorting, and item selection.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import {
|
|
69
|
+
createTablePageReducers,
|
|
70
|
+
createGetPageTableAction,
|
|
71
|
+
createFilterPageTableAction,
|
|
72
|
+
TableState
|
|
73
|
+
} from '@verisoft/store';
|
|
74
|
+
import { RequestParams } from '@verisoft/core';
|
|
75
|
+
|
|
76
|
+
// State interface
|
|
77
|
+
interface UserTableState extends TableState<User> {
|
|
78
|
+
// Additional state properties if needed
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Create reducer
|
|
82
|
+
const userTableReducer = createTablePageReducers<User>(
|
|
83
|
+
'userTable', // Repository name
|
|
84
|
+
INITIAL_USER_TABLE_STATE
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Actions usage
|
|
88
|
+
export class UserListComponent {
|
|
89
|
+
constructor(private store: Store) {}
|
|
90
|
+
|
|
91
|
+
loadPage(page: number) {
|
|
92
|
+
this.store.dispatch(
|
|
93
|
+
createGetPageTableAction('userTable')({
|
|
94
|
+
page,
|
|
95
|
+
filter: this.currentFilter,
|
|
96
|
+
sort: this.currentSort
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
applyFilter(filter: UserFilter) {
|
|
102
|
+
this.store.dispatch(
|
|
103
|
+
createFilterPageTableAction('userTable')({ filter })
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
selectItems(selectedItems: User[]) {
|
|
108
|
+
this.store.dispatch(
|
|
109
|
+
createSelectItemsTableAction('userTable')({ selectedItems })
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Binding State
|
|
116
|
+
|
|
117
|
+
Manages relationships between entities (e.g., user roles, project members).
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import {
|
|
121
|
+
createBindingsEffects,
|
|
122
|
+
createAddBindingAction,
|
|
123
|
+
createEditBindingAction,
|
|
124
|
+
createDeleteBindingAction
|
|
125
|
+
} from '@verisoft/store';
|
|
126
|
+
|
|
127
|
+
// Usage in effects
|
|
128
|
+
@Injectable()
|
|
129
|
+
export class UserRoleEffects {
|
|
130
|
+
constructor(
|
|
131
|
+
private actions$: Actions,
|
|
132
|
+
private userRoleService: UserRoleService,
|
|
133
|
+
private store: Store
|
|
134
|
+
) {}
|
|
135
|
+
|
|
136
|
+
// Create binding effects
|
|
137
|
+
userRoleBindings$ = createBindingsEffects(
|
|
138
|
+
'userRoles', // Repository name
|
|
139
|
+
this.actions$, // Actions observable
|
|
140
|
+
this.userDetail$, // Parent entity observable
|
|
141
|
+
'role', // Singular name
|
|
142
|
+
this.userRoleService.addRoles.bind(this.userRoleService),
|
|
143
|
+
this.userRoleService.deleteRoles.bind(this.userRoleService),
|
|
144
|
+
this.snackBar,
|
|
145
|
+
this.translateService,
|
|
146
|
+
this.tableService
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Complete Example
|
|
152
|
+
|
|
153
|
+
### Setting up Feature State
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { createFeatureSelector, createSelector } from '@ngrx/store';
|
|
157
|
+
import {
|
|
158
|
+
DetailState,
|
|
159
|
+
TableState,
|
|
160
|
+
createDetailReducers,
|
|
161
|
+
createTablePageReducers
|
|
162
|
+
} from '@verisoft/store';
|
|
163
|
+
|
|
164
|
+
// Feature state interface
|
|
165
|
+
interface UserFeatureState {
|
|
166
|
+
userDetail: DetailState<User>;
|
|
167
|
+
userTable: TableState<User>;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Reducers
|
|
171
|
+
const userDetailReducer = createDetailReducers<User>('userDetail');
|
|
172
|
+
const userTableReducer = createTablePageReducers<User>('userTable');
|
|
173
|
+
|
|
174
|
+
// Feature selector
|
|
175
|
+
const selectUserFeature = createFeatureSelector<UserFeatureState>('users');
|
|
176
|
+
|
|
177
|
+
// Selectors
|
|
178
|
+
export const selectUserDetail = createSelector(
|
|
179
|
+
selectUserFeature,
|
|
180
|
+
state => state.userDetail
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
export const selectUserTable = createSelector(
|
|
184
|
+
selectUserFeature,
|
|
185
|
+
state => state.userTable
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
export const selectCurrentUser = createSelector(
|
|
189
|
+
selectUserDetail,
|
|
190
|
+
state => state.item
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
export const selectUserList = createSelector(
|
|
194
|
+
selectUserTable,
|
|
195
|
+
state => state.gPage?.data || []
|
|
196
|
+
);
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Component Integration
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { Component, OnInit } from '@angular/core';
|
|
203
|
+
import { Store } from '@ngrx/store';
|
|
204
|
+
import {
|
|
205
|
+
createInitDetailAction,
|
|
206
|
+
createSaveDetailAction,
|
|
207
|
+
createGetPageTableAction
|
|
208
|
+
} from '@verisoft/store';
|
|
209
|
+
|
|
210
|
+
@Component({
|
|
211
|
+
template: `
|
|
212
|
+
<div *ngIf="user$ | async as user">
|
|
213
|
+
<form [formGroup]="userForm" (ngSubmit)="saveUser()">
|
|
214
|
+
<!-- Form controls -->
|
|
215
|
+
</form>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<div *ngIf="users$ | async as users">
|
|
219
|
+
<table>
|
|
220
|
+
<tr *ngFor="let user of users">
|
|
221
|
+
<td>{{ user.name }}</td>
|
|
222
|
+
<td>{{ user.email }}</td>
|
|
223
|
+
</tr>
|
|
224
|
+
</table>
|
|
225
|
+
</div>
|
|
226
|
+
`
|
|
227
|
+
})
|
|
228
|
+
export class UserManagementComponent implements OnInit {
|
|
229
|
+
user$ = this.store.select(selectCurrentUser);
|
|
230
|
+
users$ = this.store.select(selectUserList);
|
|
231
|
+
loading$ = this.store.select(selectUserTable).pipe(
|
|
232
|
+
map(state => state.dataLoading)
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
constructor(private store: Store) {}
|
|
236
|
+
|
|
237
|
+
ngOnInit() {
|
|
238
|
+
this.loadUsers();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
loadUsers() {
|
|
242
|
+
this.store.dispatch(
|
|
243
|
+
createGetPageTableAction('userTable')({
|
|
244
|
+
page: 1,
|
|
245
|
+
size: 20
|
|
246
|
+
})
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
loadUser(id: string) {
|
|
251
|
+
this.store.dispatch(
|
|
252
|
+
createInitDetailAction('userDetail')({ obj: id })
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
saveUser() {
|
|
257
|
+
this.store.dispatch(
|
|
258
|
+
createSaveDetailAction('userDetail')()
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Form Integration with DetailStore Directive
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// Component using the directive
|
|
268
|
+
@Component({
|
|
269
|
+
template: `
|
|
270
|
+
<form
|
|
271
|
+
v-baseForm
|
|
272
|
+
#userForm="baseForm"
|
|
273
|
+
v-useDetailStore
|
|
274
|
+
[form]="userForm"
|
|
275
|
+
detailsRepository="userDetail"
|
|
276
|
+
ngrxFeatureKey="users"
|
|
277
|
+
[detailId]="userId">
|
|
278
|
+
|
|
279
|
+
<input
|
|
280
|
+
type="text"
|
|
281
|
+
formControlName="name"
|
|
282
|
+
placeholder="User Name">
|
|
283
|
+
|
|
284
|
+
<input
|
|
285
|
+
type="email"
|
|
286
|
+
formControlName="email"
|
|
287
|
+
placeholder="Email">
|
|
288
|
+
|
|
289
|
+
<button type="submit" [disabled]="!userForm.valid">
|
|
290
|
+
Save User
|
|
291
|
+
</button>
|
|
292
|
+
</form>
|
|
293
|
+
`
|
|
294
|
+
})
|
|
295
|
+
export class UserFormComponent {
|
|
296
|
+
@Input() userId?: string;
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## State Models
|
|
301
|
+
|
|
302
|
+
### DetailState Interface
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
interface DetailState<T> {
|
|
306
|
+
loaded: boolean; // Data load status
|
|
307
|
+
item?: T; // Current entity
|
|
308
|
+
formState?: FormState; // Form validation state
|
|
309
|
+
error?: string | null; // Error message
|
|
310
|
+
saveItemState: SaveItemState; // Save operation state
|
|
311
|
+
backendValidationErrors: BackendValidationError[]; // Server validation
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
interface FormState {
|
|
315
|
+
dirty: boolean; // Form has changes
|
|
316
|
+
valid: boolean; // Form is valid
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
interface SaveItemState {
|
|
320
|
+
saveInProgress: boolean; // Save operation in progress
|
|
321
|
+
error?: string | null; // Save error
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### TableState Interface
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
interface TableState<T> {
|
|
329
|
+
dataLoading: boolean; // Data loading status
|
|
330
|
+
requestParams: RequestParams<T>; // Current request parameters
|
|
331
|
+
gPage?: Page<T>; // Paginated data
|
|
332
|
+
error?: string | null; // Error message
|
|
333
|
+
selectedItems?: T[]; // Selected table items
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Available Actions
|
|
338
|
+
|
|
339
|
+
### Detail Actions
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// Initialize detail (load or create new)
|
|
343
|
+
createInitDetailAction(repository)({ obj: id })
|
|
344
|
+
createInitNewDetailAction(repository)()
|
|
345
|
+
|
|
346
|
+
// Data operations
|
|
347
|
+
createLoadDetailSuccessAction(repository)({ item })
|
|
348
|
+
createLoadDetailFailureAction(repository)({ error })
|
|
349
|
+
|
|
350
|
+
// Form operations
|
|
351
|
+
createUpdateDetailAction(repository)({ item })
|
|
352
|
+
createUpdateFormStateAction(repository)({ formState })
|
|
353
|
+
|
|
354
|
+
// Save operations
|
|
355
|
+
createSaveDetailAction(repository)()
|
|
356
|
+
createSaveDetailSuccessAction(repository)({ item })
|
|
357
|
+
createSaveDetailFailureAction(repository)({ error })
|
|
358
|
+
|
|
359
|
+
// State management
|
|
360
|
+
createResetStateAction(repository)()
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Table Actions
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
// Data loading
|
|
367
|
+
createGetPageTableAction(repository)({ page, filter, sort })
|
|
368
|
+
createDataLoadSuccessTableAction(repository)({ gPage })
|
|
369
|
+
createDataLoadErrorTableAction(repository)({ error })
|
|
370
|
+
|
|
371
|
+
// Filtering and sorting
|
|
372
|
+
createFilterPageTableAction(repository)({ filter })
|
|
373
|
+
createStaticFilterTableAction(repository)({ filter })
|
|
374
|
+
createResetTableFilterAction(repository)()
|
|
375
|
+
|
|
376
|
+
// Selection and pagination
|
|
377
|
+
createSelectItemsTableAction(repository)({ selectedItems })
|
|
378
|
+
createChangePageSizeTableAction(repository)({ size })
|
|
379
|
+
|
|
380
|
+
// State management
|
|
381
|
+
createDestroyTableAction(repository)()
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Binding Actions
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
// Binding operations
|
|
388
|
+
createAddBindingAction(repository)({ bindings })
|
|
389
|
+
createEditBindingAction(repository)({ binding })
|
|
390
|
+
createDeleteBindingAction(repository)({ bindingIds })
|
|
391
|
+
|
|
392
|
+
// Success/failure actions
|
|
393
|
+
createBindingModifiedSuccessAction(repository)()
|
|
394
|
+
createBindingModifiedFailureAction(repository)({ error })
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Best Practices
|
|
398
|
+
|
|
399
|
+
1. **Repository Naming**: Use consistent repository names across your application
|
|
400
|
+
2. **State Structure**: Keep state normalized and avoid deep nesting
|
|
401
|
+
3. **Selectors**: Create memoized selectors for computed state
|
|
402
|
+
4. **Effects**: Handle side effects (API calls) in NgRx effects
|
|
403
|
+
5. **Error Handling**: Implement proper error handling for all operations
|
|
404
|
+
6. **Type Safety**: Use TypeScript generics for type-safe state management
|
|
405
|
+
|
|
406
|
+
## Dependencies
|
|
407
|
+
|
|
408
|
+
- `@ngrx/store`
|
|
409
|
+
- `@ngrx/effects`
|
|
410
|
+
- `@verisoft/core` (for HTTP models)
|
|
411
|
+
|
|
412
|
+
## Contributing
|
|
413
|
+
|
|
414
|
+
This library is part of the Verisoft framework ecosystem. Follow the established patterns and ensure compatibility with other @verisoft packages.
|
|
415
|
+
|
|
416
|
+
## Running unit tests
|
|
417
|
+
|
|
418
|
+
Run `nx test store` to execute the unit tests.
|
|
2
419
|
|
|
3
420
|
This library was generated with [Nx](https://nx.dev).
|
|
4
421
|
|