RxJS Mastery – #59 withLatestFrom

RxJS withLatestFrom operator

The RxJS withLatestFrom operator combines the source Observable with the most recent values of other Observables.

RxJS withLatestFrom

Importantly, the combination of values and emission only happens whenever the source Observable emits a value. The below example nicely shows that exactly this is happening. In the source Observable, there are four values, a-d, emitted. For every output the value is paired with the most recent value from the hot Observable passed to withLatestFrom.

const source$ = cold('--a--b-c----d|');
const result$ = source$.pipe(withLatestFrom(
    hot('12345678|'),
));

expectObservable(result$).toBe(
    '--a--b-c----d|',
    {
        a: ['a', '3'],
        b: ['b', '6'],
        c: ['c', '8'],
        d: ['d', '8'],
    }
);

Because the paired Observable ends with the value 8, this value is the most recent emission and is also used together with d in the result$.

Values from each combined Observable are needed

The source Observable will not cause an emission on its own. It always needs a value from one of the Observables passed to withLatestFrom. Therefore in the below example, the first output happens on time frame 6 which is actually on the second value from the source$:

const source$ = cold('--s--s-s----s|');
const result$ = source$.pipe(withLatestFrom(
    hot('-a--b--c'),
    hot('---xyz---'),
));

expectObservable(result$).toBe(
    '-----s-t----u|',
    {
        s: ['s', 'b', 'z'],
        t: ['s', 'c', 'z'],
        u: ['s', 'c', 'z'],
    }
);

RxJS withLatestFrom using a mapper function

The withLatestFrom RxJS dev documentation is a bit confusing regarding the mapper function. It mentions that the last parameter is treated as a mapper function if it is a function. However, the example given just used the RxJS map operator as a last argument in the pipe. And of course, a map function receives the withLatestFrom output as an array. This array contains the source value in index 0 and the combined values in the later indexes. This allows to manipulate the final output as shown below but is not a specialty of the withLatestFrom operator itself:

const source$ = cold('--a--b-c----d|');
const result$ = source$.pipe(
    withLatestFrom(
        hot('12345678|'),
    ),
    map(([s, o]) => `${s}-${o}`)
);

expectObservable(result$).toBe(
    '--a--b-c----d|',
    {
        a: 'a-3',
        b: 'b-6',
        c: 'c-8',
        d: 'd-8',
    }
);

RxJS withLatestFrom vs. combineLatest

The withLatestFrom operator is quite similar to combineLatest (or rather combineLatestWith). However, the combineLatestWith operator also emits when changes in the combined Observables happen. Our combineLatest is “source-driven” and emits only when the source value changes.
In the below example, this is demonstrated with the help of the toArray() operator that emits the result as an array. Compare this to the example above in the previous section where there were only 4 value in the result:

const { expectObservable, cold, hot } = helpers;

const source$ = cold('--a--b-c----d|');
const result$ = source$.pipe(
    combineLatestWith(
        hot('12345678|'),
    ),
    map(([s, o]) => `${s}-${o}`),
    toArray(),
);

expectObservable(result$).toBe(
    '-------------(a|)',
    {
        a: [
            "a-3",
            "a-4",
            "a-5",
            "a-6",
            "b-6",
            "b-7",
            "b-8",
            "c-8",
            "d-8",
        ]
    }
);

Exercise for the RxJS withLatestFrom operator 💪

As shown above withLatestFrom needs values from the combined Observables to emit something into the final result. Your task is here to also allow emissions earlier by using a default value of 0. This means that in time frame 3 the value a should be emitted together with 0.

const source$ = cold('--a--b-c----d|');
const result$ = source$.pipe(withLatestFrom(
    hot('------78|').pipe(// add something),
));

This post is part of the RxJS mastery series. As always the code examples can be found on GitHub.