injectQueryParamSync
projects/common/layout/router/inject-query-param-sync.ts
Description#
Bidirectional sync between a caller-owned WritableSignal<T> and a named
query param. URL wins on initial load (deep-link intent); after hydrate the
signal is the source and reflects outward. Re-hydrates on browser
back/forward. Dev-warns once and no-ops when @angular/router is absent.
Loop-safety (the core of this helper): the reflect effect writes the URL in
untracked() and skips navigate when the serialized state() equals the
last value it wrote - a local closure seeded from the initial URL read, not
the live URL param (the live param races the navigate-settle window). The
hydrate path updates that same closure whenever it accepts a URL value, so
back/forward re-hydration never triggers a reflex navigate. A param rename
is migrated in one navigate ({ [old]: null, [new]: serialize(state()) })
by a dedicated effect whose only tracked source is the normalized param
signal; its first run seeds bookkeeping and never navigates.
The state.set inside the hydrate effect is a deliberate signal-write-in-
effect: the URL is an external system and state is a caller-owned signal
the consumer must stay able to toggle, so hydrate cannot be a computed. It
is edge-guarded by serialized equality and wrapped in untracked().
Signature#
injectQueryParamSync(state: WritableSignal, opts: QueryParamSyncOptions)Parameters#
Returns#
void