BACK
Use casesQuery params filters — the URL as filter state
Use cases · 19

Query params filters — the URL as filter state

Pattern

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.

script.ts // TypeScript
CONSOLE · Console output
Hit Run to see the result...