Overview
The distinctUntilChanged operator forwards items from the source observable only when they differ from the item emitted immediately before them. In its default configuration, it relies on the strict equality check, meaning object references need to match for values to be considered identical.
An optional comparator function can be supplied to the operator. This function is invoked with two arguments — the current value and the previous value — and it must return a boolean indicating whether these values are equivalent. When the comparator returns true, the current value is deemed a duplicate and is suppressed; when it returns false, the value proceeds to the observer.
It’s important to note that this operator does not filter out all unique values globally — it only suppresses consecutive duplicates. For instance, given the sequence {1,2,3,2}, the number 2 appears twice but not back-to-back, so it will be emitted on both occasions.
For scenarios that require more advanced filtering, you may consider the filter operator.
The operational flow of distinctUntilChanged is as follows:
- Subscribe to the source observable
- Upon each new emission, compare it against the previous value using the provided comparator or, failing that, the strict equality operator
- If the values are equal, discard the current emission; otherwise, pass it along to the observer
- When the source finishes, forward the complete notification
- If the source signals an error, forward the error notification
Practical Use
A common application for distinctUntilChanged is eliminating redundant emissions from form inputs. Consider this example:
const input = document.createElement('input');
document.body.appendChild(input);
fromEvent(input, 'input').pipe(
debounceTime(1000),
map((event: any) => event.target.value)
).subscribe((value) => console.log(value));
With debounceTime(1000), an emission occurs only after the user halts typing for a full second. But within that window, the user might type ab, then insert c, and then delete c again. Consequently, you would receive ab twice in the output stream {ab, ab}.
Adding distinctUntilChanged to the chain resolves this issue:
fromEvent(input, 'input').pipe(
debounceTime(1000),
map((event: any) => event.target.value),
distinctUntilChanged()
).subscribe((value) => console.log(value));
The operator stops the second ab from being emitted, as it is identical to the prior emission.
