Skip to content

Commit 352e447

Browse files
committed
feat(router): add linkedQueryParam
1 parent f6770f9 commit 352e447

3 files changed

Lines changed: 75 additions & 0 deletions

File tree

docs/src/content/packages/router/index.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ Click on the symbol to see the detailed documentation.
1414
1515
### Exported from `@angularity/router`
1616

17+
Component Utilities:
18+
19+
- `linkedQueryParam`
20+
{{ JSDoc.description("packages/router/src/public-api.ts#linkedQueryParam") }}
21+
1722
General Utilities:
1823

1924
- `useChildActivatedRoute`
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { effect, inject, linkedSignal, WritableSignal } from '@angular/core';
2+
import { toSignal } from '@angular/core/rxjs-interop';
3+
import { ActivatedRoute, Router } from '@angular/router';
4+
import { map, startWith } from 'rxjs';
5+
6+
export interface LinkedQueryParamOptions<T> {
7+
/**
8+
* Initial value of the linked signal,
9+
* used when the query parameter is not present in the URL.
10+
*/
11+
initialValue: T;
12+
/**
13+
* Transform the query parameter value as a string into
14+
* the desired type.
15+
* @param v string value of the query parameter
16+
* @returns transformed value
17+
*/
18+
transform: (v: string) => T;
19+
}
20+
21+
/**
22+
* Create a linked signal associated with a query parameter.
23+
* @param name name of the query parameter in the URL
24+
* @param options configuration for the linked signal
25+
* @example
26+
* ```ts
27+
* export class MyComponent {
28+
* readonly filter = linkedQueryParam('filter', { initialValue: 'all', transform: (v) => this.transformFilter(v) });
29+
* onFilterChange(newFilter: string): void {
30+
* this.filter.set(newFilter);
31+
* }
32+
* }
33+
* ```
34+
*/
35+
export function linkedQueryParam(
36+
name: string,
37+
): WritableSignal<string | undefined>;
38+
export function linkedQueryParam<T>(
39+
name: string,
40+
options: LinkedQueryParamOptions<T>,
41+
): WritableSignal<T>;
42+
export function linkedQueryParam<T>(
43+
name: string,
44+
options?: LinkedQueryParamOptions<T>,
45+
): WritableSignal<T> {
46+
const router = inject(Router);
47+
const route = inject(ActivatedRoute);
48+
const value$ = route.queryParams.pipe(
49+
map((params) => params[name] as string | undefined),
50+
map((v) => {
51+
if (v === undefined) return options?.initialValue;
52+
return options?.transform ? options.transform(v) : (v as string);
53+
}),
54+
startWith(options?.initialValue),
55+
);
56+
const source = toSignal(value$, { requireSync: true });
57+
const linked = linkedSignal(source) as WritableSignal<T>;
58+
effect(() => {
59+
const isNewValueWritten = source() !== linked();
60+
if (!isNewValueWritten) return;
61+
const value = linked();
62+
router.navigate(['.'], {
63+
relativeTo: route,
64+
queryParams: { [name]: value },
65+
queryParamsHandling: 'merge',
66+
});
67+
});
68+
return linked;
69+
}

packages/router/src/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './child-activated-route';
2+
export * from './linked-query-param';
23
export * from './load-children-injection';
34
export * from './route-data';

0 commit comments

Comments
 (0)