Skip to content

Commit 9ab326d

Browse files
committed
updates to listen for a specific action
1 parent 2959e40 commit 9ab326d

File tree

6 files changed

+244
-173
lines changed

6 files changed

+244
-173
lines changed

src/models/store.options.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
export type UpdateFlag = 'extend' | 'override' | 'replace';
1+
2+
export const UpdateFlags = ['extend', 'merge', 'override', 'replace'] as const;
3+
export const isUpdateFlag = (value: any): value is UpdateFlag => typeof value === 'string' && UpdateFlags.includes(value as UpdateFlag);
4+
5+
export type UpdateFlag = (typeof UpdateFlags)[number];
6+
27

38
type StoreFlags = {
49
onSet?: UpdateFlag;

src/services/store.facade.ts

Lines changed: 102 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,115 @@
1-
import { inject, Injectable, Signal } from '@angular/core';
2-
import { exhaustMap, Observable, of } from 'rxjs';
3-
import { UpdateFlag } from '../models/store.options';
1+
import { inject, Injectable, linkedSignal } from '@angular/core';
2+
import { distinctUntilChanged, exhaustMap, filter, map, Observable, of } from 'rxjs';
3+
import { isUpdateFlag, UpdateFlag } from '../models/store.options';
44
import { IStoreState } from '../models/store.state';
5-
import { DefaultActions } from '../shared/store.enums';
6-
import { ActionKey, DeepPartial, DispatchArguments, DispatchResponse, State, StateActionNames, StateData, StateFormatter, StateKey } from '../shared/store.types';
5+
import { ActionStatus, DefaultActions } from '../shared/store.enums';
6+
import { ActionKey, DeepPartial, DispatchArguments, DispatchResponse, FacadeSelection, State, StateActionNames, StateData, StateFormatter, StateKey } from '../shared/store.types';
77
import { isEmpty } from '../shared/store.utils';
88
import { StoreDispatcher } from './store.dispatcher';
99
import { StoreManager } from './store.manager';
1010

11-
12-
1311
@Injectable({ providedIn: 'root' })
1412
export class StoreFacade<States extends readonly IStoreState[] = any, Keys extends string = StateKey<States>> {
15-
cache: any = {};
16-
17-
manager = inject(StoreManager);
18-
dispatcher = inject(StoreDispatcher);
19-
20-
selectAsync<K extends Keys, S extends IStoreState = State<States, K>>(stateKey: K): Observable<StateData<S>> {
21-
return this.select(stateKey, true);
22-
}
23-
24-
select<K extends Keys, S extends IStoreState = State<States, K>>(stateKey: K): StateData<S>;
25-
select<K extends Keys, S extends IStoreState = State<States, K>>(stateKey: K, async: false): Signal<StateData<S>>;
26-
select<K extends Keys, S extends IStoreState = State<States, K>>(stateKey: K, async: true): Observable<StateData<S>>;
27-
select<K extends Keys, S extends IStoreState = State<States, K>>(stateKey: K, async: boolean | null = null) {
28-
if (async === null) {
29-
return this.manager.value(stateKey);
30-
} else if (async) {
31-
return this.manager.observable(stateKey);
32-
} else {
33-
return this.manager.signal<K>(stateKey);
13+
cache: any = {};
14+
15+
manager = inject(StoreManager);
16+
dispatcher = inject(StoreDispatcher);
17+
18+
select<K extends Keys, S extends State<States, K>, D extends StateData<S>, P extends keyof D>(stateKey: K): FacadeSelection<false, D, ''>;
19+
select<K extends Keys, S extends State<States, K>, D extends StateData<S>, P extends keyof D>(stateKey: K, arg1: false): FacadeSelection<false, D, ''>;
20+
select<K extends Keys, S extends State<States, K>, D extends StateData<S>, P extends keyof D>(stateKey: K, arg1: true): FacadeSelection<true, D, ''>;
21+
select<K extends Keys, S extends State<States, K>, D extends StateData<S>, P extends keyof D>(stateKey: K, arg1: P): FacadeSelection<false, D, P>;
22+
select<K extends Keys, S extends State<States, K>, D extends StateData<S>, P extends keyof D>(stateKey: K, arg1: P, arg2: false): FacadeSelection<false, D, P>;
23+
select<K extends Keys, S extends State<States, K>, D extends StateData<S>, P extends keyof D>(stateKey: K, arg1: P, arg2: true): FacadeSelection<true, D, P>;
24+
select(stateKey: string, arg1?: string | boolean, arg2?: boolean) {
25+
let state = this.manager.value<any>(stateKey);
26+
const propertKey = typeof arg1 === 'string' && arg1;
27+
const full = arg1 === true || arg2 === true;
28+
29+
if (full) {
30+
state = {
31+
...state,
32+
asObservable: () => this.manager.observable(stateKey),
33+
asSignal: () => this.manager.signal(stateKey),
34+
};
35+
}
36+
37+
if (propertKey) {
38+
const value = state[propertKey];
39+
if (full) {
40+
return {
41+
value,
42+
asObservable: () =>
43+
state.asObservable().pipe(
44+
map((data: any) => data?.[arg1]),
45+
filter(value => !!(value ?? false)),
46+
distinctUntilChanged(),
47+
),
48+
asSignal: () => linkedSignal(() => state.asSignal()()[arg1]),
49+
} as any;
50+
}
51+
return value;
52+
}
53+
return state;
54+
}
55+
56+
dispatch<K extends Keys, S extends IStoreState = State<States, K>, N extends string = StateActionNames<S>>(
57+
stateKey: K,
58+
actionKey: ActionKey<S, N>,
59+
...[payload, flag]: DispatchArguments<S, N>
60+
): Observable<DispatchResponse<S, N>> {
61+
const action = this.dispatcher.dispatch(stateKey, actionKey, payload, flag);
62+
return this.manager.observable(stateKey, action);
63+
}
64+
65+
on<K extends Keys, S extends IStoreState = State<States, K>, N extends string = StateActionNames<S>>(stateKey: K, actionKey: ActionKey<S, N>, status = ActionStatus.SUCCESS, observe?: 'state' | 'action'): Observable<DispatchResponse<S, N>> {
66+
return this.manager.listen(stateKey, actionKey, status, observe);
67+
}
68+
69+
set<K extends Keys, S extends State<States, K>, D extends StateData<S>, P extends keyof D>(stateKey: K, arg1: P | DeepPartial<D>, arg2?: DeepPartial<D>[P] | UpdateFlag, arg3?: UpdateFlag) {
70+
const payload = (typeof arg1 === 'string' ? { [arg1]: arg2 } : arg1) as DeepPartial<D>;
71+
const flag = isUpdateFlag(arg2) ? arg2 : arg3;
72+
return this.dispatch(stateKey, DefaultActions.SET, payload, flag);
3473
}
35-
}
36-
37-
dispatch<K extends Keys, S extends IStoreState = State<States, K>, N extends string = StateActionNames<S>>(stateKey: K, actionKey: ActionKey<S, N>, ...[payload, flag]: DispatchArguments<S, N>): Observable<DispatchResponse<S, N>> {
38-
const action = this.dispatcher.dispatch(stateKey, actionKey, payload, flag);
39-
return this.manager.observable(stateKey, action);
40-
}
41-
42-
get<K extends Keys>(stateKey: K) {
43-
return this.dispatch(stateKey, DefaultActions.GET);
44-
}
45-
46-
set<K extends Keys, S extends State<States, K>>(stateKey: K, payload: DeepPartial<StateData<S>>, flag?: UpdateFlag) {
47-
return this.dispatch(stateKey, DefaultActions.SET, payload, flag);
48-
}
49-
50-
unset<K extends Keys>(stateKey: K) {
51-
return this.dispatch(stateKey, DefaultActions.UNSET);
52-
}
53-
54-
extend<K extends Keys, S extends IStoreState = State<States, K>>(stateKey: K, payload: DeepPartial<StateData<S>>, flag?: UpdateFlag) {
55-
return this.dispatch(stateKey, DefaultActions.EXTEND, payload, flag);
56-
}
57-
58-
init<K extends Keys, S extends IStoreState = State<States, K>>(stateKey: K, getter: Observable<StateData<S>>, formatter: StateFormatter<S>, force = false) {
59-
formatter = formatter || ((payload: StateData<S>) => payload);
60-
const stateData = this.select(stateKey);
61-
if (!isEmpty(stateData) && !force) {
62-
getter = of(stateData);
74+
75+
unset<K extends Keys>(stateKey: K) {
76+
return this.dispatch(stateKey, DefaultActions.UNSET);
77+
}
78+
79+
extend<K extends Keys, S extends IStoreState = State<States, K>>(stateKey: K, payload: DeepPartial<StateData<S>>, flag?: UpdateFlag) {
80+
return this.dispatch(stateKey, DefaultActions.EXTEND, payload, flag);
81+
}
82+
83+
init<K extends Keys, S extends IStoreState = State<States, K>>(stateKey: K, getter: Observable<StateData<S>>, formatter?: StateFormatter<S>, force = false) {
84+
formatter = formatter || ((payload: StateData<S>) => payload);
85+
const stateData = this.select(stateKey);
86+
if (!isEmpty(stateData) && !force) {
87+
getter = of(stateData);
88+
}
89+
90+
return getter.pipe(
91+
exhaustMap((payload: DeepPartial<StateData<S>>) => {
92+
return this.set(stateKey, formatter(payload as any));
93+
}),
94+
);
95+
}
96+
97+
clear<K extends Keys>(exclude: K[] = []) {
98+
Object.keys(this.dispatcher.states).forEach(stateKey => {
99+
if (exclude.includes(stateKey as K)) {
100+
this.cache[stateKey as K] = this.select(stateKey as Keys);
101+
} else {
102+
this.unset(stateKey as K);
103+
}
104+
});
63105
}
64106

65-
return getter.pipe(exhaustMap((payload: DeepPartial<StateData<S>>) => {
66-
return this.set(stateKey, formatter(payload));
67-
}));
68-
}
69-
70-
clear<K extends Keys>(exclude: K[] = []) {
71-
Object.keys(this.dispatcher.states).forEach((stateKey) => {
72-
if (exclude.includes(stateKey as K)) {
73-
this.cache[stateKey as K] = this.select(stateKey as Keys);
74-
} else {
75-
this.unset(stateKey as K);
76-
}
77-
});
78-
}
79-
80-
restore() {
81-
Object.keys(this.cache).forEach((stateKey) => {
82-
this.set(stateKey as Keys, this.cache[stateKey]);
83-
delete this.cache[stateKey];
84-
});
85-
}
107+
restore() {
108+
Object.keys(this.cache).forEach(stateKey => {
109+
this.set(stateKey as Keys, this.cache[stateKey]);
110+
delete this.cache[stateKey];
111+
});
112+
}
86113
}
87114

88115
export { StoreFacade as _StoreFacade };

src/services/store.manager.ts

Lines changed: 93 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -4,93 +4,108 @@ import { Observable, distinctUntilKeyChanged, filter, map, take } from 'rxjs';
44
import { StoreAction } from '../models/store.action';
55
import { StoreOptions } from '../models/store.options';
66
import { StoreState } from '../models/store.state';
7-
import { ActionStatus, StateKeys } from '../shared/store.enums';
7+
import { ActionStatus, DefaultActions, StateKeys } from '../shared/store.enums';
88
import { STORE_OPTIONS, STORE_STATES } from '../shared/store.providers';
99
import { uniqueBy } from '../shared/store.utils';
1010
import { StoreDispatcher } from './store.dispatcher';
1111

12-
1312
@Injectable({ providedIn: 'root' })
1413
export class StoreManager {
15-
dispatcher = inject(StoreDispatcher);
16-
storeOptions = inject(STORE_OPTIONS, { optional: true }) || {};
17-
storeStates = inject(STORE_STATES, { optional: true }) || [];
18-
19-
injector = inject(Injector);
20-
store = inject(Store);
21-
22-
constructor() {
23-
this.initialize();
24-
}
25-
26-
initialize(): void;
27-
initialize(states: StoreState[], options?: StoreOptions): void;
28-
initialize(...args: any[]) {
29-
const states = (args?.[0] || this.storeStates) as StoreState[];
30-
const options = (args?.[1] || this.storeOptions) as StoreOptions;
31-
states.flat(Number.MAX_VALUE).forEach(config => {
32-
this.addState(config as StoreState, options);
33-
});
34-
}
35-
36-
addState<N extends string, M>(config: StoreState<N, M> | N, options?: StoreOptions) {
37-
if (typeof (config) === 'string') {
38-
const name = config;
39-
config = new StoreState<N, M>({ name });
14+
dispatcher = inject(StoreDispatcher);
15+
storeOptions = inject(STORE_OPTIONS, { optional: true }) || {};
16+
storeStates = inject(STORE_STATES, { optional: true }) || [];
17+
18+
injector = inject(Injector);
19+
store = inject(Store);
20+
21+
constructor() {
22+
this.initialize();
23+
}
24+
25+
initialize(): void;
26+
initialize(states: StoreState[], options?: StoreOptions): void;
27+
initialize(...args: any[]) {
28+
const states = (args?.[0] || this.storeStates) as StoreState[];
29+
const options = (args?.[1] || this.storeOptions) as StoreOptions;
30+
states.flat(Number.MAX_VALUE).forEach(config => {
31+
this.addState(config as StoreState, options);
32+
});
33+
}
34+
35+
addState<N extends string, M>(config: StoreState<N, M> | N, options?: StoreOptions) {
36+
if (typeof config === 'string') {
37+
const name = config;
38+
config = new StoreState<N, M>({ name });
39+
}
40+
41+
config.options = new StoreOptions({ ...this.storeOptions, ...options, ...config.options });
42+
43+
if (this.dispatcher.exists(config.name)) {
44+
const existingActions = this.dispatcher.getStateByName(config.name).actions;
45+
config.actions = uniqueBy([...existingActions, ...config.actions], 'name');
46+
}
47+
48+
const state = new StoreState<N, M>(config);
49+
this.dispatcher.add(state);
50+
return this;
51+
}
52+
53+
mapState(data: any = {}) {
54+
const {
55+
//
56+
[StateKeys.uuid]: uuid,
57+
[StateKeys.status]: status,
58+
...rest
59+
} = data;
60+
return rest;
4061
}
4162

42-
config.options = new StoreOptions({ ...this.storeOptions, ...options, ...config.options });
63+
getData<D, T extends 'signal' | 'observable'>(key: string, type: 'signal'): () => D;
64+
getData<D, T extends 'signal' | 'observable'>(key: string, type: 'observable'): Observable<D>;
65+
getData<D, T extends 'signal' | 'observable'>(key: string, type: T) {
66+
const selector = createFeatureSelector<D>(key);
67+
const state = {
68+
signal: this.store.selectSignal(selector),
69+
observable: this.store.select(selector),
70+
};
4371

44-
if (this.dispatcher.exists(config.name)) {
45-
config.actions = uniqueBy([...this.dispatcher.states[config.name].actions, ...config.actions], 'name');
72+
return state[type];
4673
}
4774

48-
const state = new StoreState<N, M>(config);
49-
this.dispatcher.add(state);
50-
return this;
51-
}
52-
53-
mapState(data: any = {}) {
54-
const {
55-
[StateKeys.uuid]: uuid,
56-
[StateKeys.status]: status,
57-
...rest
58-
} = data;
59-
return rest;
60-
}
61-
62-
getData<T extends 'signal' | 'observable'>(key: string, type: 'signal'): Signal<any>;
63-
getData<T extends 'signal' | 'observable'>(key: string, type: 'observable'): Observable<any>;
64-
getData<T extends 'signal' | 'observable'>(key: string, type: T) {
65-
const selector = createFeatureSelector(key);
66-
const store = this.injector.get(Store);
67-
const state = {
68-
signal: store.selectSignal(selector),
69-
observable: store.select(selector),
70-
};
71-
return state[type];
72-
}
73-
74-
signal<T>(key: string): Signal<T> {
75-
const signal = this.getData(key, 'signal');
76-
return computed(() => this.mapState(signal && signal()));
77-
}
78-
79-
value<T>(key: string): T {
80-
return this.signal(key)() as T;
81-
}
82-
83-
observable<T>(key: string, action?: StoreAction): Observable<T> {
84-
let observable = this.getData(key, 'observable');
85-
return observable.pipe(
86-
filter((payload: any) => {
87-
const noAction = !action;
88-
const isSuccessful = payload?.[StateKeys.status] === ActionStatus.SUCCESS;
89-
const isSameAction = payload[StateKeys.uuid] === action?.uuid;
90-
return isSuccessful && (noAction || isSameAction);
91-
}),
92-
!!action ? take(1) : distinctUntilKeyChanged(StateKeys.uuid),
93-
map(data => this.mapState(data)),
94-
);
95-
}
75+
signal<T>(key: string): Signal<T> {
76+
const signal = this.getData(key, 'signal');
77+
78+
return computed(() => this.mapState(signal && signal()));
79+
}
80+
81+
value<T>(key: string): T {
82+
return this.signal(key)() as T;
83+
}
84+
85+
observable<T>(key: string, action?: StoreAction): Observable<T> {
86+
let observable = this.getData(key, 'observable');
87+
88+
return observable.pipe(
89+
filter((payload: any) => {
90+
const noAction = !action;
91+
const isSuccessful = payload?.[StateKeys.status] === ActionStatus.SUCCESS;
92+
const isSameAction = payload?.[StateKeys.uuid] === action?.uuid;
93+
94+
return isSuccessful && (noAction || isSameAction);
95+
}),
96+
!!action ? take(1) : distinctUntilKeyChanged(StateKeys.uuid),
97+
map(data => this.mapState(data)),
98+
);
99+
}
100+
101+
listen(key: string, action: string, status = ActionStatus.SUCCESS, observe?: 'state' | 'action'): Observable<any> {
102+
const pipeline = this.dispatcher.getStateByName(key).pipeline;
103+
104+
observe = observe || (Object.keys(DefaultActions).includes(action) ? 'state' : 'action');
105+
106+
return pipeline.pipe(
107+
filter(payload => payload.name === action && payload.status === status),
108+
map(data => (observe === 'state' ? this.mapState(data.statePayload) : data.payload)),
109+
);
110+
}
96111
}

0 commit comments

Comments
 (0)