Skip to content

Commit a4b62e1

Browse files
committed
feat: add rx-state
1 parent a8176a5 commit a4b62e1

File tree

4 files changed

+373
-194
lines changed

4 files changed

+373
-194
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { Injectable } from '@angular/core';
2+
import { RxState } from '@rx-angular/state';
3+
import { MonoTypeOperatorFunction, Observable, startWith, tap } from 'rxjs';
4+
5+
export const startWithUndefined = <T>(): MonoTypeOperatorFunction<T> => startWith<T>(undefined! as T);
6+
7+
type EffectFn<TValue> = (
8+
value: TValue
9+
) => void | undefined | ((cleanUpParams: { prev: TValue | undefined; complete: boolean; error: boolean }) => void);
10+
11+
/**
12+
* An extended `tap` operator that accepts an `effectFn` which:
13+
* - runs on every `next` notification from `source$`
14+
* - can optionally return a `cleanUp` function that
15+
* invokes from the 2nd `next` notification onward and on `unsubscribe` (destroyed)
16+
*
17+
*
18+
* @example
19+
* ```typescript
20+
* source$.pipe(
21+
* tapEffect((sourceValue) = {
22+
* const cb = () => {
23+
* doStuff(sourceValue);
24+
* };
25+
* addListener('event', cb);
26+
*
27+
* return () => {
28+
* removeListener('event', cb);
29+
* }
30+
* })
31+
* )
32+
* ```
33+
*/
34+
function tapEffect<TValue>(effectFn: EffectFn<TValue>): MonoTypeOperatorFunction<TValue> {
35+
let cleanupFn: (cleanUpParams: { prev: TValue | undefined; complete: boolean; error: boolean }) => void = () => {};
36+
let firstRun = false;
37+
let prev: TValue | undefined = undefined;
38+
39+
const teardown = (error: boolean) => {
40+
return () => {
41+
if (cleanupFn) {
42+
cleanupFn({ prev, complete: true, error });
43+
}
44+
};
45+
};
46+
47+
return tap<TValue>({
48+
next: (value: TValue) => {
49+
if (cleanupFn && firstRun) {
50+
cleanupFn({ prev, complete: false, error: false });
51+
}
52+
53+
const cleanUpOrVoid = effectFn(value);
54+
if (cleanUpOrVoid) {
55+
cleanupFn = cleanUpOrVoid;
56+
}
57+
58+
prev = value;
59+
60+
if (!firstRun) {
61+
firstRun = true;
62+
}
63+
},
64+
complete: teardown(false),
65+
unsubscribe: teardown(false),
66+
error: teardown(true),
67+
});
68+
}
69+
70+
@Injectable()
71+
export class NgtRxStore<
72+
TState extends object = any,
73+
TRxState extends object = TState & Record<string, any>
74+
> extends RxState<TRxState> {
75+
constructor() {
76+
super();
77+
// set a dummy property so that initial this.get() won't return undefined
78+
this.set({ __ngt_dummy__: '__ngt_dummy__' } as TRxState);
79+
// call initialize that might be setup by derived Stores
80+
this.initialize();
81+
}
82+
83+
protected initialize() {
84+
return;
85+
}
86+
87+
effect<S>(obs: Observable<S>, sideEffectFn: EffectFn<S>): void {
88+
return this.hold(obs.pipe(tapEffect(sideEffectFn)));
89+
}
90+
}

libs/angular-three/src/lib/types.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)