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.