2017-12-05 11 views
4

REST APIからアイテムのリストを取得しています。ユーザーはクリックしてそれぞれのユーザーと対話し、未使用のカップルが残っているだけの場合は、さらにアイテムを取得するリクエストを繰り返したいと思います。私は適切なRxJs(5)ストリーム指向のアプローチを使用してこれをしようとしています。RxJs:サーバーからのリクエストリスト、値の消費、値がほとんどなくなったときの再リクエスト

だから、のようなもの:

var userClick$ = Observable.fromEvent(button.nativeElement, 'click'); 

var needToExtend$ = new BehaviorSubject(1); 

var list$ = needToExtend$ 
      .flatMap(() => this.http.get("http://myserver/get-list")) 
      .flatMap(x => x['list']); 

var itemsUsed$ = userClick$.zip(list$, (click, item) => item); 
itemsUsed$.subscribe(item => use(item)); 

し、その後、必要な時に再ロードをトリガするために:

list$.subscribe(
    if (list$.isEmpty()) { 
     needToExtend$.next(1); 
    } 
) 

をこの最後のビットが間違っている、と手動で再トリガはいないようです非常に "ストリーム指向の"意図したように動作した場合でも。何か案は?

これはRxjs - Consume API output and re-query when cache is emptyと似ていますが、APIによって返されるリストの長さについては仮定できません。リストが完全に消耗される前に再度リクエストしたいと思います。そして、そこの解決策は少し賢明だと感じます。もっと読みやすい方法が必要でしょうね。

+0

アイテムを複数回クリックすることはできますか?それは問題ですか?コンポーネントテンプレートのコード/ what use()はここで役に立ちます – bryan60

答えて

2

どのようにこのようなものについて:

const LIST_LIMIT = 3; 
userClick$ = Observable.fromEvent(button.nativeElement, 'click'); 
list$ = this.http.get("http://myserver/get-list").map(r => r.list); 

clickCounter$ = this.userClick$.scan((acc: number, val) => acc + 1, 0); 

getList$ = new BehaviorSubject([]); 

this.getList$ 
    .switchMap(previousList => this.list$) 
    .switchMap(list => this.clickCounter$, (list, clickCount) => { return {list, clickCount}; }) 
    .filter(({list, clickCount}) => clickCount >= list.length - LIST_LIMIT) 
    .map(({list, clickCount}) => list) 
    .subscribe(this.getList$); 

ロジックここにあなたがリストゲッターストリーム、およびそれをトリガーする信号を定義した場合。

最初に、信号によってswitchMapは新しいリストをフェッチし、次にそれをクリックカウンタに再サブスクリプションする別のスイッチマップに送ります。両方のストリームの結果を結合してフィルタリングします。これは、クリック数がリストの長さから3を引いた値(または任意の値)を超えた場合にのみ発生します。その後、信号はこのストリーム全体に加入し、それ自体が再び立ち上がります。

編集:この点の最大の弱点は、サブスクリプションまたは非同期パイプではなく、副作用でリスト値(表示用)を設定する必要があることです。

const LIST_LIMIT = 3; 
userClick$ = Observable.fromEvent(button.nativeElement, 'click'); 
list$ = this.http.get("http://myserver/get-list").map(r => r.list); 

clickCounter$: Observable<number> = this.userClick$.scan((acc: number, val) => acc + 1, 0).startWith(0); 

getList$ = new BehaviorSubject([]); 

refresh$ = this.getList$ 
     .switchMap(list => this.clickCounter$ 
           .filter(clickCount => list.length <= clickCount + LIST_LIMIT) 
           .first(), 
      (list, clickCount) => list) 
     .switchMap(previousList => this.list$) 
     .multicast(() => this.getList$); 

this.refresh$.connect(); 
this.refresh$.subscribe(e => console.log(e)); 

この方法では、いくつかの利点がありますが、少し「読み」であってもよい:あなたは、しかし、それとマルチキャストを並べ替えることができます。ピースはほとんど同じですが、代わりに最初にカウンターに行き、リストフェッチへの切り替えにつながります。それをマルチキャストしてカウンタを再起動します。

+0

ありがとう!これは私を実用的な解決策に導いた。 1つの小さな注記:私は、この解決策は、新しいリストを要求し、古いリストの残りを破棄する前に、各リストを完全に使い果たしていないと思う。ここでLIST_LIMITを削除してから、最初に最初のリクエストを作成し、それを最新のものにマージしてしまいました。 – Will

+0

それは仕事を終えてうれしいです。それは少し楽しい運動でした。元のリストが完全に使い切られる前に新しいリストをリクエストすることが目的だと思ったのでリストの制限を入れましたが、私は誤解している可能性があります – bryan60

2

どのように次の項目を取得しているかわからないので、私はそれが私の答えのページングの何らかの形であると想定します。私はまたあなたがアイテムの総数を知らないと仮定します。

  • Rx.Observable.interval(#):ここ

    console.clear(); 
     
    const pageSize = 5; 
     
    const pageBuffer = 2; 
     
    const data = [...Array(17).keys()] 
     
    
     
    function getData(page) { 
     
        const begin = pageSize * page 
     
        const end = begin + pageSize; 
     
    \t return Rx.Observable.of(data.slice(begin, end)); 
     
    } 
     
    
     
    const clicks = Rx.Observable.interval(400); 
     
    
     
    clicks 
     
        .scan(count => ++count, 0) 
     
        .do(() => console.log('click')) 
     
        .map(count => { 
     
        const page = Math.floor(count/pageSize) + 1; 
     
        const total = page * pageSize; 
     
        return { total, page, count } 
     
        }) 
     
        .filter(x => x.total - pageBuffer === x.count) 
     
        .startWith({ page: 0 }) 
     
        .switchMap(x => getData(x.page)) 
     
        .takeWhile(x => x.length > 0) 
     
        .subscribe(
     
        x => { console.log('next: ', x); }, 
     
        x => { console.log('error: ', x); }, 
     
        () => { console.log('completed'); } 
     
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.3/Rx.min.js"></script>
    は、について説明され蓄積クリックイベント
  • .map(...)
  • :ページインデックスを計算し、クライアントのクリックイベント
  • .scan(...)
  • をシミュレート潜在的な総項目共同実際の数は少なくてもかまいませんが、私たちの目的には関係ありません。
  • .filter(...):ページバッファにヒットしたばかりのデータの新しいページを取得するためにのみ通過することができます。
  • .startWith(...):クリックを待たずに最初のページを取得します。 .scanのページ計算の+1がこれを説明します。
  • .switchMap(...):データの次のページを取得します。
  • .takeWhile(...):空リストを取得するまでストリームを開いたままにします。

最初のページが表示され、クリック数が指定されたバッファ内にある場合は常に新しいページが表示されます。すべてのアイテムが取得されると(空のリストによって知られている)、完了します。

私は、ページの長さがページのサイズよりも小さいときにリストを完成させる方法を理解していませんでした。それがあなたにとって重要かどうかは分かりません。

+0

私はこのようなことをしたいと思っていましたが、 APIによって返されたリストの長さを仮定していないかもしれませんが、返す配列の長さが基本的にランダムなので、一定のページサイズでは機能しません。 – bryan60

+0

@ bryan60良い点。私が最初に読んだのは、それらがアイテムの総数を指していると思ったが、あなたが正しいかもしれないと思う。 – bygrace

+0

返されたリストの長さがわからないブライアンの権利(それは変わるかもしれません)が、この素敵なレスポンスを書く時間をとってくれてありがとう。それはとにかく教育的だった。 – Will

関連する問題