@verisoft/store 18.1.0 → 18.3.1
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/.eslintrc.json +43 -43
- package/README.md +7 -7
- package/jest.config.ts +22 -22
- package/ng-package.json +7 -7
- package/package.json +13 -12
- package/project.json +36 -36
- package/src/index.ts +12 -9
- package/src/lib/binding-state/binding.actions.ts +76 -0
- package/src/lib/binding-state/binding.effects.ts +472 -0
- package/src/lib/detail-state/detail.actions.ts +89 -63
- package/src/lib/detail-state/detail.effects.ts +283 -131
- package/src/lib/detail-state/detail.models.ts +42 -32
- package/src/lib/detail-state/detail.reducer.ts +147 -122
- package/src/lib/table-state/actions.ts +92 -92
- package/src/lib/table-state/effects.ts +100 -100
- package/src/lib/table-state/models.ts +19 -19
- package/src/lib/table-state/reducers.ts +126 -126
- package/src/test-setup.ts +8 -8
- package/tsconfig.json +29 -29
- package/tsconfig.lib.json +17 -17
- package/tsconfig.lib.prod.json +9 -9
- package/tsconfig.spec.json +16 -16
|
@@ -1,131 +1,283 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
1
|
+
import { Router } from '@angular/router';
|
|
2
|
+
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
|
3
|
+
import { BaseHttpService } from '@verisoft/core';
|
|
4
|
+
import {
|
|
5
|
+
catchError,
|
|
6
|
+
EMPTY,
|
|
7
|
+
map,
|
|
8
|
+
Observable,
|
|
9
|
+
of,
|
|
10
|
+
switchMap,
|
|
11
|
+
tap,
|
|
12
|
+
withLatestFrom,
|
|
13
|
+
} from 'rxjs';
|
|
14
|
+
import {
|
|
15
|
+
createDeleteDetailAction,
|
|
16
|
+
createDeleteDetailFailureAction,
|
|
17
|
+
createDeleteDetailSuccessAction,
|
|
18
|
+
createInitDetailAction,
|
|
19
|
+
createInitNewDetailAction,
|
|
20
|
+
createLoadDetailFailureAction,
|
|
21
|
+
createLoadDetailSuccessAction,
|
|
22
|
+
createSaveDetailAction,
|
|
23
|
+
createSaveDetailFailureAction,
|
|
24
|
+
createSaveDetailSuccessAction,
|
|
25
|
+
} from './detail.actions';
|
|
26
|
+
|
|
27
|
+
export interface CreateInitFormConfig<T> {
|
|
28
|
+
service?: BaseHttpService<T>;
|
|
29
|
+
get?: (obj: any) => Observable<T>;
|
|
30
|
+
snackbar?: any;
|
|
31
|
+
errorMessage?: string;
|
|
32
|
+
notFoundMessage?: string;
|
|
33
|
+
successMessge?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface DetailEffectConfig<T> {
|
|
37
|
+
detail$: Observable<T | undefined>;
|
|
38
|
+
snackbar?: any;
|
|
39
|
+
router?: Router;
|
|
40
|
+
redirectPath?: string;
|
|
41
|
+
useItemId?: boolean;
|
|
42
|
+
navigationMap?: Map<keyof T, string>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createInitDetailEffect<T>(
|
|
46
|
+
detailsRepository: string,
|
|
47
|
+
actions$: Actions,
|
|
48
|
+
config: CreateInitFormConfig<T>
|
|
49
|
+
) {
|
|
50
|
+
return createEffect(() => {
|
|
51
|
+
return actions$.pipe(
|
|
52
|
+
ofType(createInitDetailAction(detailsRepository)),
|
|
53
|
+
switchMap(({ obj }) => {
|
|
54
|
+
const get$: Observable<any> = (config?.service?.get(obj as any) ??
|
|
55
|
+
config?.get?.(obj)) as Observable<any>;
|
|
56
|
+
if (!get$) return EMPTY;
|
|
57
|
+
return get$?.pipe(
|
|
58
|
+
map((item) => {
|
|
59
|
+
return createLoadDetailSuccessAction(detailsRepository)({ item });
|
|
60
|
+
}),
|
|
61
|
+
catchError((error) => {
|
|
62
|
+
if (error.status === 404) {
|
|
63
|
+
config?.snackbar?.showError(config?.notFoundMessage ?? error);
|
|
64
|
+
} else {
|
|
65
|
+
config?.snackbar?.showError(config?.errorMessage ?? error);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.error('Error', error);
|
|
69
|
+
return of(
|
|
70
|
+
createLoadDetailFailureAction(detailsRepository)({ error })
|
|
71
|
+
);
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function createInitNewDetailEffect(
|
|
80
|
+
detailsRepository: string,
|
|
81
|
+
actions$: Actions
|
|
82
|
+
) {
|
|
83
|
+
return createEffect(() =>
|
|
84
|
+
actions$.pipe(
|
|
85
|
+
ofType(createInitNewDetailAction(detailsRepository)),
|
|
86
|
+
map(() => {
|
|
87
|
+
return createLoadDetailSuccessAction(detailsRepository)({ item: null });
|
|
88
|
+
})
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function createSaveDetailEffect<
|
|
94
|
+
T extends { id: number | string },
|
|
95
|
+
SERVICE extends BaseHttpService<T>
|
|
96
|
+
>(
|
|
97
|
+
detailsRepository: string,
|
|
98
|
+
actions$: Actions,
|
|
99
|
+
service: SERVICE,
|
|
100
|
+
config: DetailEffectConfig<T>
|
|
101
|
+
) {
|
|
102
|
+
return createEffect(() =>
|
|
103
|
+
actions$.pipe(
|
|
104
|
+
ofType(createSaveDetailAction(detailsRepository)),
|
|
105
|
+
withLatestFrom(config.detail$),
|
|
106
|
+
switchMap(([, savedItem]) => {
|
|
107
|
+
const isNew = !savedItem?.id;
|
|
108
|
+
const saveAction = isNew
|
|
109
|
+
? service.post(savedItem as T)
|
|
110
|
+
: service.put(savedItem.id, savedItem);
|
|
111
|
+
return saveAction.pipe(
|
|
112
|
+
map((item) => {
|
|
113
|
+
if (isNew && config?.router && config?.redirectPath) {
|
|
114
|
+
const suffix = config?.useItemId ? '/' + item.id : '';
|
|
115
|
+
config.router.navigateByUrl(config.redirectPath + suffix);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (isNew && config?.router && config?.navigationMap) {
|
|
119
|
+
const url = constructUrlFromNavigationMap(item, config.navigationMap)
|
|
120
|
+
config.router.navigateByUrl(url);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return createSaveDetailSuccessAction(detailsRepository)({
|
|
124
|
+
item: item,
|
|
125
|
+
});
|
|
126
|
+
}),
|
|
127
|
+
catchError((error) => {
|
|
128
|
+
console.error('Error', error);
|
|
129
|
+
return of(
|
|
130
|
+
createSaveDetailFailureAction(detailsRepository)({ error })
|
|
131
|
+
);
|
|
132
|
+
})
|
|
133
|
+
);
|
|
134
|
+
})
|
|
135
|
+
)
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function createSaveDetailSuccessEffect(
|
|
140
|
+
detailsRepository: string,
|
|
141
|
+
actions$: Actions,
|
|
142
|
+
config?: CreateInitFormConfig<unknown>
|
|
143
|
+
) {
|
|
144
|
+
return createEffect(
|
|
145
|
+
() =>
|
|
146
|
+
actions$.pipe(
|
|
147
|
+
ofType(createSaveDetailSuccessAction(detailsRepository)),
|
|
148
|
+
map((data: any) => {
|
|
149
|
+
config?.snackbar?.showSuccess(
|
|
150
|
+
config?.successMessge ?? `Item successfully saved.`
|
|
151
|
+
);
|
|
152
|
+
return createInitDetailAction(detailsRepository)({ obj: data.item.id });
|
|
153
|
+
})
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function createSaveDetailFailureEffect(
|
|
159
|
+
detailsRepository: string,
|
|
160
|
+
actions$: Actions,
|
|
161
|
+
config?: CreateInitFormConfig<unknown>
|
|
162
|
+
) {
|
|
163
|
+
return createEffect(
|
|
164
|
+
() =>
|
|
165
|
+
actions$.pipe(
|
|
166
|
+
ofType(createSaveDetailFailureAction(detailsRepository)),
|
|
167
|
+
tap(({ error }) => {
|
|
168
|
+
config?.snackbar?.showError(
|
|
169
|
+
config.errorMessage ?? `Error saving item. ${error.message}`
|
|
170
|
+
);
|
|
171
|
+
})
|
|
172
|
+
),
|
|
173
|
+
{
|
|
174
|
+
dispatch: false,
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function createDeleteDetailEffect<
|
|
180
|
+
T extends { id: number | string },
|
|
181
|
+
SERVICE extends BaseHttpService<T>
|
|
182
|
+
>(
|
|
183
|
+
detailsRepository: string,
|
|
184
|
+
actions$: Actions,
|
|
185
|
+
service: SERVICE,
|
|
186
|
+
config: DetailEffectConfig<T>,
|
|
187
|
+
) {
|
|
188
|
+
return createEffect(() =>
|
|
189
|
+
actions$.pipe(
|
|
190
|
+
ofType(createDeleteDetailAction(detailsRepository)),
|
|
191
|
+
withLatestFrom(config.detail$),
|
|
192
|
+
switchMap(([, savedItem]) => {
|
|
193
|
+
const id = savedItem?.id;
|
|
194
|
+
if (!id) {
|
|
195
|
+
return of(
|
|
196
|
+
createSaveDetailFailureAction(detailsRepository)({
|
|
197
|
+
error: 'Item has no id',
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return service.delete(id).pipe(
|
|
203
|
+
map(() => {
|
|
204
|
+
if (config?.router && config?.redirectPath) {
|
|
205
|
+
config.router.navigateByUrl(`${config.redirectPath}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (config?.router && config?.navigationMap) {
|
|
209
|
+
const url = constructUrlFromNavigationMap(savedItem, config.navigationMap)
|
|
210
|
+
config.router.navigateByUrl(url);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return createDeleteDetailSuccessAction(detailsRepository)();
|
|
214
|
+
}),
|
|
215
|
+
catchError((error) => {
|
|
216
|
+
console.error('Error', error);
|
|
217
|
+
config.snackbar?.showError('Failed to delete item.');
|
|
218
|
+
return of(
|
|
219
|
+
createDeleteDetailFailureAction(detailsRepository)({ error })
|
|
220
|
+
);
|
|
221
|
+
})
|
|
222
|
+
);
|
|
223
|
+
})
|
|
224
|
+
)
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function createDeleteDetailSuccessEffect(
|
|
229
|
+
detailsRepository: string,
|
|
230
|
+
actions$: Actions,
|
|
231
|
+
config?: CreateInitFormConfig<unknown>
|
|
232
|
+
) {
|
|
233
|
+
return createEffect(
|
|
234
|
+
() =>
|
|
235
|
+
actions$.pipe(
|
|
236
|
+
ofType(createDeleteDetailSuccessAction(detailsRepository)),
|
|
237
|
+
tap(() => {
|
|
238
|
+
config?.snackbar?.showSuccess(
|
|
239
|
+
config.successMessge ?? `Item successfully deleted.`
|
|
240
|
+
);
|
|
241
|
+
})
|
|
242
|
+
),
|
|
243
|
+
{
|
|
244
|
+
dispatch: false,
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function createDeleteDetailFailureEffect(
|
|
250
|
+
detailsRepository: string,
|
|
251
|
+
actions$: Actions,
|
|
252
|
+
config?: CreateInitFormConfig<unknown>
|
|
253
|
+
) {
|
|
254
|
+
return createEffect(
|
|
255
|
+
() =>
|
|
256
|
+
actions$.pipe(
|
|
257
|
+
ofType(createDeleteDetailFailureAction(detailsRepository)),
|
|
258
|
+
tap(({ error }) => {
|
|
259
|
+
config?.snackbar?.showError(
|
|
260
|
+
config.errorMessage ?? `Error deleting item. ${error.message}`
|
|
261
|
+
);
|
|
262
|
+
})
|
|
263
|
+
),
|
|
264
|
+
{
|
|
265
|
+
dispatch: false,
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function constructUrlFromNavigationMap<T extends object>(
|
|
271
|
+
item: T,
|
|
272
|
+
navigationMap: Map<keyof T, string>)
|
|
273
|
+
: string {
|
|
274
|
+
let url = '';
|
|
275
|
+
navigationMap.forEach((urlPartValue, suffixPropertyKey) => {
|
|
276
|
+
const suffix = item[suffixPropertyKey];
|
|
277
|
+
if (suffix) {
|
|
278
|
+
url += `${urlPartValue}/${suffix}`;
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
return url;
|
|
283
|
+
}
|
|
@@ -1,32 +1,42 @@
|
|
|
1
|
-
export interface DetailState<T> {
|
|
2
|
-
loaded: boolean;
|
|
3
|
-
item?: T;
|
|
4
|
-
formState?: FormState;
|
|
5
|
-
error?: string | null;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface SaveItemState {
|
|
21
|
-
saveInProgress: boolean;
|
|
22
|
-
error?: string | null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const INITIAL_SAVE_ITEM_STATE: SaveItemState = {
|
|
26
|
-
saveInProgress: false,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
1
|
+
export interface DetailState<T> {
|
|
2
|
+
loaded: boolean;
|
|
3
|
+
item?: T;
|
|
4
|
+
formState?: FormState;
|
|
5
|
+
error?: string | null;
|
|
6
|
+
saveItemState: SaveItemState;
|
|
7
|
+
backendValidationErrors: BackendValidationError[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface BackendValidationError {
|
|
11
|
+
code: string;
|
|
12
|
+
message: string;
|
|
13
|
+
parameters: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ChosenRowsState<T> {
|
|
17
|
+
rows: Array<T>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SaveItemState {
|
|
21
|
+
saveInProgress: boolean;
|
|
22
|
+
error?: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const INITIAL_SAVE_ITEM_STATE: SaveItemState = {
|
|
26
|
+
saveInProgress: false,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const INITIAL_CHOSEN_ROW_STATE: ChosenRowsState<any> = {
|
|
30
|
+
rows: []
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const INITIAL_DETAIL_STATE: DetailState<any> = {
|
|
34
|
+
loaded: false,
|
|
35
|
+
saveItemState: INITIAL_SAVE_ITEM_STATE,
|
|
36
|
+
backendValidationErrors: [],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export interface FormState {
|
|
40
|
+
dirty: boolean;
|
|
41
|
+
valid: boolean;
|
|
42
|
+
}
|