通过项

时间:2018-01-10 22:09:42

标签: angular typescript data-binding rxjs

由于标题可能无法解释我正在做的事情,我将举一个小例子:

REST api返回一个对象列表。对于它们中的每一个,都应显示一个复选框,并将所选值用于进一步处理。但是,api可能被多次调用(刷新数据),因此我处理的是Observables,结果(即所选对象)也应该可以作为Observable使用。

I have created a simple Plunker to illustrate this:我在Component中收到一个带有对象数组的observable(这里只是简化的字符串):

var observable = Observable.of(["Hello", "World"]);

基于此,我创建类型为Item的对象,同时存储对象和信息是否被检查(作为Observable / BehaviorSubject):

this.items$ = observable.map(arr => arr.map(s => new Item(s)))
//...
class Item {
   public readonly isChecked$ : BehaviorSubject<boolean> = new BehaviorSubject(false);

   constructor(public readonly value : string) { }
}

然后,我使用How to two-way bind my own RxJS Subject to an [(ngModel)]?的解决方案显示它们:

<li *ngFor="let item of items$ | async as items" >
   <input id="cb-{{item.value}}" type="checkbox" [ngModel]="item.isChecked$ | async" (ngModelChange)="item.isChecked$.next($event)"  />
   <label for="cb-{{item.value}}">{{item.value}}</label>
</li>

但是,我现在遇到了包含所选值的Observable<string[]>的实际问​​题。基本上,我有一个Observable<Item[]>,我需要:

  1. 访问数组项的可观察项(item.isSelected $)
  2. 根据其值过滤
  3. 将结果转换回Observable,即收集数组中的值(一次)并将其作为Observable返回
  4. 第三个是我无法实现的:例如,一旦我flatMap数组,似乎就没有回复&#34;再次创建一个数组 - 我不想最终得到一个Observable<string>(当选中一个新项目时通知我),但是每次选择的项目集合都会引发Observable<string[]>变化。

    我能想到的最好的是plunker中的代码(依赖于subscribe +手动将值插入第二个数组):

    this.items$
        .switchMap(items => items.map(item => item.isChecked$.map(isChecked => ({ checked: isChecked, value: item.value}))))
        .mergeAll()
        .subscribe(o => {
            var selected = this._selectedItems.slice(0);
            if (o.checked)
                selected.push(o.value);
            else {
                var index = selected.indexOf(o.value);
                selected.splice(index, 1);
            }
    
            this._selectedItems = selected;
            this._selectedItems$.next(this._selectedItems);
        });
    

    在rxjs中是否有更优雅的功能允许我实现这一点(不需要第二个数组)?

2 个答案:

答案 0 :(得分:0)

如果要将值推送到另一个observable,则应使用SubjectSubject既是观察者又是观察者。

因此,您的过滤器可以使用next()方法将值推送到新的observable中,类似这样的

mySubject.next(val)

您的代码的另一部分可以订阅此主题,并做出相应的反应

答案 1 :(得分:0)

考虑让BehaviorSubject与您的商品列表保持同步,然后您可以将复选框事件映射到所有商品的状态。

const item_subject$ = new BehaviorSubject([]); // BehaviorSubject<Item[]>
items$.scan((acc, item) => acc.push(item), [])
      .subscribe(item_subject$);

const checked_subject$ = new Subject(); // Subject<string[]>
items$.subscribe(item =>
  item.isChecked$.subscribe(_ => 
    checked_subject.next(
      item_subject$.getValue()
                   .filter(inner_item => inner_item.isChecked$.getValue())
                   .map(inner_item => inner_item.value)
    )
  )
);

<强>定时:

checked_subject$应该永远不会出现误报,这要归功于isChecked$更新其值isChecked$向其订阅者发送的事件。唯一的竞争部分可能是假设在第2行scan之前发生的事情,将新项目推入item_subject与项目更改状态。我想象的不是现实问题,因为用户必须在新项目呈现时立即点击。