`distinctUntilChanged` compare function always has initial value as `previous` param, instead of the previously emitted value
bene-we opened this issue · 6 comments
Describe the bug
When using distinctUntilChanged
with a custom compare function on an observable emitting an array, the previous
value is always the initial value of the subject, instead of the previous one.
Please have a look on the minimal reproduction example and the console output. Am I getting something wrong on the compare function with distinctUntilChanged
?
Actual behavior
[]
0 1
0 2
0 3
// Missing emit of array
Expected behavior
[]
0 1
1 2
2 3
3 3
["test1", "test2", "test3"]
Reproduction code
import { distinctUntilChanged } from "rxjs/operators";
import { BehaviorSubject } from "rxjs";
const subject = new BehaviorSubject([]);
const test$ = subject.asObservable().pipe(
distinctUntilChanged((prev, curr) => {
console.log(prev.length, curr.length);
return prev.length !== curr.length;
})
);
test$.subscribe(console.log);
subject.next(["test"]);
subject.next(["test1", "test2"]);
subject.next(["test1", "test2", "test3"]);
subject.next(["test1", "test2", "test3"]);
Reproduction URL
https://codesandbox.io/s/wonderful-thunder-6usy6z?file=/src/index.ts
Version
7.5.7
Environment
Please see codesandbox example
Additional context
No response
// If it's the first value, we always emit it.
// Otherwise, we compare this key to the previous key, and
// if the comparer returns false, we emit.
Since example in the code's comparator always returns true, it is considered as same
value so observable won't emit subsequent values. In result, initial []
is considered as last-emitted value and keep comes as prev
.
@kwonoj thanks for your response! Am I correct in the assumption that distinctUntilChanged
only uses values that have been emitted completely (past the distinctUntilChanged
)?
@bene-we I think you are correct. I've got the same issue and... I dont think it makes sense.
Here we need some way to test if a switch has been changed, i.e. isSaving from true to false. Since it seems to use the after value instead of the real input, it swallows real states for me.
I also created an example: (https://codesandbox.io/embed/rxjs-playground-forked-x8xriv?fontsize=14&hidenavigation=1&theme=dark)
@bene-we just if you're interested here my solution with a separate property in the component:
private previousValue;
output$ = this.incoming$.pipe(
filter(currentValue => {
if (!this.previousValue) {
this.previousValue = currentValue; // set it
return true; // initial should always go through
}
const shouldContinue = this.previousValue.id !== currentValue.id; // do your custom comparison
this.previousValue = currentValue; // obviously has to come after the comparison
return shouldContinue;
}),
);
This will look at the incoming value instead of the outgoing one.
@bene-we sorry to bother you :) I did some refactoring by creating an operator for this which compares against the last incoming value. Its reusable, no additional property is needed and you can focus on the comparer instead of that additional code from here above inside the filter.
If interested see here: #7180
@daniel-seitz sorry pal for not getting back to you, I’m traveling right now. I will try to test your custom implementation once I’m back. Thanks for your persistent efforts on this!