用于组成 RxJS Observables 的布尔代数助手

Sim*_*ver 3 boolean-operations rxjs

如何避免编写疯狂的combineLatest语句来计算简单的布尔逻辑表达式?

例如。这个简单的表达式几乎不适合 stackoverflow 代码控件,如果你不小心重新排序参数,你将很难调试!

this.showPlayButton = combineLatest(this.playPending, this.isReady, this.showOverlay)
  .pipe(
   map(([playPending, isReady, showOverlay]) => isReady && !playPending && showOverlay),
   distinctUntilChanged();
Run Code Online (Sandbox Code Playgroud)

Sim*_*ver 5

好吧,我很惊讶我找不到一个现有的库,所以我开始收集一些辅助可观察创建函数。

Observable<boolean>帮手

这些是“最纯粹”的帮手,既吸收又输出Observable<boolean>。我已经添加distinctUntilChanged()了每一个,这可以防止多次不必要的排放。这是一个非常惰性的运算符,没有更复杂的shareor后果shareReplay(1)- 但重要的是要知道它已被应用。

export const allTrue = (...observables: Array<ObservableInput<boolean>> ) => combineLatest(observables).pipe(map(values => values.every(v => v == true) ), distinctUntilChanged());
export const allFalse = (...observables: Array<ObservableInput<boolean>> ) => combineLatest(observables).pipe(map(values => values.every(v => v == false) ), distinctUntilChanged());
export const anyTrue = (...observables: Array<ObservableInput<boolean>> ) => combineLatest(observables).pipe(map(values => values.find(v => v == true) != undefined ), distinctUntilChanged());
export const anyFalse = (...observables: Array<ObservableInput<boolean>> ) => combineLatest(observables).pipe(map(values => values.find(v => v == false) != undefined), distinctUntilChanged());

export const not = (observable: Observable<boolean> ) => observable.pipe(map(value => !value), distinctUntilChanged());
Run Code Online (Sandbox Code Playgroud)

true / falsey 的布尔转换器

同一类别中的一些其他“帮助器”,例如ifTruthyifFalsy旨在与上述帮助器一起使用(因为它们需要真正的布尔值)。这些当前使用!=与 相对的!==,所以isDefined(of(null))isDefined(of(undefined))都产生一个trueObservable。也isEqual不会进行深入的比较。

 export const isTruthy = <T>(observable: Observable<T>) => observable.pipe(map(obsValue => !!obsValue), distinctUntilChanged());
 export const isFalsey = <T>(observable: Observable<T>) => observable.pipe(map(obsValue => !obsValue), distinctUntilChanged());

 export const isDefined = <T>(observable: Observable<T>) => observable.pipe(map(obsValue => obsValue != undefined), distinctUntilChanged());
 export const isEqual = <T>(observable: Observable<T>, value: T) => observable.pipe(map(obsValue => obsValue == value), distinctUntilChanged());
 export const notEqual = <T>(observable: Observable<T>, value: T) => observable.pipe(map(obsValue => obsValue != value), distinctUntilChanged());
Run Code Online (Sandbox Code Playgroud)

还有第三类,相当于iffSQL Server的语句。这些的输出是or (无论你的参数是什么类型)。这就像一个非常简单的 if 语句。 iffAB

export const iff = <A, B>(ifObs: Observable<boolean>, trueValue: A, falseValue: B) => ifObs.pipe(map(value => value ? trueValue : falseValue));
Run Code Online (Sandbox Code Playgroud)

作品

使用 RxJS,可以非常轻松地将它们组合在一起并创建非常可读的组合。我经常在 Angular 组件中使用这些来获取特定于 UI 的属性,并且调试起来要容易得多。下面这些是真实的代码示例,应该是非常不言自明的。

loading$ = not(this.loaded$);
hasSelectedOrder$ = isTruthy(orderId$);  // Note: this wouldn't work for something zero
hasSelectedOrder$ = isDefined(orderId$);  // uses != undefined 

layout$ = iff(deviceType.isDesktop$, 'horizontal', 'vertical');
isMobileOrTablet$: anyTrue(this.isMobile$, this.isTablet);
isTabletOrDesktop$: anyTrue(this.isTablet, this.isDesktop$);

isBusy$ = anyTrue(this.hasBusyTask$, this.busyService.isBusy$);

// very useful for UI (using async pipe)
isAddNewCreditCardSelected$ = isEqual(selectedPaymentMoniker$, 'NEW');
showSavedPayments$ = allTrue(showAvailablePaymentMethods$, hasVault$, not(isAddNewCreditCardSelected$));
showAddCreditCardButton$ = allTrue(showPaymentButtons$, not(showAddCreditCardPanel$), not(showVault$))
showDefaultFooter$ = allTrue(not(this.isWebapp$), this.showDefaultFooter$);
showBusyIndicator$ = allTrue(not(this.pageService.handlesBusyIndicator$), this.busyService.isBusy$) ;
Run Code Online (Sandbox Code Playgroud)

至于原来的问题,就变成了这样:

this.showPlayButton$ = allTrue(this.isReady$, not(this.playPending$), this.showOverlay$)
Run Code Online (Sandbox Code Playgroud)

当我想出新的时,我会添加到这个列表中。这些已经涵盖了我迄今为止遇到的大多数情况。这当然可以制作成一个库,但我现在无法将其形式化以能够做到这一点。如果某些东西已经存在,我很乐意进行比较:-)