【RxJS】withLatestFromオペレータとは?「使い方」や「特徴」などを解説!

この記事ではRxJSの『withLatestFromオペレータ』について、以下の内容を図とサンプルコードを用いて分かりやすく解説します。

  • withLatestFromオペレータとは
  • withLatestFromオペレータの「使い方」
  • withLatestFromオペレータの「特徴」
  • withLatestFromオペレータとcombineLatestオペレータの違いと使い分け

RxJSのwithLatestFromとは

RxJSのwithLatestFromとは

RxJSのwithLatestFromは、メインのObservableの値が発行された際に、サブのObservableから最新の値を取得して組み合わせて、新しいObservableを出力するオペレータです。

以下にwithLatestFromを用いたサンプルコードを示しています。

import { timer } from 'rxjs';
import { withLatestFrom } from 'rxjs/operators';

// 1000ms後に0を出力し、その後1000ms秒ごとに「1→2→3→4→・・・」というデータを流すObservable
const mainSource$ = timer(1000, 1000);

// 1500ms後に0を出力し、その後1000ms秒ごとに「1→2→3→4→・・・」というデータを流すObservable
const subSource$ = timer(1500, 1000);

// withLatestFromを使用して、2つのObservableの最新の値を組み合わせて新しいObservableを出力する
const combined$ = mainSource$.pipe(withLatestFrom(subSource$));

combined$.subscribe((value) => console.log(value));

// ログ
// [ 1, 0 ]
// [ 2, 1 ]
// [ 3, 2 ]
// [ 4, 3 ]
// [ 5, 4 ]
// [ 6, 5 ]

上記のサンプルコードでは、withLatestFromを使用して、mainSource$(メインのObservable)が値を発行するたびに、subSource$(サブのObservable)から最新の値を取得し、結果を出力しています。

あわせて読みたい

timerオペレータ』については下記の記事で詳しく説明しています。興味のある方は下記のリンクからぜひチェックをしてみてください。

withLatestFromの返り値は配列のObservableなので、以下のプログラムに示すように、配列の分割代入を使用することもできます。

import { timer } from 'rxjs';
import { withLatestFrom } from 'rxjs/operators';

// 1000ms後に0を出力し、その後1000ms秒ごとに「1→2→3→4→・・・」というデータを流すObservable
const mainSource$ = timer(1000, 1000);

// 1500ms後に0を出力し、その後1000ms秒ごとに「1→2→3→4→・・・」というデータを流すObservable
const subSource$ = timer(1500, 1000);

// withLatestFromを使用して、2つのObservableの最新の値を組み合わせて新しいObservableを出力する
const combined$ = mainSource$.pipe(withLatestFrom(subSource$));

// 配列の分割代入を使用
combined$.subscribe(([mainSource, subSource]) => {
  console.log(`mainSource:${mainSource}  subSource:${subSource}`);
});

// ログ
// mainSource:1  subSource:0
// mainSource:2  subSource:1
// mainSource:3  subSource:2
// mainSource:4  subSource:3
// mainSource:5  subSource:4
// mainSource:6  subSource:5

withLatestFromの特徴

withLatestFromの特徴を以下に示します。

  • サブのObservableから最新の値を取得する
    • withLatestFromは、サブのObservableの最新の値を保持しており、必要に応じてメインのObservableと組み合わせます。
  • メインのObservableの発行をトリガーとする
    • withLatestFromは、メインのObservableが値を発行するまで何も出力しません。このため、メインのObservableがトリガーとして機能します。
  • サブのObservableが最初の値を発行しないと、メインのObservableが値を発行しても何も起こらない
    • サブのObservableが最初の値を発行していない場合、メインのObservableが値を発行しても何も起こりません。初期値を保証したい場合はstartWithオペレータを組み合わせるのが有効です。

上記の特徴が分かるサンプルコードをこれから説明します。

値を流し始めると、メインのObservableの発行をトリガーとする

値を流し始めると、メインのObservableの発行をトリガーとする

withLatestFromは一度動作を開始すると、メインのObservableの値が発行されるたびに後続の処理が行われます。サンプルコードを以下に示します。

import { timer } from 'rxjs';
import { withLatestFrom } from 'rxjs/operators';

// 1000ms後に0を出力し、その後1000ms秒ごとに「1→2→3→4→・・・」というデータを流すObservable
const mainSource$ = timer(1000, 1000);

// 1500ms後に0を出力し、その後2000ms秒ごとに「1→2→3→4→・・・」というデータを流すObservable
const subSource$ = timer(1500, 2000);

// withLatestFromを使用して、2つのObservableの最新の値を組み合わせて新しいObservableを出力する
const combined$ = mainSource$.pipe(withLatestFrom(subSource$));

combined$.subscribe((value) => console.log(value));

