loading...

RxJS & the combineLatest Operator "Gotcha"

averyferrante profile image Joseph Avery Ferrante ・2 min read

Today, I spent a good chunk of my workday debugging an issue I had with an observable stream spawned from the combineLatest function. I want to share this life lesson in hopes others do not suffer a similar fate.

I've boiled the problem down to the code snippet below:

// Will output one value, an array of 3 numbers
const oneValue$ = of([10, 20, 30]);
// Will output two values
const twoValues$ = of(100, 200);

combineLatest(oneValue$, twoValues$).pipe(
  map(([val1, val2]) => {
    return {
      a: val1.splice(0, 1)[0],
      b: val2
    }
  })
).subscribe(output => console.log(output));
// Desired Output:
// { a: 10, b: 100 }
// { a: 10, b: 200 }

// Actual Output:
// { a: 10, b: 100 }
// { a: 20, b: 200 }

Notice the desired vs. actual outputs. Let's walk through this example:

We have two observable streams, one that will emit only 1 value in its lifetime, the other will emit two values.

Using combineLatest I want to construct a new return value based on the latest values from each stream.

I map the two emitted values [val1, val2] to a new object.

I want this new object to have two properties where

  1. property a is the first value in the array from oneValue$ (i.e. the value 10)
  2. property b is the latest value in twoValues$ (i.e. the value 100, then 200)

So I want my output to look like

  // Desired
  { a: 10, b: 100 }
  { a: 10, b: 200 }

  // Actual
  { a: 10, b: 100 }
  { a: 20, b: 200 }

My a value isn't always the first value from the array. Strange?

combineLatest should re-emit the last value from oneValue$ when twoValues$ emits the second time. That should always be the array [10, 20, 30]. I'm using the function splice(0, 1)[0] to grab that first item in the array, yet the second emission seems to have grabbed the second item of the array.

Have you figured it out yet?

The splice() function is permanently altering the array values

combineLatest is emitting the same array value during the second emission, but
The splice() function actually alters the values of the array during each mapping. Remember, JavaScript is always passing by reference. In our map() function, val1 is another variable that is pointing to the same array object being held in the combineLatest operator. We then modify the array contents. So when combineLatest passes the array reference to map() the second time, we receive the same (modified) array reference.

Obvious fixes are to use the spread operator if it's an array or object of primitive values, or possibly pulling in something like cloneDeep from the lodash library for more complex objects.

Many array functions return new arrays without altering the original, however, not all do. Make sure you make a conscious decision around this!

Posted on by:

averyferrante profile

Joseph Avery Ferrante

@averyferrante

Web developer + Bloody Marys

Discussion

pic
Editor guide
 

Thank you, that was driving me crazy! 🤯