14

In an rxjs stream, I'm using distinctUntilChanged with lodash's isEqual to filter out duplicate values. However it appears to not be working as expected. Take the following code snippet

import { isEqual } from 'lodash-es';

let cachedValue: any;

function testFn(observableVal: Observable<any>) {
  return observableVal
    .pipe(
      distinctUntilChanged(isEqual),
      tap(val => {
        const equal = isEqual(cachedValue, val);
        console.log('"output":', equal, cachedValue, val);
        cachedValue = val;
      })
    )
}

In this example, I would expect that const equal inside the tap function would never === true. I would expect that distinctUntilChanged(isEqual) would filter out any values where isEqual(cachedValue, val) === true --> meaning that const equal === false always. However, console output shows:

"output": false undefined [ContactList]
"output": true [ContactList] [ContactList]
"output": true [ContactList] [ContactList]
"output": true [ContactList] [ContactList]

Do I misunderstand something fundamental about how the distinctUntilChanged() operator works? I've posted a simplified example because the actual rxjs stream is very complex, but I wouldn't expect the complexity to make any difference in so far as const equal should always === false in the tap operator.

I'm just trying to understand what's going on, so any info is appreciated. Thanks!

Update

It should be noted that if I change the code to:

function testFn(observableVal: Observable<any>) {
  return observableVal
    .pipe(
      filter(val => {
        const equal = isEqual(cachedValue, val);
        cachedValue = val;
        return !equal;
      }),
      tap(val => {
        console.log('"output":', val);
      })
    )
}

Then the filtering works as expected. I was under the impression that distinctUntilChanged(isEqual) was equivalent to:

filter(val => {
  const equal = isEqual(cachedValue, val);
  cachedValue = val;
  return !equal;
})

Am I mistaken / misunderstanding the distinctUntilChanged operator?

John
  • 9,249
  • 5
  • 44
  • 76

2 Answers2

26

I figured it out! Thanks to a comment in an rxjs issue: I had accidently subscribed to the observable multiple times (which shouldn't have happened). The multiple console.log instances were coming from different subscription instances.

John
  • 9,249
  • 5
  • 44
  • 76
  • 8
    Thanks for keeping your question up John! Helped me see how to write my own custom DistinctUntilChanged() operator – Valentine Bondar Apr 28 '20 at 13:59
  • 1
    It was useful to me, since I had the same issue. I forgot that I had subscribed twice in my template. One of the pitfalls of relying on `tap` to do what `subscribe` should do. – MrMcPlad Jan 12 '21 at 20:53
  • Useful to me. I'm going to go eyeball the merge() that I place my distinctUntilChanged observable in. @Wilt maybe you should just consider deleting your comment, it is not of any use for other ending up here. – RoboticRenaissance Feb 05 '21 at 18:44
  • 1
    Just if someone else stumbles over this with the same problem: distinctUntilChanged() does not work with FormControl value changes: https://stackoverflow.com/questions/46889851/formcontrol-detectchanges-why-use-distinctuntilchanged – malumno Nov 24 '21 at 12:14
2

In case someone will get here because of similar issue as I had - keep in mind that custom comparator function should return false to pass the distinctUntilChanged operator.

In other words this comparator will allow to pass when no changes in someProp:

  distinctUntilChanged((prev, curr) => prev.someProp !== curr.someProp)

and this - only if someProp was changed:

  distinctUntilChanged((prev, curr) => prev.someProp === curr.someProp)
godblessstrawberry
  • 4,556
  • 2
  • 40
  • 58