@angular-architects/ngrx-toolkit 20.0.0 → 20.0.2

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.
Files changed (84) hide show
  1. package/eslint.config.cjs +43 -0
  2. package/jest.config.ts +22 -0
  3. package/ng-package.json +7 -0
  4. package/package.json +4 -21
  5. package/project.json +37 -0
  6. package/redux-connector/docs/README.md +131 -0
  7. package/redux-connector/index.ts +6 -0
  8. package/redux-connector/ng-package.json +5 -0
  9. package/redux-connector/src/lib/create-redux.ts +102 -0
  10. package/redux-connector/src/lib/model.ts +89 -0
  11. package/redux-connector/src/lib/rxjs-interop/redux-method.ts +66 -0
  12. package/redux-connector/src/lib/signal-redux-store.ts +59 -0
  13. package/redux-connector/src/lib/util.ts +22 -0
  14. package/src/index.ts +43 -0
  15. package/src/lib/assertions/assertions.ts +9 -0
  16. package/src/lib/devtools/features/with-disabled-name-indicies.ts +31 -0
  17. package/src/lib/devtools/features/with-glitch-tracking.ts +35 -0
  18. package/src/lib/devtools/features/with-mapper.ts +34 -0
  19. package/src/lib/devtools/internal/current-action-names.ts +1 -0
  20. package/src/lib/devtools/internal/default-tracker.ts +60 -0
  21. package/src/lib/devtools/internal/devtools-feature.ts +37 -0
  22. package/src/lib/devtools/internal/devtools-syncer.service.ts +202 -0
  23. package/src/lib/devtools/internal/glitch-tracker.service.ts +61 -0
  24. package/src/lib/devtools/internal/models.ts +29 -0
  25. package/src/lib/devtools/provide-devtools-config.ts +32 -0
  26. package/src/lib/devtools/rename-devtools-name.ts +21 -0
  27. package/src/lib/devtools/tests/action-name.spec.ts +48 -0
  28. package/src/lib/devtools/tests/basic.spec.ts +111 -0
  29. package/src/lib/devtools/tests/connecting.spec.ts +37 -0
  30. package/src/lib/devtools/tests/helpers.spec.ts +43 -0
  31. package/src/lib/devtools/tests/naming.spec.ts +216 -0
  32. package/src/lib/devtools/tests/provide-devtools-config.spec.ts +25 -0
  33. package/src/lib/devtools/tests/types.spec.ts +19 -0
  34. package/src/lib/devtools/tests/update-state.spec.ts +29 -0
  35. package/src/lib/devtools/tests/with-devtools.spec.ts +5 -0
  36. package/src/lib/devtools/tests/with-glitch-tracking.spec.ts +272 -0
  37. package/src/lib/devtools/tests/with-mapper.spec.ts +69 -0
  38. package/src/lib/devtools/update-state.ts +38 -0
  39. package/src/lib/devtools/with-dev-tools-stub.ts +6 -0
  40. package/src/lib/devtools/with-devtools.ts +81 -0
  41. package/src/lib/immutable-state/deep-freeze.ts +43 -0
  42. package/src/lib/immutable-state/is-dev-mode.ts +6 -0
  43. package/src/lib/immutable-state/tests/with-immutable-state.spec.ts +278 -0
  44. package/src/lib/immutable-state/with-immutable-state.ts +150 -0
  45. package/src/lib/shared/prettify.ts +3 -0
  46. package/src/lib/shared/signal-store-models.ts +30 -0
  47. package/src/lib/shared/throw-if-null.ts +7 -0
  48. package/src/lib/storage-sync/features/with-indexed-db.ts +81 -0
  49. package/src/lib/storage-sync/features/with-local-storage.ts +58 -0
  50. package/src/lib/storage-sync/internal/indexeddb.service.ts +124 -0
  51. package/src/lib/storage-sync/internal/local-storage.service.ts +19 -0
  52. package/src/lib/storage-sync/internal/models.ts +62 -0
  53. package/src/lib/storage-sync/internal/session-storage.service.ts +18 -0
  54. package/src/lib/storage-sync/tests/indexeddb.service.spec.ts +99 -0
  55. package/src/lib/storage-sync/tests/with-storage-async.spec.ts +305 -0
  56. package/src/lib/storage-sync/tests/with-storage-sync.spec.ts +273 -0
  57. package/src/lib/storage-sync/with-storage-sync.ts +236 -0
  58. package/src/lib/with-call-state.spec.ts +42 -0
  59. package/src/lib/with-call-state.ts +195 -0
  60. package/src/lib/with-conditional.spec.ts +125 -0
  61. package/src/lib/with-conditional.ts +74 -0
  62. package/src/lib/with-data-service.spec.ts +564 -0
  63. package/src/lib/with-data-service.ts +433 -0
  64. package/src/lib/with-feature-factory.spec.ts +69 -0
  65. package/src/lib/with-feature-factory.ts +56 -0
  66. package/src/lib/with-pagination.spec.ts +135 -0
  67. package/src/lib/with-pagination.ts +373 -0
  68. package/src/lib/with-redux.spec.ts +258 -0
  69. package/src/lib/with-redux.ts +387 -0
  70. package/src/lib/with-reset.spec.ts +112 -0
  71. package/src/lib/with-reset.ts +62 -0
  72. package/src/lib/with-undo-redo.spec.ts +274 -0
  73. package/src/lib/with-undo-redo.ts +200 -0
  74. package/src/test-setup.ts +6 -0
  75. package/tsconfig.json +29 -0
  76. package/tsconfig.lib.json +17 -0
  77. package/tsconfig.lib.prod.json +9 -0
  78. package/tsconfig.spec.json +17 -0
  79. package/fesm2022/angular-architects-ngrx-toolkit-redux-connector.mjs +0 -119
  80. package/fesm2022/angular-architects-ngrx-toolkit-redux-connector.mjs.map +0 -1
  81. package/fesm2022/angular-architects-ngrx-toolkit.mjs +0 -1780
  82. package/fesm2022/angular-architects-ngrx-toolkit.mjs.map +0 -1
  83. package/index.d.ts +0 -938
  84. package/redux-connector/index.d.ts +0 -59
