I have a stream of objects and I need to compare if the current object is not the same as the previous and in this case emit a new value. I found distinctUntilChanged operator should do exactly what I want, but for some reason, it never emits value except the first one. If I remove distinctUntilChanged values are emitted normally.
My code:
export class SettingsPage {
static get parameters() {
return [[NavController], [UserProvider]];
}
constructor(nav, user) {
this.nav = nav;
this._user = user;
this.typeChangeStream = new Subject();
this.notifications = {};
}
ngOnInit() {
this.typeChangeStream
.map(x => {console.log('value on way to distinct', x); return x;})
.distinctUntilChanged(x => JSON.stringify(x))
.subscribe(settings => {
console.log('typeChangeStream', settings);
this._user.setNotificationSettings(settings);
});
}
toggleType() {
this.typeChangeStream.next({
"sound": true,
"vibrate": false,
"badge": false,
"types": {
"newDeals": true,
"nearDeals": true,
"tematicDeals": false,
"infoWarnings": false,
"expireDeals": true
}
});
}
emitDifferent() {
this.typeChangeStream.next({
"sound": false,
"vibrate": false,
"badge": false,
"types": {
"newDeals": false,
"nearDeals": false,
"tematicDeals": false,
"infoWarnings": false,
"expireDeals": false
}
});
}
}
I had the same problem, and fixed it with using JSON.stringify to compare the objects:
.distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))
This code will work if the attriutes are in the same order, if not it may break, so here's a quick fix for that (be careful this method is slower)
.distinctUntilChanged((a, b) => JSON.stringify(a).split('').sort().join('') === JSON.stringify(b).split('').sort().join(''))
I finally figure out where problem is. Problem was in version of RxJS, in V4 and earlier is different parameters order than V5.
RxJS 4:
distinctUntilChanged = function (keyFn, comparer)
RxJS 5:
distinctUntilChanged = function (comparer, keyFn)
In every docs today, you can find V4 parameters order, beware of that!
When you have lodash in your application anyway, you can simply utilize lodash's isEqual()
function, which does a deep comparison and perfectly matches the signature of distinctUntilChanged()
:
.distinctUntilChanged(isEqual),
Or if you have _
available (which is not recommended anymore these days):
.distinctUntilChanged(_.isEqual),
You can also wrap the original distinctUntilChanged
function.
function distinctUntilChangedObj<T>() {
return distinctUntilChanged<T>((a, b) => JSON.stringify(a) === JSON.stringify(b));
}
This lets you use it just like the original.
$myObservable.pipe(
distinctUntilChangedObj()
)
This method also has several pitfalls as some commenters have pointed out.
JSON.stringify({a:1, b:2}) === JSON.stringify({b:2, a:1})
// will return false :(
JSON.stringify
will also throw an error if the object has circular references. For example stringify'ing something from the firebase sdk
will lead you to these errors:TypeError: Converting circular structure to JSON
Use the deep-object-diff library instead of JSON.stringify
. This will solve the above problems 👍
import { detailedDiff } from 'deep-object-diff';
function isSame(a, b) {
const result = detailedDiff(a, b);
const areSame = Object.values(result)
.every((obj) => Object.keys(obj).length === 0);
return areSame;
}
function distinctUntilChangedObj<T>() {
return distinctUntilChanged<T>((a, b) => isSame(a, b));
}
From RxJS v6+ there is distinctUntilKeyChanged
https://www.learnrxjs.io/operators/filtering/distinctuntilkeychanged.html
const source$ = from([
{ name: 'Brian' },
{ name: 'Joe' },
{ name: 'Joe' },
{ name: 'Sue' }
]);
source$
// custom compare based on name property
.pipe(distinctUntilKeyChanged('name'))
// output: { name: 'Brian }, { name: 'Joe' }, { name: 'Sue' }
.subscribe(console.log);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With