Skip to content

Commit 5c37c02

Browse files
committed
feat!: use strict query params
Array query parameters like `?size=m&size=l&size=xl now correctly resolved to `readonly string[]` instead of `string`. **BREAKING CHANGES** `RouterStore#queryParams$` and `MinimalActivatedRouteSnapshot#queryParams` use `StrictQueryParams` instead of `StrictRouteParams`. Members are of type `string | readonly string[] | undefined` instead of `string | undefined`. The TypeScript compiler will fail to compile code that does not handle the string array type. BEFORE: ```typescript // shirts.component.ts // (...) import { RouterStore } from "@ngworker/router-component-store"; @component({ // (...) }) export class ShirtsComponent { #routerStore = inject(RouterStore); size$: Observable<string> = this.#routerStore.queryParams$.pipe( map((params) => params["size"]), ); } ``` AFTER: ```typescript // shirts.component.ts // (...) import { RouterStore } from "@ngworker/router-component-store"; @component({ // (...) }) export class ShirtsComponent { #routerStore = inject(RouterStore); size$: Observable<readonly string[]> = this.#routerStore.queryParams$.pipe( map((params) => params["size"]), map((size) => (Array.isArray(size) ? size : [size])) ); } ``` `RouterStore#selectQueryParam` use `StrictQueryParams` instead of `StrictRouteParams`. The returned value is of type `string | readonly string[] | undefined` instead of `string | undefined`. The TypeScript compiler will fail to compile code that does not handle the string array type. BEFORE: ```typescript // shirts.component.ts // (...) import { RouterStore } from "@ngworker/router-component-store"; @component({ // (...) }) export class ShirtsComponent { #routerStore = inject(RouterStore); size$: Observable<string> = this.#routerStore.selectQueryParam('size'); } ``` AFTER: ```typescript // shirts.component.ts // (...) import { RouterStore } from "@ngworker/router-component-store"; @component({ // (...) }) export class ShirtsComponent { #routerStore = inject(RouterStore); size$: Observable<readonly string[]> = this.#routerStore.selectQueryParam('size').pipe( map((size) => (Array.isArray(size) ? size : [size])) ); } ```
1 parent 6ac16c9 commit 5c37c02

7 files changed

Lines changed: 41 additions & 12 deletions

File tree

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
// GlobalRouterStore
2+
// Serializable route state
3+
export * from './lib/@ngrx/router-store/minimal-activated-route-state-snapshot';
24
export * from './lib/global-router-store/provide-global-router-store';
3-
45
// LocalRouterStore
56
export * from './lib/local-router-store/provide-local-router-store';
6-
77
// RouterStore
88
export * from './lib/router-store';
9-
10-
// Serializable route state
11-
export * from './lib/@ngrx/router-store/minimal-activated-route-state-snapshot';
9+
export * from './lib/strict-query-params';
1210
export * from './lib/strict-route-data';
1311
export * from './lib/strict-route-params';

packages/router-component-store/src/lib/@ngrx/router-store/minimal-activated-route-state-snapshot.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
* found in the LICENSE file at https://angular.io/license
3131
*/
3232
import { ActivatedRouteSnapshot } from '@angular/router';
33+
import { StrictQueryParams } from '../../strict-query-params';
3334
import { StrictRouteData } from '../../strict-route-data';
3435
import { StrictRouteParams } from '../../strict-route-params';
3536

@@ -54,7 +55,7 @@ export interface MinimalActivatedRouteSnapshot {
5455
/**
5556
* The query parameters shared by all the routes.
5657
*/
57-
readonly queryParams: StrictRouteParams;
58+
readonly queryParams: StrictQueryParams;
5859
/**
5960
* The URL fragment shared by all the routes.
6061
*/

packages/router-component-store/src/lib/global-router-store/global-router-store.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { MinimalActivatedRouteSnapshot } from '../@ngrx/router-store/minimal-act
1414
import { MinimalRouterStateSnapshot } from '../@ngrx/router-store/minimal-router-state-snapshot';
1515
import { MinimalRouterStateSerializer } from '../@ngrx/router-store/minimal_serializer';
1616
import { filterRouterEvents } from '../filter-router-event.operator';
17+
import { InternalStrictQueryParams } from '../internal-strict-query-params';
1718
import { InternalStrictRouteData } from '../internal-strict-route-data';
1819
import { InternalStrictRouteParams } from '../internal-strict-route-params';
1920
import { RouterStore } from '../router-store';
@@ -52,7 +53,7 @@ export class GlobalRouterStore
5253
this.#rootRoute$,
5354
(route) => route.fragment
5455
);
55-
queryParams$: Observable<InternalStrictRouteParams> = this.select(
56+
queryParams$: Observable<InternalStrictQueryParams> = this.select(
5657
this.#rootRoute$,
5758
(route) => route.queryParams
5859
);
@@ -101,7 +102,9 @@ export class GlobalRouterStore
101102
})
102103
);
103104

