【问题标题】:Combine two or more (boolean) observables on single ngIf using async pipe使用异步管道在单个 ngIf 上组合两个或多个(布尔值)可观察对象
【发布时间】:2018-02-16 05:45:09
【问题描述】:

如果没有 observables,我可以在 HTML 模板中编写以下行:

<div *ngIf="(myVarA || myVarB) && myVarC !== x"></div>

如果所有myVar 变量现在实际上都是可观察的,我该如何翻译这一行?

<div *ngIf="((myVarA | async) || (myVarB | async)) && (myVarC | async) !== x">

不工作。

在另一个问题 (Putting two async subscriptions in one Angular *ngIf statement) 中,将两个或多个 observables 组合成一个 ngIf 的可能性可以像这样实现

<div *ngIf="{ a: myVarA | async, b: myVarB | async } as result"></div>

但是,我看不到在用于评估 ngIf 的表达式上使用任何布尔运算符(或任何运算符)的可能性。

我该如何解决这个问题?请注意,我所有的 Observable 都在下面使用 BehaviorSubject。我想基本上我想要的是在模板中使用combineLatest 运算符。

奖励:如果整个表达式的计算结果为 true 以供以后在模板中使用(如 myVarA | async as varA),是否有任何方法可以提取 myVarA 的单个值?

【问题讨论】:

  • 是否有可能制作一个将两者结合起来的新可观察对象?另一种方法是使用一个以 undefined 或 null 开头的对象变量,在 observables 发送下一个值之后,您可以将您的对象变量设置为某个值。
  • observables 用于在您的情况下获取值流,变量一次只能有一个值。您要么订阅可观察数组,然后将异步仅用于数组而不是单个可观察数组
  • @John 我正在使用带有 ChangeDetectionStrategy.onPush 的反应式方法,因此我不能在代码中使用然后在模板中更新的对象。使用 combineLatest 组合 observables 是我的想法,但我真的很想避免在代码中这样做
  • @SunilKumar 如何订阅可观察的数组?

标签: angular rxjs angular-template


【解决方案1】:

为常见的 AND / OR / ALL 逻辑创建辅助可观察“生成器”

一开始输入paymentSelected == false &amp;&amp; mode == 'addPayment' 很简单,但是当您需要添加新条件时,您必须在多个位置更新 UI。

最好公开一个名为showPaymentPanel$ 的可观察对象,然后在.ts 和模板文件中明确它的用途:*ngIf="showPaymentPanel$ | async"。这也使得测试更容易。

但是我最终得到了很多这样的代码:

showTokenizedPaymentMethods$ = combineLatest(this.hasTokenizedPaymentMethods$, 
                                             this.showAvailablePaymentMethods$).
                              pipe(map(([ hasTokenizedMethods, showAvailableMethods ]) => 
                              showAvailableMethods && hasTokenizedMethods));

那真是一团糟!甚至可能比多个异步管道更糟糕!

所以我创建了辅助函数来生成新的 observables:(全局某处)

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());

注意:这些不是要在管道中使用的运算符。

在 ts 文件中,您可以像这样创建 observable(命名为特定于 UI):

public showPaymentPanel$ = allTrue(this.hasTokenizedPaymentMethods$, this.showAvailableMethods$);

即使存在现有的可观察对象,我通常也会创建 UI 可观察对象:

public showAccountDetails$ = this.isLoggedIn$;     // this condition is always subject to change

你也可以组合它们:

public showSomethingElse$ = allTrue(this.showPaymentPanel$, this.whateverItIs$);

有时我会将它们展示给这样组合在一起的 UI:

public ui = { this.showPaymentPanel$, this.showSomethingElse$ );

然后用法:

`*ngIf="ui.showPaymentPanel$ | async"` 

(只有ui 应该是公开的,所以在模板中它可以非常清楚您想要允许的内容)

尽量限制在一根管子上!

【讨论】:

  • 我希望有人已经在考虑所有这些情况的情况下创建了一个库。如果有人发现类似的东西,请在此处发布:-)
  • 正是我正在寻找的。使用这些运算符可以避免如此多的重复代码!
【解决方案2】:

Jota.Toledo 的回答非常好,但是我想跟进我的“奖励”问题,即如何在 ngIf 之后访问表达式的一部分。

基本上,我所做的只是将类中的两个或多个可观察对象结合起来,正如 Jota.Toledo 所描述的那样。然而,一个 observable 不是布尔值,而是包含需要在 ngIf 之后可用的项目。

那么很容易做到以下几点:

newObs = combineLatest(
  this.itemObs,
  this.boolObs1
  this.boolObs3,
  (item, bool1, bool2) => ((item || bool1) && bool2) ? item : null
);

因为我们可以依赖 ngIf 中非空对象的真实性。 ngIf 然后看起来像

<div *ngIf="newObs | async as item"></div>

如果在 ngIf 之后需要多个项目,这当然不起作用。在这种情况下,您必须在嵌套的 div 部分上使用两个 ngIf。

【讨论】:

    【解决方案3】:

    使用combineLatest怎么样?

    例如:

    import { combineLatest } from 'rxjs/observable/combineLatest';
    import { Observable } from 'rxjs/Observable';    
    
    @Component({...})
    export class FooComponent {
      obs1$: Observable<bolean>;
      obs2$: Observable<bolean>;
      obs3$: Observable<bolean>;
    
      constructor(){
        // set observables
      }
    
      get combined$(){
        return combineLatest(
          this.obs1$,
          this.obs2$
          this.obs3$,
          (one,two,three)=>(one || two) && three);
      }
    }
    
    // template
    <div *ngIf="combined$ | async">
    

    检查以下小提琴以获得指导:

    https://jsfiddle.net/uehasmb6/11/

    有关combineLatest 运算符here 的更多信息

    更新:但如果您仍想将所有这些逻辑保留在模板中,您可以尝试以下操作:

    <div *ngIf="((myVarA | async) || (myVarB | async)) && ((myVarC | async) !== x)">
    

    但我建议你不要这样做。保持 HTML 模板尽可能干净是good practice

    【讨论】:

    • 谢谢。我也发现了这一点。所以真的没有办法在 ngIf 中使用一些很酷的语法来做到这一点,只有在模板之外?
    • 更新了我的答案,我已经在一些项目中做了这样的东西并且工作正常
    • 有趣。所以我确实只是遗漏了一组括号......不过,我同意新的显式管道更干净。
    • @Sebastian 是的。如果这解决了您的问题,请考虑通过选择此作为答案来关闭它
    猜你喜欢
    • 2019-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-16
    • 2020-02-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多