A table with filters and pagination. The user wants to share a link so a colleague opens exactly the same state. So the filters must live in the URL (?q=rxjs&page=2), and the form and the URL must stay in sync.
The problem it solves
Two sources of truth — the form and the URL — must be kept consistent. Do it imperatively and you easily get an infinite loop: the form updates the URL, the URL updates the form, the form updates the URL again. Or the opposite — navigate with the same state, jerking the browser history on every twitch.
Operators and why they matter
combineLatest — merges the form filters and the query params into one state.
map + a key string — normalize the state into a canonical string (q=rxjs&page=2). Strings are easy to compare.
distinctUntilChanged with a comparator on that key — let through only genuinely new state, breaking the loop.
tap — syncing the URL as a side effect.
Gotchas
distinctUntilChanged() without a comparator won't work — the form-value object is recreated on every change, so a reference comparison always returns false.
Don't confuse this stream with the data-loading stream. First sync URL ↔ filters; a separate stream loads data by the normalized state.
Mutating the original object inside tap makes the comparator unpredictable. Pure operations only.
What you get
The URL and the filters stay in one canonical state. The link is shareable, and the browser history isn't cluttered with duplicates.