Effects
Not familiar with Observables/RxJS v6?
Sigi
requires an understanding of Observables with RxJS v6. If you're new to Reactive Programming with RxJS v6, head over to http://reactivex.io/rxjs/ to familiarize yourself first.
An Effect is the core primitive of Sigi
.
It is a method in EffectModule
class which takes a stream of Payloads
and returns a stream of actions. Payloads in, actions out.
It has roughly this type signature:
<Payload>(action$: Observable<Payload>): Observable<Action>;
While you'll most commonly produce actions out in response to some Payload
you received in, that's not actually a requirement! Once you're inside your Effect, use any Observable patterns you desire as long as any output from the final, returned stream, is an action.
The actions you emit will be immediately dispatched to their Module
.
If emit action which actually associated with an Effect itself, it may create an infinite loop:
// DO NOT DO THISsomething(payload$: Observable<void>) {return payload$.map(() => this.getActions().something())}
Access AppState in Effect
In EffectModule
instances, we could access the state$
property, which is a Observable
represent the latest state.
We do not provide any way to access state directly in Effect, because access state directly rather than the reactive way would cause problems in many scenarios, and many of these problems are hard to debug.
Think about the code snippets below:
@Module('B')class ModuleB extends EffectModule<BState> {defaultState = {}constructor(private readonly httpClient: HttpClient) {super()}@Effect()addAndSync(payload$: Observable<number>) {return payload$.pipe(mergeMap((payload) => {const state = this.getState()return this.httpClient.get(`/api/something`, {body: {query: payload}}).pipe(map((response) => {if (state.expireIn < Date.now()) { // state here is staledreturn this.getActions().setResponse(response.body)} else {return this.getActions().setExpired(response.body)}}),catchError(...),startWith(this.getActions().setLoading(true)),endWith(this.getActions().setLoading(false)),)}))}}
If we access state in first mergeMap
by getState
directly, and reuse it in the map
operator then, the state may already stated when we really use it. It could simply fix by getState
in the map
operator, but it's truly hard to debug.
So, always access state in reactive
way is a better practice:
@Module('B')class ModuleB extends EffectModule<BState> {defaultState = {}constructor(private readonly httpClient: HttpClient) {super()}@Effect()addAndSync(payload$: Observable<number>) {return payload$.pipe(mergeMap((payload) => {return this.httpClient.get(`/api/something`, {body: {query: payload}}).pipe(withLatestFrom(this.state$),map(([response, state]) => {if (state.expireIn < Date.now()) { // always latest state herereturn this.getActions().setResponse(response.body)} else {return this.getActions().setExpired(response.body)}}),catchError(...),startWith(this.getActions().setLoading(true)),endWith(this.getActions().setLoading(false)),)}))}}