// ログ
// [ 1, 0 ]
// [ 2, 0 ]
// [ 3, 1 ]
// [ 4, 1 ]
// [ 5, 2 ]
// [ 6, 2 ]

上記のサンプルコードでは、mainSource$が値を発行するたびに、最新のsubSource$の値を取得して処理を行います。このように、値を流し始めると、メインのObservableから値が流された時のみ後続に値を流すのが特徴になっています。

サブのObservableが最初の値を発行しないと、メインのObservableが値を発行しても何も起こらない

サブのObservableが最初の値を発行しないと、メインのObservableが値を発行しても何も起こらない

以下のサンプルコードは、この動作を示しています。

import { timer } from 'rxjs';
import { withLatestFrom } from 'rxjs/operators';

// 1000ms後に0を出力し、その後1000ms秒ごとに「1→2→3→4→・・・」というデータを流すObservable
const mainSource$ = timer(1000, 1000);

// 3500ms後に0を出力し、その後1000ms秒ごとに「1→2→3→4→・・・」というデータを流すObservable
const subSource$ = timer(3500, 1000);

// withLatestFromを使用して、2つのObservableの最新の値を組み合わせて新しいObservableを出力する
const combined$ = mainSource$.pipe(withLatestFrom(subSource$));

combined$.subscribe((value) => console.log(value));

// ログ
// [ 3, 0 ]
// [ 4, 1 ]
// [ 5, 2 ]
// [ 6, 3 ]
// [ 7, 4 ]

上記のサンプルコードでは、subSource$が少なくとも1つ値を発行した後で、mainSource$が値を発行すると、組み合わせた結果が出力されます。このように、サブのObservableから1つ以上の値が流されている状態で、メインのObservableから値が流れた時に後続に値を流すのが特徴となっています。

初期値を保証したい場合はstartWithオペレータを組み合わせるのが有効です。

import { timer } from 'rxjs';
import { startWith, withLatestFrom } from 'rxjs/operators';

// 1000ms後に0を出力し、その後1000ms秒ごとに「1→2→3→4→・・・」というデータを流すObservable
const mainSource$ = timer(1000, 1000);

// 3500ms後に0を出力し、その後1000ms秒ごとに「1→2→3→4→・・・」というデータを流すObservable
const subSource$ = timer(3500, 1000).pipe(startWith(0));

// withLatestFromを使用して、2つのObservableの最新の値を組み合わせて新しいObservableを出力する
const combined$ = mainSource$.pipe(withLatestFrom(subSource$));

combined$.subscribe((value) => console.log(value));

// ログ
// [ 0, 0 ]
// [ 1, 0 ]
// [ 2, 0 ]
// [ 3, 0 ]
// [ 4, 1 ]
// [ 5, 2 ]
// [ 6, 3 ]
// [ 7, 4 ]

補足

オペレーターの動作を理解する上で、以下のサイトが非常に便利です。発行されるObservableを移動させたりして、各オペレーターの動作を確認できます。

https://rxmarbles.com/

withLatestFromオペレータとcombineLatestオペレータの違いと使い分け

withLatestFromと似たオペレータにcombineLatestがあります。combineLatestは、複数のObservableを入力として取り、それぞれのObservableの最新の値を組み合わせて新しいObservableを出力するオペレータです。

あわせて読みたい

combineLatestオペレータ』については下記の記事で詳しく説明しています。興味のある方は下記のリンクからぜひチェックをしてみてください。

以下にwithLatestFromcombineLatestの違いを示します。

withLatestFromcombineLatest
合成可能なObservable数2つ
(メインとサブのObservable)
2つ以上
初回値を流す条件サブのObservableから1回以上値が流れている状態で、メインのObservableから値が流れた時全てのObservableから少なくとも1回値が流れた時
2回目以降の値を流す条件メインのObservableが値を流したときどれか1つのObservableが値を流したとき
用途メインのObservableに基づいてデータを加工する際に使用複数のObservableの状態を常に最新で追跡したい場合

withLatestFromcombineLatestの使い分けを以下に示します。

  • withLatestFromを使うとき
    • 2つのObservableを合成する場合において、メインのObservableから値が流れた時のみ値を流したい場合
    • 例: ボタンをクリックした際に他の状態(サブObservable)を参照したい
  • combineLatestを使うとき
    • 2つ以上のObservableを合成する場合
    • 2つのObservableを合成する場合において、どちらかのObservableから値が流れた時に値を流したい場合
    • 例: 複数のフォーム入力状態の最新値をリアルタイムに結合して表示したい。

本記事のまとめ

この記事ではRxJSの『withLatestFromオペレータ』について、以下の内容を説明しました。

  • withLatestFromオペレータとは
  • withLatestFromオペレータの「使い方」
  • withLatestFromオペレータの「特徴」
  • withLatestFromオペレータとcombineLatestオペレータの違いと使い分け

お読み頂きありがとうございました。