UtilAdded in v15.5Optional

wrQueryParam

A writable signal linked to one query parameter: follows the address, writes it on set, one navigation per task, the default left out.

Installation

Import the class, and @use the entry point in your global styles. Nothing warns you when the @use is missing.

import { Component } from '@angular/core';
import { WrButton } from 'ngwr/button';
import { WrInput } from 'ngwr/input';
import { WrPagination } from 'ngwr/pagination';
import { wrQueryParam } from 'ngwr/utils/router';

@Component({ imports: [WrButton, WrInput, WrPagination] })
export class MyComponent {}

Usage

set and update work as on any signal. The signal takes the value at once, and one navigation per task carries every write made in it. Needs an injection context. Lives in ngwr/utils/router, since @angular/router is an optional peer.

import { wrQueryParam } from 'ngwr/utils/router';

@Component({
  template: `
    <input wrInput type="search" [value]="q()" (input)="q.set($any($event.target).value)" />
    <wr-pagination [total]="total()" [(page)]="page" />
  `,
})
export class Orders {
  readonly q = wrQueryParam('q', { replaceUrl: true });
  readonly page = wrQueryParam<number>('page', { default: 1, parse: raw => Number(raw) || 1, serialize: String });
  readonly orders = httpResource<Order[]>(() => `/api/orders?q=${this.q()}&page=${this.page()}`);
}
1 2 3 4 5 … 12 1 / 12

Type and page, then watch the address bar. Back and Forward move both signals. q replaces the history entry, page pushes one.

A typed parameter

A string needs no options. Any other type needs default, parse and serialize, and the compiler refuses one without them. The address is user input: a parse that throws reads as default.

readonly tags = wrQueryParam<string[]>('tags', {
  default: [],
  parse: raw => raw.split(','),
  serialize: tags => tags.join(','),
});

readonly sort = wrQueryParam<'asc' | 'desc'>('sort', {
  default: 'asc',
  parse: raw => (raw === 'desc' ? 'desc' : 'asc'),
  serialize: String,
});

// Refused by the compiler: a number needs parse and serialize.
readonly page = wrQueryParam<number>('page', { default: 1 });

Rules

WhenWhat happens
The parameter is absentReads as default. A repeated one reads its first value.
A write of defaultRemoves the parameter. Already absent: no navigation.
Several writes in one taskOne navigation, across every signal. Other parameters and the fragment survive.
replaceUrl differs within one taskThe navigation pushes. A replaced entry is lost; a pushed one is one more Back.
A navigation is in flightThe write waits, then lands on the address it produced.
Its navigation is cancelled or failsThe signal reads the address again. A lost write is never kept.
The injection context is destroyed Following stops. A write already made is navigated, unless it was waiting for the navigation that destroyed it.
On the serverReads from the router. A write moves the signal only; prerender and hydration navigate nothing.

API

NameDescriptionTypeDefault
wrQueryParam(name, options?)A WritableSignal<string> linked to the name parameter. options.default is what an absent parameter reads as and the value that removes it; options.replaceUrl replaces the history entry instead of pushing one. Needs an injection context.(name: string, options?: WrQueryParamStringOptions) => WritableSignal<string>-
wrQueryParam<T>(name, options)The typed form: options.default, options.parse and options.serialize are required. A parse that throws reads as default. serialize is called once for default, so a value that serializes like it is left out of the address too.<T>(name: string, options: WrQueryParamOptions<T>) => WritableSignal<T>-
WrQueryParamStringOptionsdefault (an empty string unless given) and replaceUrl.{ default?: string; replaceUrl?: boolean }-
WrQueryParamOptions<T>default, parse, serialize and replaceUrl.{ default: T; parse: (raw: string) => T; serialize: (value: T) => string; replaceUrl?: boolean }-

See also