Skip to content

Instantly share code, notes, and snippets.

@baetheus
Last active November 7, 2022 14:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save baetheus/6396d5681ee039105b38acf713722af8 to your computer and use it in GitHub Desktop.
Save baetheus/6396d5681ee039105b38acf713722af8 to your computer and use it in GitHub Desktop.
A factory function POC for cancellable ngrx effects
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