@@ -0,0 +1,373 @@
1
+ /** With pagination comes in two flavors the first one is local pagination or in memory pagination. For example we have 2000 items which we want
2
+ * to display in a table and the response payload is small enough to be stored in the memory. But we can not display all 2000 items at once
3
+ * so we need to paginate the data. The second flavor is server side pagination where the response payload is too large to be stored in the memory
4
+ * and we need to fetch the data from the server in chunks. In the second case we 'could' also cache the data in the memory but that could lead to
5
+ * other problems like memory leaks and stale data. So we will not cache the data in the memory in the second case.
6
+ * This feature implements the local pagination.
7
+ */
8
+
9
+ import { Signal, computed } from '@angular/core';
10
+ import {
11
+ EmptyFeatureResult,
12
+ SignalStoreFeature,
13
+ signalStoreFeature,
14
+ withComputed,
15
+ withState,
16
+ } from '@ngrx/signals';
17
+ import { capitalize } from './with-data-service';
18
+
19
+ // This is a virtual page which is can be used to create a pagination control
20
+ export type Page = { label: string | number; value: number };
21
+
22
+ export type NamedPaginationServiceState<E, Collection extends string> = {
23
+ [K in Collection as `selectedPage${Capitalize<K>}Entities`]: Array<E>;
24
+ } & {
25
+ [K in Collection as `${Lowercase<K>}CurrentPage`]: number;
26
+ } & {
27
+ [K in Collection as `${Lowercase<K>}PageSize`]: number;
28
+ } & {
29
+ [K in Collection as `${Lowercase<K>}TotalCount`]: number;
30
+ } & {
31
+ [K in Collection as `${Lowercase<K>}PageCount`]: number;
32
+ } & {
33
+ [K in Collection as `${Lowercase<K>}PageNavigationArray`]: number;
34
+ } & {
35
+ [K in Collection as `${Lowercase<K>}PageNavigationArrayMax`]: number;
36
+ };
37
+
38
+ export type NamedPaginationServiceSignals<E, Collection extends string> = {
39
+ [K in Collection as `selectedPage${Capitalize<K>}Entities`]: Signal<E[]>;
40
+ } & {
41
+ [K in Collection as `${Lowercase<K>}CurrentPage`]: Signal<number>;
42
+ } & {
43
+ [K in Collection as `${Lowercase<K>}PageSize`]: Signal<number>;
44
+ } & {
45
+ [K in Collection as `${Lowercase<K>}TotalCount`]: Signal<number>;
46
+ } & {
47
+ [K in Collection as `${Lowercase<K>}PageCount`]: Signal<number>;
48
+ } & {
49
+ [K in Collection as `${Lowercase<K>}PageNavigationArray`]: Signal<Page[]>;
50
+ } & {
51
+ [K in Collection as `${Lowercase<K>}PageNavigationArrayMax`]: Signal<number>;
52
+ } & {
53
+ [K in Collection as `hasNext${Capitalize<K>}Page`]: Signal<boolean>;
54
+ } & {
55
+ [K in Collection as `hasPrevious${Capitalize<K>}Page`]: Signal<boolean>;
56
+ };
57
+
58
+ export type PaginationServiceState<E> = {
59
+ selectedPageEntities: Array<E>;
60
+ currentPage: number;
61
+ pageSize: number;
62
+ totalCount: number;
63
+ pageCount: number;
64
+ pageNavigationArray: Page[];
65
+ pageNavigationArrayMax: number;
66
+ };
67
+
68
+ export type PaginationServiceSignals<E> = {
69
+ selectedPageEntities: Signal<E[]>;
70
+ currentPage: Signal<number>;
71
+ pageSize: Signal<number>;
72
+ totalCount: Signal<number>;
73
+ pageCount: Signal<number>;
74
+ pageNavigationArray: Signal<Page[]>;
75
+ pageNavigationArrayMax: Signal<number>;
76
+ hasNextPage: Signal<boolean>;
77
+ hasPreviousPage: Signal<boolean>;
78
+ };
79
+
80
+ type PageState<Collection extends string | undefined> =
81
+ Collection extends string
82
+ ? {
83
+ [K in Collection as `${Lowercase<K>}CurrentPage`]: number;
84
+ }
85
+ : { currentPage: number };
86
+
87
+ type PageSizeState<Collection extends string | undefined> =
88
+ Collection extends string
89
+ ? {
90
+ [K in Collection as `${Lowercase<K>}PageSize`]: number;
91
+ }
92
+ : { pageSize: number };
93
+
94
+ export type SetPaginationState<
95
+ E,
96
+ Collection extends string | undefined,
97
+ > = Collection extends string
98
+ ? NamedPaginationServiceState<E, Collection>
99
+ : PaginationServiceState<E>;
100
+
101
+ export function withPagination<E, Collection extends string>(options: {
102
+ entity: E;
103
+ collection: Collection;
104
+ }): SignalStoreFeature<
105
+ EmptyFeatureResult,
106
+ EmptyFeatureResult & {
107
+ state: NamedPaginationServiceState<E, Collection>;
108
+ props: NamedPaginationServiceSignals<E, Collection>;
109
+ }
110
+ >;
111
+
112
+ export function withPagination<E>(): SignalStoreFeature<
113
+ EmptyFeatureResult,
114
+ EmptyFeatureResult & {
115
+ state: PaginationServiceState<E>;
116
+ props: PaginationServiceSignals<E>;
117
+ }
118
+ >;
119
+
120
+ export function withPagination<E, Collection extends string>(options?: {
121
+ entity: E;
122
+ collection: Collection;
123
+ }): SignalStoreFeature {
124
+ const {
125
+ pageKey,
126
+ pageSizeKey,
127
+ entitiesKey,
128
+ selectedPageEntitiesKey,
129
+ totalCountKey,
130
+ pageCountKey,
131
+ pageNavigationArrayMaxKey,
132
+ pageNavigationArrayKey,
133
+ hasNextPageKey,
134
+ hasPreviousPageKey,
135
+ } = createPaginationKeys<Collection>(options);
136
+
137
+ return signalStoreFeature(
138
+ withState({
139
+ [pageKey]: 0,
140
+ [pageSizeKey]: 10,
141
+ [pageNavigationArrayMaxKey]: 7,
142
+ }),
143
+ withComputed((store: Record<string, unknown>) => {
144
+ const entities = store[entitiesKey] as Signal<E[]>;
145
+ const page = store[pageKey] as Signal<number>;
146
+ const pageSize = store[pageSizeKey] as Signal<number>;
147
+ const pageNavigationArrayMax = store[
148
+ pageNavigationArrayMaxKey
149
+ ] as Signal<number>;
150
+
151
+ return {
152
+ // The derived enitites which are displayed on the current page
153
+ [selectedPageEntitiesKey]: computed<E[]>(() => {
154
+ const pageSizeValue = pageSize();
155
+ const pageValue = page();
156
+
157
+ return entities().slice(
158
+ pageValue * pageSizeValue,
159
+ (pageValue + 1) * pageSizeValue,
160
+ ) as E[];
161
+ }),
162
+ [totalCountKey]: computed(() => entities().length),
163
+ [pageCountKey]: computed(() => {
164
+ const totalCountValue = entities().length;
165
+ const pageSizeValue = pageSize();
166
+
167
+ if (totalCountValue === 0) {
168
+ return 0;
169
+ }
170
+
171
+ return Math.ceil(totalCountValue / pageSizeValue);
172
+ }),
173
+ [pageNavigationArrayKey]: computed(() =>
174
+ createPageArray(
175
+ page(),
176
+ pageSize(),
177
+ entities().length,
178
+ pageNavigationArrayMax(),
179
+ ),
180
+ ),
181
+
182
+ [hasNextPageKey]: computed(() => {
183
+ return page() < pageSize();
184
+ }),
185
+
186
+ [hasPreviousPageKey]: computed(() => {
187
+ return page() > 1;
188
+ }),
189
+ };
190
+ }),
191
+ );
192
+ }
193
+
194
+ export function gotoPage<Collection extends string>(
195
+ page: number,
196
+ options?: {
197
+ collection: Collection;
198
+ },
199
+ ): PageState<Collection> {
200
+ const { pageKey } = createPaginationKeys<Collection>(options);
201
+
202
+ return {
203
+ [pageKey]: page,
204
+ } as PageState<Collection>;
205
+ }
206
+
207
+ export function setPageSize<Collection extends string>(
208
+ pageSize: number,
209
+ options?: {
210
+ collection: Collection;
211
+ },
212
+ ) {
213
+ const { pageSizeKey } = createPaginationKeys<Collection>(options);
214
+
215
+ return {
216
+ [pageSizeKey]: pageSize,
217
+ } as PageSizeState<Collection>;
218
+ }
219
+
220
+ type SetPageState<Collection extends string | undefined> = (
221
+ state: PageState<Collection>,
222
+ ) => PageState<Collection>;
223
+
224
+ export function nextPage<Collection extends string>(options?: {
225
+ collection: Collection;
226
+ }): SetPageState<Collection> {
227
+ const { pageKey } = createPaginationKeys<Collection>(options);
228
+
229
+ return (state: Record<string, number>) => {
230
+ const currentPage = state[pageKey];
231
+
232
+ return { [pageKey]: currentPage + 1 } as PageState<Collection>;
233
+ };
234
+ }
235
+
236
+ export function previousPage<Collection extends string>(options?: {
237
+ collection: Collection;
238
+ }): SetPageState<Collection> {
239
+ const { pageKey } = createPaginationKeys<Collection>(options);
240
+
241
+ return (state: Record<string, number>) => {
242
+ const currentPage = state[pageKey];
243
+
244
+ return { [pageKey]: currentPage - 1 } as PageState<Collection>;
245
+ };
246
+ }
247
+
248
+ export function firstPage<Collection extends string>(options?: {
249
+ collection: Collection;
250
+ }) {
251
+ const { pageKey } = createPaginationKeys<Collection>(options);
252
+
253
+ return { [pageKey]: 0 } as PageState<Collection>;
254
+ }
255
+
256
+ export function setMaxPageNavigationArrayItems<E, Collection extends string>(
257
+ maxPageNavigationArrayItems: number,
258
+ options?: {
259
+ collection: Collection;
260
+ },
261
+ ): Partial<SetPaginationState<E, Collection>> {
262
+ const { pageNavigationArrayMaxKey } =
263
+ createPaginationKeys<Collection>(options);
264
+
265
+ return {
266
+ [pageNavigationArrayMaxKey]: maxPageNavigationArrayItems,
267
+ } as Partial<SetPaginationState<E, Collection>>;
268
+ }
269
+
270
+ function createPaginationKeys<Collection extends string>(
271
+ options: { collection: Collection } | undefined,
272
+ ) {
273
+ const entitiesKey = options?.collection
274
+ ? `${options.collection}Entities`
275
+ : 'entities';
276
+
277
+ const selectedPageEntitiesKey = options?.collection
278
+ ? `selectedPage${capitalize(options?.collection)}Entities`
279
+ : 'selectedPageEntities';
280
+
281
+ const pageKey = options?.collection
282
+ ? `${options.collection}CurrentPage`
283
+ : 'currentPage';
284
+
285
+ const pageSizeKey = options?.collection
286
+ ? `${options.collection}PageSize`
287
+ : 'pageSize';
288
+
289
+ const totalCountKey = options?.collection
290
+ ? `${options.collection}TotalCount`
291
+ : 'totalCount';
292
+
293
+ const pageCountKey = options?.collection
294
+ ? `${options.collection}PageCount`
295
+ : 'pageCount';
296
+
297
+ const pageNavigationArrayMaxKey = options?.collection
298
+ ? `${options.collection}PageNavigationArrayMax`
299
+ : 'pageNavigationArrayMax';
300
+
301
+ const pageNavigationArrayKey = options?.collection
302
+ ? `${options.collection}PageNavigationArray`
303
+ : 'pageNavigationArray';
304
+
305
+ const hasNextPageKey = options?.collection
306
+ ? `hasNext${capitalize(options.collection)}Page`
307
+ : 'hasNextPage';
308
+
309
+ const hasPreviousPageKey = options?.collection
310
+ ? `hasPrevious${capitalize(options.collection)}Page`
311
+ : 'hasPreviousPage';
312
+
313
+ return {
314
+ pageKey,
315
+ pageSizeKey,
316
+ entitiesKey,
317
+ selectedPageEntitiesKey,
318
+ totalCountKey,
319
+ pageCountKey,
320
+ pageNavigationArrayKey,
321
+ pageNavigationArrayMaxKey,
322
+ hasNextPageKey,
323
+ hasPreviousPageKey,
324
+ };
325
+ }
326
+
327
+ export function createPageArray(
328
+ currentPage: number,
329
+ itemsPerPage: number,
330
+ totalItems: number,
331
+ paginationRange: number,
332
+ ): Page[] {
333
+ // Convert paginationRange to number in case it's a string
334
+ paginationRange = +paginationRange;
335
+
336
+ // Calculate total number of pages
337
+ const totalPages = Math.max(Math.ceil(totalItems / itemsPerPage), 1);
338
+ const halfWay = Math.ceil(paginationRange / 2);
339
+
340
+ const isStart = currentPage <= halfWay;
341
+ const isEnd = totalPages - halfWay < currentPage;
342
+ const isMiddle = !isStart && !isEnd;
343
+
344
+ const ellipsesNeeded = paginationRange < totalPages;
345
+ const pages: Page[] = [];
346
+
347
+ for (let i = 1; i <= totalPages && i <= paginationRange; i++) {
348
+ let pageNumber = i;
349
+
350
+ if (i === paginationRange) {
351
+ pageNumber = totalPages;
352
+ } else if (ellipsesNeeded) {
353
+ if (isEnd) {
354
+ pageNumber = totalPages - paginationRange + i;
355
+ } else if (isMiddle) {
356
+ pageNumber = currentPage - halfWay + i;
357
+ }
358
+ }
359
+
360
+ const openingEllipsesNeeded = i === 2 && (isMiddle || isEnd);
361
+ const closingEllipsesNeeded =
362
+ i === paginationRange - 1 && (isMiddle || isStart);
363
+
364
+ const label =
365
+ ellipsesNeeded && (openingEllipsesNeeded || closingEllipsesNeeded)
366
+ ? '...'
367
+ : pageNumber;
368
+
369
+ pages.push({ label, value: pageNumber });
370
+ }
371
+
372
+ return pages;
373
+ }
@@ -0,0 +1,258 @@
1
+ import {
2
+ HttpClient,
3
+ HttpParams,
4
+ provideHttpClient,
5
+ } from '@angular/common/http';
6
+ import {
7
+ HttpTestingController,
8
+ provideHttpClientTesting,
9
+ } from '@angular/common/http/testing';
10
+ import { inject } from '@angular/core';
11
+ import { TestBed } from '@angular/core/testing';
12
+ import { patchState, signalStore, withMethods, withState } from '@ngrx/signals';
13
+ import { map, switchMap } from 'rxjs';
14
+ import {
15
+ createEffects,
16
+ createReducer,
17
+ noPayload,
18
+ payload,
19
+ withRedux,
20
+ } from './with-redux';
21
+
22
+ interface Flight {
23
+ id: number;
24
+ from: string;
25
+ to: string;
26
+ delayed: boolean;
27
+ date: Date;
28
+ }
29
+
30
+ let currentId = 1;
31
+
32
+ const createFlight = (flight: Partial<Flight> = {}) => {
33
+ return {
34
+ ...{
35
+ id: currentId++,
36
+ from: 'Vienna',
37
+ to: 'London',
38
+ delayed: false,
39
+ date: new Date(2024, 0, 1),
40
+ },
41
+ ...flight,
42
+ };
43
+ };
44
+
45
+ describe('with redux', () => {
46
+ it('should load flights', () => {
47
+ TestBed.configureTestingModule({
48
+ providers: [provideHttpClient(), provideHttpClientTesting()],
49
+ });
50
+
51
+ TestBed.runInInjectionContext(() => {
52
+ const controller = TestBed.inject(HttpTestingController);
53
+ const FlightsStore = signalStore(
54
+ withState({ flights: [] as Flight[] }),
55
+ withRedux({
56
+ actions: {
57
+ public: {
58
+ loadFlights: payload<{ from: string; to: string }>(),
59
+ delayFirst: noPayload,
60
+ },
61
+ private: {
62
+ flightsLoaded: payload<{ flights: Flight[] }>(),
63
+ },
64
+ },
65
+
66
+ reducer: (actions, on) => {
67
+ on(actions.flightsLoaded, (state, { flights }) => {
68
+ patchState(state, { flights });
69
+ });
70
+ },
71
+
72
+ effects: (actions, create) => {
73
+ const httpClient = inject(HttpClient);
74
+
75
+ return {
76
+ loadFlights$: create(actions.loadFlights).pipe(
77
+ switchMap(({ from, to }) => {
78
+ return httpClient.get<Flight[]>(
79
+ 'https://www.angulararchitects.io',
80
+ {
81
+ params: new HttpParams().set('from', from).set('to', to),
82
+ },
83
+ );
84
+ }),
85
+ map((flights) => actions.flightsLoaded({ flights })),
86
+ ),
87
+ };
88
+ },
89
+ }),
90
+ );
91
+
92
+ const flightsStore = new FlightsStore();
93
+ flightsStore.loadFlights({ from: 'Vienna', to: 'London' });
94
+ const flight = createFlight();
95
+ controller
96
+ .expectOne((req) =>
97
+ req.url.startsWith('https://www.angulararchitects.io'),
98
+ )
99
+ .flush([flight]);
100
+
101
+ expect(flightsStore.flights()).toEqual([flight]);
102
+
103
+ controller.verify();
104
+ });
105
+ });
106
+
107
+ it('should allow a noPayload action to call without parameters', () => {
108
+ const FlightsStore = signalStore(
109
+ withState({ flights: [] as Flight[] }),
110
+ withRedux({
111
+ actions: {
112
+ init: noPayload,
113
+ },
114
+ reducer() {
115
+ return {};
116
+ },
117
+ effects() {
118
+ return {};
119
+ },
120
+ }),
121
+ );
122
+
123
+ const flightStore = TestBed.configureTestingModule({
124
+ providers: [FlightsStore],
125
+ }).inject(FlightsStore);
126
+
127
+ flightStore.init();
128
+ });
129
+
130
+ it('should allow multiple effects listening to the same action', () => {
131
+ const FlightsStore = signalStore(
132
+ withState({ flights: [] as Flight[], effect1: false, effect2: false }),
133
+ withRedux({
134
+ actions: {
135
+ init: noPayload,
136
+ updateEffect1: payload<{ value: boolean }>(),
137
+ updateEffect2: payload<{ value: boolean }>(),
138
+ },
139
+ reducer(actions, on) {
140
+ on(actions.updateEffect1, (state, { value }) => {
141
+ patchState(state, { effect1: value });
142
+ });
143
+
144
+ on(actions.updateEffect2, (state, { value }) => {
145
+ patchState(state, { effect2: value });
146
+ });
147
+ },
148
+ effects(actions, create) {
149
+ return {
150
+ init1$: create(actions.init).pipe(
151
+ map(() => actions.updateEffect1({ value: true })),
152
+ ),
153
+ init2$: create(actions.init).pipe(
154
+ map(() => actions.updateEffect2({ value: true })),
155
+ ),
156
+ };
157
+ },
158
+ }),
159
+ );
160
+
161
+ const flightStore = TestBed.configureTestingModule({
162
+ providers: [FlightsStore],
163
+ }).inject(FlightsStore);
164
+
165
+ flightStore.init();
166
+
167
+ expect(flightStore.effect1()).toBe(true);
168
+ expect(flightStore.effect2()).toBe(true);
169
+ });
170
+
171
+ it('should be possible to separate actions, reducer and effects', () => {
172
+ interface FlightState {
173
+ flights: Flight[];
174
+ effect1: boolean;
175
+ effect2: boolean;
176
+ }
177
+
178
+ const initialState: FlightState = {
179
+ flights: [],
180
+ effect1: false,
181
+ effect2: false,
182
+ };
183
+
184
+ const actions = {
185
+ init: noPayload,
186
+ updateEffect1: payload<{ value: boolean }>(),
187
+ updateEffect2: payload<{ value: boolean }>(),
188
+ };
189
+
190
+ const effects = createEffects(actions, (actions, create) => {
191
+ return {
192
+ init1$: create(actions.init).pipe(
193
+ map(() => actions.updateEffect1({ value: true })),
194
+ ),
195
+ init2$: create(actions.init).pipe(
196
+ map(() => actions.updateEffect2({ value: true })),
197
+ ),
198
+ };
199
+ });
200
+
201
+ const reducer = createReducer<FlightState, typeof actions>(
202
+ (actions, on) => {
203
+ on(actions.updateEffect1, (state, { value }) => {
204
+ patchState(state, { effect1: value });
205
+ });
206
+
207
+ on(actions.updateEffect2, (state, { value }) => {
208
+ patchState(state, { effect2: value });
209
+ });
210
+ },
211
+ );
212
+
213
+ const FlightsStore = signalStore(
214
+ withState(initialState),
215
+ withRedux({
216
+ actions,
217
+ effects,
218
+ reducer,
219
+ }),
220
+ );
221
+
222
+ const flightStore = TestBed.configureTestingModule({
223
+ providers: [FlightsStore],
224
+ }).inject(FlightsStore);
225
+
226
+ flightStore.init();
227
+
228
+ expect(flightStore.effect1()).toBe(true);
229
+ expect(flightStore.effect2()).toBe(true);
230
+ });
231
+
232
+ it('should not override methods defined before', () => {
233
+ const FlightsStore = signalStore(
234
+ withMethods(() => ({
235
+ sayHi() {
236
+ return 'hi';
237
+ },
238
+ })),
239
+ withRedux({
240
+ actions: {
241
+ init: noPayload,
242
+ },
243
+ reducer() {
244
+ return {};
245
+ },
246
+ effects() {
247
+ return {};
248
+ },
249
+ }),
250
+ );
251
+
252
+ const flightStore = TestBed.configureTestingModule({
253
+ providers: [FlightsStore],
254
+ }).inject(FlightsStore);
255
+
256
+ expect(flightStore.sayHi()).toBe('hi');
257
+ });
258
+ });