@angular-architects/ngrx-toolkit 0.0.7 → 0.1.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 -0
- package/jest.config.ts +22 -0
- package/ng-package.json +7 -0
- package/package.json +3 -16
- package/project.json +37 -0
- package/{index.d.ts → src/index.ts} +3 -2
- package/src/lib/assertions/assertions.ts +9 -0
- package/src/lib/redux-connector/create-redux.ts +94 -0
- package/{lib/redux-connector/index.d.ts → src/lib/redux-connector/index.ts} +1 -0
- package/src/lib/redux-connector/model.ts +67 -0
- package/{lib/redux-connector/rxjs-interop/index.d.ts → src/lib/redux-connector/rxjs-interop/index.ts} +1 -0
- package/src/lib/redux-connector/rxjs-interop/redux-method.ts +61 -0
- package/src/lib/redux-connector/signal-redux-store.ts +54 -0
- package/src/lib/redux-connector/util.ts +22 -0
- package/src/lib/shared/empty.ts +2 -0
- package/src/lib/with-call-state.spec.ts +24 -0
- package/src/lib/with-call-state.ts +136 -0
- package/src/lib/with-data-service.ts +312 -0
- package/src/lib/with-devtools.spec.ts +157 -0
- package/src/lib/with-devtools.ts +128 -0
- package/src/lib/with-redux.spec.ts +100 -0
- package/src/lib/with-redux.ts +261 -0
- package/src/lib/with-storage-sync.spec.ts +237 -0
- package/src/lib/with-storage-sync.ts +160 -0
- package/src/lib/with-undo-redo.ts +184 -0
- package/src/test-setup.ts +8 -0
- package/tsconfig.json +29 -0
- package/tsconfig.lib.json +17 -0
- package/tsconfig.lib.prod.json +9 -0
- package/tsconfig.spec.json +16 -0
- package/esm2022/angular-architects-ngrx-toolkit.mjs +0 -5
- package/esm2022/index.mjs +0 -9
- package/esm2022/lib/assertions/assertions.mjs +0 -6
- package/esm2022/lib/redux-connector/create-redux.mjs +0 -41
- package/esm2022/lib/redux-connector/index.mjs +0 -2
- package/esm2022/lib/redux-connector/model.mjs +0 -2
- package/esm2022/lib/redux-connector/rxjs-interop/index.mjs +0 -2
- package/esm2022/lib/redux-connector/rxjs-interop/redux-method.mjs +0 -22
- package/esm2022/lib/redux-connector/signal-redux-store.mjs +0 -43
- package/esm2022/lib/redux-connector/util.mjs +0 -13
- package/esm2022/lib/shared/empty.mjs +0 -2
- package/esm2022/lib/with-call-state.mjs +0 -58
- package/esm2022/lib/with-data-service.mjs +0 -161
- package/esm2022/lib/with-devtools.mjs +0 -79
- package/esm2022/lib/with-redux.mjs +0 -95
- package/esm2022/lib/with-storage-sync.mjs +0 -56
- package/esm2022/lib/with-undo-redo.mjs +0 -93
- package/fesm2022/angular-architects-ngrx-toolkit.mjs +0 -653
- package/fesm2022/angular-architects-ngrx-toolkit.mjs.map +0 -1
- package/lib/assertions/assertions.d.ts +0 -2
- package/lib/redux-connector/create-redux.d.ts +0 -13
- package/lib/redux-connector/model.d.ts +0 -36
- package/lib/redux-connector/rxjs-interop/redux-method.d.ts +0 -11
- package/lib/redux-connector/signal-redux-store.d.ts +0 -11
- package/lib/redux-connector/util.d.ts +0 -5
- package/lib/shared/empty.d.ts +0 -1
- package/lib/with-call-state.d.ts +0 -56
- package/lib/with-data-service.d.ts +0 -115
- package/lib/with-devtools.d.ts +0 -32
- package/lib/with-redux.d.ts +0 -57
- package/lib/with-storage-sync.d.ts +0 -58
- package/lib/with-undo-redo.d.ts +0 -55
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": ["../../.eslintrc.json"],
|
|
3
|
+
"ignorePatterns": ["!**/*"],
|
|
4
|
+
"overrides": [
|
|
5
|
+
{
|
|
6
|
+
"files": ["*.ts"],
|
|
7
|
+
"extends": [
|
|
8
|
+
"plugin:@nx/angular",
|
|
9
|
+
"plugin:@angular-eslint/template/process-inline-templates"
|
|
10
|
+
],
|
|
11
|
+
"rules": {
|
|
12
|
+
"@angular-eslint/directive-selector": [
|
|
13
|
+
"error",
|
|
14
|
+
{
|
|
15
|
+
"type": "attribute",
|
|
16
|
+
"prefix": "ngrxToolkit",
|
|
17
|
+
"style": "camelCase"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"@angular-eslint/component-selector": [
|
|
21
|
+
"error",
|
|
22
|
+
{
|
|
23
|
+
"type": "element",
|
|
24
|
+
"prefix": "ngrx-toolkit",
|
|
25
|
+
"style": "kebab-case"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"files": ["*.html"],
|
|
32
|
+
"extends": ["plugin:@nx/angular-template"],
|
|
33
|
+
"rules": {}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"files": ["*.json"],
|
|
37
|
+
"parser": "jsonc-eslint-parser",
|
|
38
|
+
"rules": {
|
|
39
|
+
"@nx/dependency-checks": "error"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
package/jest.config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
export default {
|
|
3
|
+
displayName: 'ngrx-toolkit',
|
|
4
|
+
preset: '../../jest.preset.js',
|
|
5
|
+
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
|
6
|
+
coverageDirectory: '../../coverage/libs/ngrx-toolkit',
|
|
7
|
+
transform: {
|
|
8
|
+
'^.+\\.(ts|mjs|js|html)$': [
|
|
9
|
+
'jest-preset-angular',
|
|
10
|
+
{
|
|
11
|
+
tsconfig: '<rootDir>/tsconfig.spec.json',
|
|
12
|
+
stringifyContentPathRegex: '\\.(html|svg)$',
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
|
17
|
+
snapshotSerializers: [
|
|
18
|
+
'jest-preset-angular/build/serializers/no-ng-attributes',
|
|
19
|
+
'jest-preset-angular/build/serializers/ng-snapshot',
|
|
20
|
+
'jest-preset-angular/build/serializers/html-comment',
|
|
21
|
+
],
|
|
22
|
+
};
|
package/ng-package.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-architects/ngrx-toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "GitHub",
|
|
@@ -14,18 +14,5 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"tslib": "^2.3.0"
|
|
16
16
|
},
|
|
17
|
-
"sideEffects": false
|
|
18
|
-
|
|
19
|
-
"typings": "index.d.ts",
|
|
20
|
-
"exports": {
|
|
21
|
-
"./package.json": {
|
|
22
|
-
"default": "./package.json"
|
|
23
|
-
},
|
|
24
|
-
".": {
|
|
25
|
-
"types": "./index.d.ts",
|
|
26
|
-
"esm2022": "./esm2022/angular-architects-ngrx-toolkit.mjs",
|
|
27
|
-
"esm": "./esm2022/angular-architects-ngrx-toolkit.mjs",
|
|
28
|
-
"default": "./fesm2022/angular-architects-ngrx-toolkit.mjs"
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
17
|
+
"sideEffects": false
|
|
18
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ngrx-toolkit",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "libs/ngrx-toolkit/src",
|
|
5
|
+
"prefix": "ngrx-toolkit",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"projectType": "library",
|
|
8
|
+
"targets": {
|
|
9
|
+
"build": {
|
|
10
|
+
"executor": "@nx/angular:package",
|
|
11
|
+
"outputs": ["{workspaceRoot}/dist/{projectRoot}"],
|
|
12
|
+
"options": {
|
|
13
|
+
"project": "libs/ngrx-toolkit/ng-package.json"
|
|
14
|
+
},
|
|
15
|
+
"configurations": {
|
|
16
|
+
"production": {
|
|
17
|
+
"tsConfig": "libs/ngrx-toolkit/tsconfig.lib.prod.json"
|
|
18
|
+
},
|
|
19
|
+
"development": {
|
|
20
|
+
"tsConfig": "libs/ngrx-toolkit/tsconfig.lib.json"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"defaultConfiguration": "production"
|
|
24
|
+
},
|
|
25
|
+
"test": {
|
|
26
|
+
"executor": "@nx/jest:jest",
|
|
27
|
+
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
|
28
|
+
"options": {
|
|
29
|
+
"jestConfig": "libs/ngrx-toolkit/jest.config.ts"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"lint": {
|
|
33
|
+
"executor": "@nx/eslint:lint",
|
|
34
|
+
"outputs": ["{options.outputFile}"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export { withDevtools, patchState, Action } from './lib/with-devtools';
|
|
2
2
|
export * from './lib/with-redux';
|
|
3
|
+
|
|
3
4
|
export * from './lib/with-call-state';
|
|
4
5
|
export * from './lib/with-undo-redo';
|
|
5
|
-
export * from './lib/with-data-service'
|
|
6
|
+
export * from './lib/with-data-service'
|
|
6
7
|
export { withStorageSync, SyncConfig } from './lib/with-storage-sync';
|
|
7
8
|
export * from './lib/redux-connector';
|
|
8
|
-
export * from './lib/redux-connector/rxjs-interop';
|
|
9
|
+
export * from './lib/redux-connector/rxjs-interop';
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ENVIRONMENT_INITIALIZER, inject, makeEnvironmentProviders } from "@angular/core";
|
|
2
|
+
import { ActionCreator, ActionType } from "@ngrx/store/src/models";
|
|
3
|
+
import { CreateReduxState, ExtractActionTypes, MapperTypes, Store } from "./model";
|
|
4
|
+
import { SignalReduxStore, injectReduxDispatch } from "./signal-redux-store";
|
|
5
|
+
import { capitalize, isActionCreator } from "./util";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export function mapAction<
|
|
9
|
+
Creators extends readonly ActionCreator[]
|
|
10
|
+
>(
|
|
11
|
+
...args: [
|
|
12
|
+
...creators: Creators,
|
|
13
|
+
storeMethod: (action: ActionType<Creators[number]>) => unknown
|
|
14
|
+
]
|
|
15
|
+
): MapperTypes<Creators>;
|
|
16
|
+
export function mapAction<
|
|
17
|
+
Creators extends readonly ActionCreator[],
|
|
18
|
+
T
|
|
19
|
+
>(
|
|
20
|
+
...args: [
|
|
21
|
+
...creators: Creators,
|
|
22
|
+
storeMethod: (action: ActionType<Creators[number]>, resultMethod: (input: T) => unknown) => unknown,
|
|
23
|
+
resultMethod: (input: T) => unknown
|
|
24
|
+
]
|
|
25
|
+
): MapperTypes<Creators>;
|
|
26
|
+
export function mapAction<
|
|
27
|
+
Creators extends readonly ActionCreator[]
|
|
28
|
+
>(
|
|
29
|
+
...args: [
|
|
30
|
+
...creators: Creators,
|
|
31
|
+
storeMethod: (action: ActionType<Creators[number]>) => unknown,
|
|
32
|
+
resultMethod?: (input: unknown) => unknown
|
|
33
|
+
]
|
|
34
|
+
): MapperTypes<Creators> {
|
|
35
|
+
let resultMethod = args.pop() as unknown as ((input: unknown) => unknown ) | undefined;
|
|
36
|
+
let storeMethod = args.pop() as unknown as (action: ActionType<Creators[number]>) => unknown;
|
|
37
|
+
|
|
38
|
+
if (isActionCreator(storeMethod)) {
|
|
39
|
+
args.push(storeMethod);
|
|
40
|
+
storeMethod = resultMethod || storeMethod;
|
|
41
|
+
resultMethod = undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const types = (args as unknown as Creators).map(
|
|
45
|
+
(creator) => creator.type
|
|
46
|
+
) as unknown as ExtractActionTypes<Creators>;
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
types,
|
|
50
|
+
storeMethod,
|
|
51
|
+
resultMethod
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function withActionMappers(
|
|
56
|
+
...mappers: MapperTypes<ActionCreator<any, any>[]>[]
|
|
57
|
+
): MapperTypes<ActionCreator<any, any>[]>[] {
|
|
58
|
+
return mappers;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createReduxState<
|
|
62
|
+
StoreName extends string,
|
|
63
|
+
STORE extends Store
|
|
64
|
+
>(
|
|
65
|
+
storeName: StoreName,
|
|
66
|
+
signalStore: STORE,
|
|
67
|
+
withActionMappers: (store: InstanceType<STORE>) => MapperTypes<ActionCreator<any, any>[]>[],
|
|
68
|
+
): CreateReduxState<StoreName, STORE> {
|
|
69
|
+
const isRootProvider = (signalStore as any)?.ɵprov?.providedIn === 'root';
|
|
70
|
+
return {
|
|
71
|
+
[`provide${capitalize(storeName)}Store`]: (connectReduxDevtools = false) => makeEnvironmentProviders([
|
|
72
|
+
isRootProvider? [] : signalStore,
|
|
73
|
+
{
|
|
74
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
75
|
+
multi: true,
|
|
76
|
+
useFactory: (
|
|
77
|
+
signalReduxStore = inject(SignalReduxStore),
|
|
78
|
+
store = inject(signalStore)
|
|
79
|
+
) => () => {
|
|
80
|
+
if (connectReduxDevtools) {
|
|
81
|
+
// addStoreToReduxDevtools(store, storeName, false);
|
|
82
|
+
}
|
|
83
|
+
signalReduxStore.connectFeatureStore(
|
|
84
|
+
withActionMappers(store)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
]),
|
|
89
|
+
[`inject${capitalize(storeName)}Store`]: () => Object.assign(
|
|
90
|
+
inject(signalStore),
|
|
91
|
+
{ dispatch: injectReduxDispatch() }
|
|
92
|
+
)
|
|
93
|
+
} as CreateReduxState<StoreName, STORE>;
|
|
94
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { EnvironmentProviders, Signal, Type } from "@angular/core";
|
|
2
|
+
import { DeepSignal } from "@ngrx/signals/src/deep-signal";
|
|
3
|
+
import { SignalStoreFeatureResult } from "@ngrx/signals/src/signal-store-models";
|
|
4
|
+
import { StateSignal } from "@ngrx/signals/src/state-signal";
|
|
5
|
+
import { Action, ActionCreator, ActionType, Prettify } from "@ngrx/store/src/models";
|
|
6
|
+
import { Observable, Unsubscribable } from "rxjs";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export type IncludePropType<T, V, WithNevers =
|
|
10
|
+
{ [K in keyof T]: Exclude<T[K], undefined> extends V
|
|
11
|
+
? T[K] extends Record<string, unknown>
|
|
12
|
+
? IncludePropType<T[K], V>
|
|
13
|
+
: T[K]
|
|
14
|
+
: never
|
|
15
|
+
}> = Prettify<
|
|
16
|
+
Pick<
|
|
17
|
+
WithNevers,
|
|
18
|
+
{ [K in keyof WithNevers]: WithNevers[K] extends never
|
|
19
|
+
? never
|
|
20
|
+
: K extends string
|
|
21
|
+
? K
|
|
22
|
+
: never
|
|
23
|
+
}[keyof WithNevers]
|
|
24
|
+
>
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
export type Store = Type<Record<string, unknown> & StateSignal<SignalStoreFeatureResult['state']>>;
|
|
28
|
+
|
|
29
|
+
export type CreateReduxState<
|
|
30
|
+
StoreName extends string,
|
|
31
|
+
STORE extends Store
|
|
32
|
+
> = {
|
|
33
|
+
[K in StoreName as `provide${Capitalize<K>}Store`]: (connectReduxDevtools?: boolean) => EnvironmentProviders
|
|
34
|
+
} & {
|
|
35
|
+
[K in StoreName as `inject${Capitalize<K>}Store`]: () => InjectableReduxSlice<STORE>
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type Selectors<STORE extends Store> = IncludePropType<InstanceType<STORE>, Signal<unknown> | DeepSignal<unknown>>;
|
|
39
|
+
export type Dispatch = { dispatch: (input: Action | Observable<Action> | Signal<Action>) => Unsubscribable };
|
|
40
|
+
export type InjectableReduxSlice<STORE extends Store> = Selectors<STORE> & Dispatch;
|
|
41
|
+
|
|
42
|
+
export type ExtractActionTypes<Creators extends readonly ActionCreator[]> = {
|
|
43
|
+
[Key in keyof Creators]: Creators[Key] extends ActionCreator<infer T>
|
|
44
|
+
? T
|
|
45
|
+
: never;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export interface ActionMethod<T, V extends Action = Action> {
|
|
49
|
+
(action: V): T;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface StoreMethod<
|
|
53
|
+
Creators extends readonly ActionCreator[],
|
|
54
|
+
ResultState = unknown,
|
|
55
|
+
> {
|
|
56
|
+
(
|
|
57
|
+
action: ActionType<Creators[number]>
|
|
58
|
+
): ResultState;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface MapperTypes<
|
|
62
|
+
Creators extends readonly ActionCreator[]
|
|
63
|
+
> {
|
|
64
|
+
types: ExtractActionTypes<Creators>,
|
|
65
|
+
storeMethod: StoreMethod<Creators>,
|
|
66
|
+
resultMethod?: (...args: unknown[]) => unknown
|
|
67
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Injector, Signal, inject } from "@angular/core";
|
|
2
|
+
import { rxMethod } from "@ngrx/signals/rxjs-interop";
|
|
3
|
+
import { Observable, Unsubscribable, map, pipe } from "rxjs";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
type RxMethodInput<Input> = Input | Observable<Input> | Signal<Input>;
|
|
7
|
+
|
|
8
|
+
type RxMethod<Input, MethodInput = Input, MethodResult = unknown> = ((
|
|
9
|
+
input: RxMethodInput<Input>,
|
|
10
|
+
resultMethod: (input: MethodInput) => MethodResult
|
|
11
|
+
) => Unsubscribable) & Unsubscribable;
|
|
12
|
+
|
|
13
|
+
export function reduxMethod<Input, MethodInput = Input>(
|
|
14
|
+
generator: (source$: Observable<Input>) => Observable<MethodInput>,
|
|
15
|
+
config?: { injector?: Injector }
|
|
16
|
+
): RxMethod<Input, MethodInput>;
|
|
17
|
+
export function reduxMethod<Input, MethodInput = Input, MethodResult = unknown>(
|
|
18
|
+
generator: (source$: Observable<Input>) => Observable<MethodInput>,
|
|
19
|
+
resultMethod: (input: MethodInput) => MethodResult,
|
|
20
|
+
config?: {
|
|
21
|
+
injector?: Injector
|
|
22
|
+
}
|
|
23
|
+
): RxMethod<Input, MethodInput, MethodResult>;
|
|
24
|
+
export function reduxMethod<Input, MethodInput = Input, MethodResult = unknown>(
|
|
25
|
+
generator: (source$: Observable<Input>) => Observable<MethodInput>,
|
|
26
|
+
resultMethodOrConfig?: ((input: MethodInput) => MethodResult) | {
|
|
27
|
+
injector?: Injector
|
|
28
|
+
},
|
|
29
|
+
config?: {
|
|
30
|
+
injector?: Injector
|
|
31
|
+
}
|
|
32
|
+
): RxMethod<Input, MethodInput, MethodResult> {
|
|
33
|
+
const injector = inject(Injector);
|
|
34
|
+
|
|
35
|
+
if (typeof resultMethodOrConfig === 'function') {
|
|
36
|
+
let unsubscribable: Unsubscribable;
|
|
37
|
+
const inputResultFn = ((
|
|
38
|
+
input: RxMethodInput<Input>,
|
|
39
|
+
resultMethod = resultMethodOrConfig
|
|
40
|
+
) => {
|
|
41
|
+
|
|
42
|
+
const rxMethodWithResult = rxMethod<Input>(pipe(
|
|
43
|
+
generator,
|
|
44
|
+
map(resultMethod)
|
|
45
|
+
), {
|
|
46
|
+
...(config || {}),
|
|
47
|
+
injector: config?.injector || injector
|
|
48
|
+
});
|
|
49
|
+
const rxWithInput = rxMethodWithResult(input);
|
|
50
|
+
unsubscribable = { unsubscribe: rxWithInput.unsubscribe.bind(rxWithInput) };
|
|
51
|
+
|
|
52
|
+
return rxWithInput;
|
|
53
|
+
}) as RxMethod<Input, MethodInput, MethodResult>;
|
|
54
|
+
|
|
55
|
+
inputResultFn.unsubscribe = () => unsubscribable?.unsubscribe();
|
|
56
|
+
|
|
57
|
+
return inputResultFn;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return rxMethod<Input>(generator, resultMethodOrConfig);
|
|
61
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Injectable, inject } from "@angular/core";
|
|
2
|
+
import { rxMethod } from "@ngrx/signals/rxjs-interop";
|
|
3
|
+
import { Action, ActionCreator } from "@ngrx/store";
|
|
4
|
+
import { pipe, tap } from "rxjs";
|
|
5
|
+
import { MapperTypes } from "./model";
|
|
6
|
+
import { isUnsubscribable } from "./util";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@Injectable({
|
|
10
|
+
providedIn: 'root'
|
|
11
|
+
})
|
|
12
|
+
export class SignalReduxStore {
|
|
13
|
+
private mapperDict: Record<string, {
|
|
14
|
+
storeMethod: (...args: unknown[]) => unknown,
|
|
15
|
+
resultMethod?: (...args: unknown[]) => unknown,
|
|
16
|
+
}> = {};
|
|
17
|
+
|
|
18
|
+
dispatch = rxMethod<Action>(pipe(
|
|
19
|
+
tap((action: Action) => {
|
|
20
|
+
const callbacks = this.mapperDict[action.type];
|
|
21
|
+
if (callbacks?.storeMethod) {
|
|
22
|
+
if (
|
|
23
|
+
isUnsubscribable(callbacks.storeMethod) &&
|
|
24
|
+
callbacks.resultMethod
|
|
25
|
+
) {
|
|
26
|
+
return callbacks.storeMethod(action, (a: Action) => {
|
|
27
|
+
const resultAction = callbacks.resultMethod?.(a) as Action;
|
|
28
|
+
this.dispatch(resultAction);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return callbacks?.storeMethod(action);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return;
|
|
36
|
+
})
|
|
37
|
+
));
|
|
38
|
+
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
+
connectFeatureStore(mappers: MapperTypes<ActionCreator<any, any>[]>[]): void {
|
|
41
|
+
mappers.forEach(
|
|
42
|
+
mapper => mapper.types.forEach(
|
|
43
|
+
action => this.mapperDict[action] = {
|
|
44
|
+
storeMethod: mapper.storeMethod,
|
|
45
|
+
resultMethod: mapper.resultMethod
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function injectReduxDispatch() {
|
|
53
|
+
return inject(SignalReduxStore).dispatch;
|
|
54
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Action } from '@ngrx/store';
|
|
2
|
+
import { Unsubscribable } from 'rxjs';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export function isUnsubscribable<F extends (...args: unknown[]) => unknown>(
|
|
6
|
+
fn: F | (F & Unsubscribable)
|
|
7
|
+
): fn is F & Unsubscribable {
|
|
8
|
+
return !!(fn as any as F & Unsubscribable)?.unsubscribe;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function capitalize(str: string): string {
|
|
12
|
+
return str ? str[0].toUpperCase() + str.substring(1) : str;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isActionCreator(action: any): action is Action {
|
|
16
|
+
return (
|
|
17
|
+
typeof action === 'function' &&
|
|
18
|
+
action &&
|
|
19
|
+
action.type &&
|
|
20
|
+
typeof action.type === 'string'
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { patchState, signalStore } from '@ngrx/signals';
|
|
2
|
+
import { setLoaded, setLoading, withCallState } from 'ngrx-toolkit';
|
|
3
|
+
|
|
4
|
+
describe('withCallState', () => {
|
|
5
|
+
it('should use and update a callState', () => {
|
|
6
|
+
const DataStore = signalStore(withCallState());
|
|
7
|
+
const dataStore = new DataStore();
|
|
8
|
+
|
|
9
|
+
patchState(dataStore, setLoading());
|
|
10
|
+
|
|
11
|
+
expect(dataStore.callState()).toBe('loading');
|
|
12
|
+
expect(dataStore.loading()).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should use the callState for a collection', () => {
|
|
16
|
+
const DataStore = signalStore(withCallState({ collection: 'entities' }));
|
|
17
|
+
const dataStore = new DataStore();
|
|
18
|
+
|
|
19
|
+
patchState(dataStore, setLoaded('entities'));
|
|
20
|
+
|
|
21
|
+
expect(dataStore.entitiesCallState()).toBe('loaded');
|
|
22
|
+
expect(dataStore.entitiesLoaded()).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Signal, computed } from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
SignalStoreFeature,
|
|
4
|
+
signalStoreFeature,
|
|
5
|
+
withComputed,
|
|
6
|
+
withState,
|
|
7
|
+
} from '@ngrx/signals';
|
|
8
|
+
import { Emtpy } from './shared/empty';
|
|
9
|
+
|
|
10
|
+
export type CallState = 'init' | 'loading' | 'loaded' | { error: string };
|
|
11
|
+
|
|
12
|
+
export type NamedCallStateSlice<Collection extends string> = {
|
|
13
|
+
[K in Collection as `${K}CallState`]: CallState;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type CallStateSlice = {
|
|
17
|
+
callState: CallState
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type NamedCallStateSignals<Prop extends string> = {
|
|
21
|
+
[K in Prop as `${K}Loading`]: Signal<boolean>;
|
|
22
|
+
} & {
|
|
23
|
+
[K in Prop as `${K}Loaded`]: Signal<boolean>;
|
|
24
|
+
} & {
|
|
25
|
+
[K in Prop as `${K}Error`]: Signal<string | null>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type CallStateSignals = {
|
|
29
|
+
loading: Signal<boolean>;
|
|
30
|
+
loaded: Signal<boolean>;
|
|
31
|
+
error: Signal<string | null>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type SetCallState<Prop extends string | undefined> = Prop extends string
|
|
35
|
+
? NamedCallStateSlice<Prop>
|
|
36
|
+
: CallStateSlice;
|
|
37
|
+
|
|
38
|
+
export function getCallStateKeys(config?: { collection?: string }) {
|
|
39
|
+
const prop = config?.collection;
|
|
40
|
+
return {
|
|
41
|
+
callStateKey: prop ? `${config.collection}CallState` : 'callState',
|
|
42
|
+
loadingKey: prop ? `${config.collection}Loading` : 'loading',
|
|
43
|
+
loadedKey: prop ? `${config.collection}Loaded` : 'loaded',
|
|
44
|
+
errorKey: prop ? `${config.collection}Error` : 'error',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function withCallState<Collection extends string>(config: {
|
|
49
|
+
collection: Collection;
|
|
50
|
+
}): SignalStoreFeature<
|
|
51
|
+
{ state: Emtpy, signals: Emtpy, methods: Emtpy },
|
|
52
|
+
{
|
|
53
|
+
state: NamedCallStateSlice<Collection>,
|
|
54
|
+
signals: NamedCallStateSignals<Collection>,
|
|
55
|
+
methods: Emtpy
|
|
56
|
+
}
|
|
57
|
+
>;
|
|
58
|
+
export function withCallState(): SignalStoreFeature<
|
|
59
|
+
{ state: Emtpy, signals: Emtpy, methods: Emtpy },
|
|
60
|
+
{
|
|
61
|
+
state: CallStateSlice,
|
|
62
|
+
signals: CallStateSignals,
|
|
63
|
+
methods: Emtpy
|
|
64
|
+
}
|
|
65
|
+
>;
|
|
66
|
+
export function withCallState<Collection extends string>(config?: {
|
|
67
|
+
collection: Collection;
|
|
68
|
+
}): SignalStoreFeature {
|
|
69
|
+
const { callStateKey, errorKey, loadedKey, loadingKey } =
|
|
70
|
+
getCallStateKeys(config);
|
|
71
|
+
|
|
72
|
+
return signalStoreFeature(
|
|
73
|
+
withState({ [callStateKey]: 'init' }),
|
|
74
|
+
withComputed((state: Record<string, Signal<unknown>>) => {
|
|
75
|
+
|
|
76
|
+
const callState = state[callStateKey] as Signal<CallState>;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
[loadingKey]: computed(() => callState() === 'loading'),
|
|
80
|
+
[loadedKey]: computed(() => callState() === 'loaded'),
|
|
81
|
+
[errorKey]: computed(() => {
|
|
82
|
+
const v = callState();
|
|
83
|
+
return typeof v === 'object' ? v.error : null;
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function setLoading<Prop extends string | undefined = undefined>(
|
|
91
|
+
prop?: Prop
|
|
92
|
+
): SetCallState<Prop> {
|
|
93
|
+
if (prop) {
|
|
94
|
+
return { [`${prop}CallState`]: 'loading' } as SetCallState<Prop>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { callState: 'loading' } as SetCallState<Prop>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function setLoaded<Prop extends string | undefined = undefined>(
|
|
101
|
+
prop?: Prop
|
|
102
|
+
): SetCallState<Prop> {
|
|
103
|
+
|
|
104
|
+
if (prop) {
|
|
105
|
+
return { [`${prop}CallState`]: 'loaded' } as SetCallState<Prop>;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
return { callState: 'loaded' } as SetCallState<Prop>;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function setError<Prop extends string | undefined = undefined>(
|
|
113
|
+
error: unknown,
|
|
114
|
+
prop?: Prop,
|
|
115
|
+
): SetCallState<Prop> {
|
|
116
|
+
|
|
117
|
+
let errorMessage = '';
|
|
118
|
+
|
|
119
|
+
if (!error) {
|
|
120
|
+
errorMessage = '';
|
|
121
|
+
}
|
|
122
|
+
else if (typeof error === 'object' && 'message' in error) {
|
|
123
|
+
errorMessage = String(error.message);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
errorMessage = String(error);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if (prop) {
|
|
131
|
+
return { [`${prop}CallState`]: { error: errorMessage } } as SetCallState<Prop>;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
return { callState: { error: errorMessage } } as SetCallState<Prop>;
|
|
135
|
+
}
|
|
136
|
+
}
|