Last active
November 7, 2022 14:15
-
-
Save baetheus/6396d5681ee039105b38acf713722af8 to your computer and use it in GitHub Desktop.
A factory function POC for cancellable ngrx effects
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Action, Effect } from "@ngrx/store"; | |
import { Observable } from "rxjs/Observable"; | |
import { of } from "rxjs/observable/of"; | |
import "rxjs/add/operator/switchMap"; | |
import "rxjs/add/operator/takeUntil"; | |
import "rxjs/add/operator/filter"; | |
import "rxjs/add/operator/map"; | |
// For POC typechecking force payloads on Actions. | |
export interface CancellableAction<T> extends Action { | |
readonly payload: T; | |
} | |
// Constructable interfaces to get nice types from success/failure classes | |
// Potentially enforce additional action structure here. | |
export interface SuccessConstructable<S = undefined> { | |
new(payload: S): Action; | |
} | |
export interface FailureConstructable<F = undefined> { | |
new(payload: F, error: Error): Action; | |
} | |
export function cancellableEffectFactory<P, S>( | |
obs: Observable<CancellableAction<P>>, | |
action: string, | |
func: (payload: P) => Observable<S>, | |
cancels: string[], | |
success: SuccessConstructable<S>, | |
failure: FailureConstructable<P> | |
) { | |
return obs | |
.filter(a => a.type === action) | |
.map(a => a.payload) | |
.switchMap(p => func(p) | |
.takeUntil(obs.filter(a => cancels.some(c => c === a.type))) | |
.map(s => new success(s)) | |
.catch(e => of(new failure(p, e)))); | |
} | |
// Reduce sig footprint, since most stuff can be bundled in actions file. | |
export interface CancellableBundle<P, S> { | |
readonly action: string; | |
readonly cancels: string[]; | |
readonly success: SuccessConstructable<S>; | |
readonly failure: FailureConstructable<P>; | |
} | |
export function bundleFactory<P, S>( | |
b: CancellableBundle<P, S>, | |
func: (payload: P) => Observable<S>, | |
obs: Observable<CancellableAction<P>> | |
) { | |
return cancellableEffectFactory(obs, b.action, func, b.cancels, b.success, b.failure); | |
} | |
/** | |
* And now its use.. | |
*/ | |
// These Action classes would already exist in existing configs | |
class Start implements Action { | |
public readonly type = "START"; | |
constructor(public readonly payload: string) {} | |
} | |
class Cancel implements Action { | |
public readonly type = "CANCEL"; | |
constructor(public readonly payload: string) {} | |
} | |
class Succ implements Action { | |
public readonly type = "SUCCESS"; | |
constructor(public readonly payload: string) {} | |
} | |
class Fail implements Action { | |
public readonly type = "FAILURE"; | |
constructor(public readonly payload: string) {} | |
} | |
// These bundles could be created and exported from the action def files. | |
const bundle: CancellableBundle<string, string> = { | |
action: "START", | |
cancels: ["CANCEL"], | |
success: Succ, | |
failure: Fail, | |
} | |
// The result is a much smaller effects definition with pretty tight type checking | |
@Injectable() | |
export class SomeEffects { | |
@Effect() | |
public cancellable = bundleFactory(bundle, this.someSvc.apiCall, this.actions); | |
constructor ( | |
private actions: Actions, | |
private someSvc: SomeService, | |
) {} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment