48

I have a BehaviorSubject which emits JavaScript objects periodically. I want to construct another observable which will emit both previous and current values of the underlying observable in order to compare two objects and determine the delta.

The pairwise() or bufferCount(2, 1) operators are looking like a good fit, but they start emitting only after buffer is filled, but I require this observable to start emitting from the first event of the underlying observable.

subject.someBufferingOperator()
    .subscribe([previousValue, currentValue] => {
        /** Do something */
    })
;

On first emission the previousValue could be just null.

Is there some built-in operators that I can use to achieve the desired result?

Slava Fomin II
  • 26,865
  • 29
  • 124
  • 202

4 Answers4

106

Actually, it was as easy as pairing pairwise() with startWith() operators:

subject
    .startWith(null) // emitting first empty value to fill-in the buffer
    .pairwise()
    .subscribe([previousValue, currentValue] => {
        if (null === previousValue) {
            console.log('Probably first emission...');
        }
    })
;
Slava Fomin II
  • 26,865
  • 29
  • 124
  • 202
  • What if the previous or current value is `null` but it's not the first emission. How to detect it? – Farid Aug 16 '19 at 03:48
  • @FARID I believe you could `startWith(new Symbol('First emission'))` if you really need such determinism, or just use a flag for this. – Slava Fomin II Aug 18 '19 at 18:56
  • 1
    Not a very TypeScript-friendly solution, unfortunately. Now both `previousValue` and `currentValue` have type `... | null`. Is there a way to not add `null` to `currentValue`'s type? – thorn0 Jun 12 '20 at 15:25
  • 3
    @thorn̈ you could always manually cast the observable to the required type, e.g. `as Observable<[null | Type, Type]>`. – Slava Fomin II Jun 13 '20 at 15:42
  • @thorn0 that's because you have strict=true in your tsconfig. The last 4 companies I worked for had strict=false. Everything still perfectly typed, but you just never need to add `| null`. Just something to consider. – bvdb Jul 01 '22 at 05:35
9

Here's a simple operator:

function withPreviousItem<T>(): OperatorFunction<
  T,
  {
    previous?: T;
    current: T;
  }
> {
  return pipe(
      startWith(undefined),
      pairwise(),
      map(([previous, current]) => ({
        previous,
        current: current!
      }))
    );
}

The nice thing about this is that the result has meaningful property names and correct types:

  • previous is T | undefined
  • current is T (not T | null)

Stackblitz example

TmTron
  • 17,012
  • 10
  • 94
  • 142
4

Here's the snippet for rxjs 6+

subject
    .pipe(
       startWith(undefined),
       pairwise()
    )
    .subscribe(([previousValue, currentValue]) => {
        /** Do something */
    });

The value in startWith() should be undefined because there is no value. Typically null is defined as "we have a value and this value is empty".

Christoph Lütjen
  • 5,403
  • 2
  • 24
  • 33
4

scan (RX equivalent of a reduce) is an option here:

subject
    .scan((accumulator, currentValue) => {
        const previousValue = ...accumulator.slice(-1);
        return [previousValue, currentValue];
    }, [null]) // emitting first empty value to fill-in the buffer
    .subscribe(([previousValue, currentValue]) => {
        // ...
    });

This can be extended to a more general case when you want to look at more than two items.

A Jar of Clay
  • 5,622
  • 6
  • 25
  • 39