The RxJS distinct, distinctUntilChanged and distinctUntilKeyChanged operators only lets a value through if it isn’t equal to an already emitted value.
RxJS distinct removes duplicates in the whole stream
Distinct keeps track of emitted values to not emit again if the same value would arrive. The example below simply ignores the duplicated emissions and only output 1, 2, 3 and 4 once.
of(1, 1, 2, 3, 1, 2, 3, 4, 3, 2, 1).pipe(
distinct()
).subscribe({ next: console.log });
// 1
// 2
// 3
// 4
This also means that distinct stores values internally. This storage grows over time and therefore the distinction check becomes slower.
RxJS distinct allows to map the value to be checked
RxJS distinct offers the keySelector parameter. This parameter is a function that allows to map from the source value to a value on which the duplication should be checked. In the example below we have source values representing people from different age groups. In the output we want to have only 1 person from each age group. This can be achieved by a simple map function:
of(
{ age: 4, name: 'Kid 2'},
{ age: 7, name: 'Kid 3'},
{ age: 5, name: 'Kid 1'},
{ age: 60, name: 'Adult 1'},
{ age: 25, name: 'Adult 2'},
{ age: 18, name: 'Adult 3'},
).pipe(
distinct(({ age }) => age - 17 > 0)
).subscribe({ next: console.log });
// { age: 4, name: 'Kid 2' }
// { age: 60, name: 'Adult 1' }
Only Kid 2 and Adult 1 are part of the final output. The rest of the same age group (1-17 and older than 18) are ignored.
RxJS distinct allows to flush the values to be compared
As mentioned above the already emitted values are kept internally to allow a comparison. This Set of internal values grows over time. That is why RxJS distinct offers a parameter to flush the Set:
zip(
of(1, 1, 2, 3, 1, 2, 3, 4, 3, 2, 1),
interval(500),
(v, frequency) => v,
)
.pipe(
distinct(v => v, interval(3000))
).subscribe({
next: console.log,
complete: done
});
// 1
// 2
// 3
// 2
// 3
// 4
// 1
Every 500ms a number is emitted. Every 3 seconds the internal Set of the operator is flushed. That is why 2, 3, and 1 occur multiple times.
RxJS distinctUntilChanged eliminates only subsequent duplicates
If you just care about not having duplicate values follow each other, then distinctUntilChanged helps.
of(1, 1, 1, 2, 2, 2, 1, 1, 3, 3).pipe(
distinctUntilChanged()
).subscribe(console.log);
// 1
// 2
// 1
// 3
Please note that the comparison happens with === as a default! This means that for objects the references matter. Hence in below code the last two values have each time the name “Joe”:
const obj = { name: 'Joe' };
const obj2 = { name: 'Roger' };
const obj3 = { name: 'Emma' };
const obj4 = { name: 'Joe' };
of(obj, obj, obj2, obj2, obj3, obj, obj4).pipe(
distinctUntilChanged()
).subscribe(console.log);
// { name: 'Joe' }
// { name: 'Roger' }
// { name: 'Emma' }
// { name: 'Joe' } --> obj
// { name: 'Joe' } --> obj4
RxJS distinctUntilKeyChanged is like distinctUntilChanged but access values through a key
Also with distinctUntilKeyChanged you can check on subsequent duplicate. But here the check happens on a specified key. Taking the example from above we could prevent the duplicate emission of “Joe” at the end:
const obj = { name: 'Joe' };
const obj2 = { name: 'Roger' };
const obj3 = { name: 'Emma' };
const obj4 = { name: 'Joe' };
of(obj, obj, obj2, obj2, obj3, obj, obj4).pipe(
distinctUntilKeyChanged('name')
).subscribe(console.log);
// { name: 'Joe' }
// { name: 'Roger' }
// { name: 'Emma' }
// { name: 'Joe' }
Exercise for the RxJS distinct operator 💪
Take the following as source values:
of(
{ id: 1, name: 'Book', price: 10},
{ id: 1, name: 'Great Book', price: 10},
{ id: 1, name: 'Great Book', price: 9},
{ id: 2, name: 'Pencil', price: 2},
{ id: 2, name: 'Pencil', price: 3},
{ id: 3, name: 'Shoe', price: 300},
{ id: 3, name: 'Red Shoe', price: 300},
)
Prevent subsequent duplicates if only the name changes. But as soon as the price changes on a certain ID an emission should happen.
As always the code examples can be found on GitHub.