104-
selectQueryParam(param: string): Observable<string | undefined> {
105+
selectQueryParam(
106+
param: string
107+
): Observable<string | readonly string[] | undefined> {
105108
return this.select(this.queryParams$, (params) => params[param]);
106109
}
107110

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Params } from '@angular/router';
2+
import { StrictNoAny } from './util-types/strict-no-any';
3+
4+
/**
5+
* @remarks We use this type to ensure compatibility with {@link Params}.
6+
* @internal
7+
*/
8+
export type InternalStrictQueryParams = Readonly<
9+
StrictNoAny<Params, string | readonly string[] | undefined>
10+
>;

packages/router-component-store/src/lib/local-router-store/local-router-store.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { MinimalActivatedRouteSnapshot } from '../@ngrx/router-store/minimal-act
1717
import { MinimalRouterStateSnapshot } from '../@ngrx/router-store/minimal-router-state-snapshot';
1818
import { MinimalRouterStateSerializer } from '../@ngrx/router-store/minimal_serializer';
1919
import { filterRouterEvents } from '../filter-router-event.operator';
20+
import { InternalStrictQueryParams } from '../internal-strict-query-params';
2021
import { InternalStrictRouteData } from '../internal-strict-route-data';
2122
import { InternalStrictRouteParams } from '../internal-strict-route-params';
2223
import { RouterStore } from '../router-store';
@@ -44,7 +45,7 @@ export class LocalRouterStore
4445

4546
currentRoute$: Observable<MinimalActivatedRouteSnapshot> = this.#localRoute;
4647
fragment$: Observable<string | null>;
47-
queryParams$: Observable<InternalStrictRouteParams>;
48+
queryParams$: Observable<InternalStrictQueryParams>;
4849
routeData$: Observable<InternalStrictRouteData>;
4950
routeParams$: Observable<InternalStrictRouteParams>;
5051
title$: Observable<string | undefined>;
@@ -87,7 +88,9 @@ export class LocalRouterStore
8788
})
8889
);
8990

90-
selectQueryParam(param: string): Observable<string | undefined> {
91+
selectQueryParam(
92+
param: string
93+
): Observable<string | readonly string[] | undefined> {
9194
return this.select(this.queryParams$, (params) => params[param]);
9295
}
9396

packages/router-component-store/src/lib/router-store.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Injectable, Type } from '@angular/core';
22
import { Event as RouterEvent } from '@angular/router';
33
import { Observable } from 'rxjs';
44
import { MinimalActivatedRouteSnapshot } from './@ngrx/router-store/minimal-activated-route-state-snapshot';
5+
import { StrictQueryParams } from './strict-query-params';
56
import { StrictRouteData } from './strict-route-data';
67
import { StrictRouteParams } from './strict-route-params';
78

@@ -46,7 +47,7 @@ export abstract class RouterStore {
4647
/**
4748
* Select the current route query parameters.
4849
*/
49-
abstract readonly queryParams$: Observable<StrictRouteParams>;
50+
abstract readonly queryParams$: Observable<StrictQueryParams>;
5051
/**
5152
* Select the current route data.
5253
*/
@@ -80,7 +81,9 @@ export abstract class RouterStore {
8081
* @example <caption>Usage</caption>
8182
* const order$ = routerStore.selectQueryParam('order');
8283
*/
83-
abstract selectQueryParam(param: string): Observable<string | undefined>;
84+
abstract selectQueryParam(
85+
param: string
86+
): Observable<string | readonly string[] | undefined>;
8487
/**
8588
* Select the specified route parameter.
8689
*
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// [@typescript-eslint/no-unused-vars] Used in TSDoc.
2+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
3+
import { Params } from '@angular/router';
4+
5+
/**
6+
* Strict route query {@link Params} with read-only members where the `any` member
7+
* type is converted to `string | readonly string[] | undefined`.
8+
*/
9+
export interface StrictQueryParams {
10+
readonly [param: string]: string | readonly string[] | undefined;
11+
}

0 commit comments

Comments
 (0)