Skip to content

Commit

Permalink
feat!: use strict query params
Browse files Browse the repository at this point in the history
Array query parameters like `?size=m&size=l&size=xl` are 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]))
  );
}
```
  • Loading branch information
LayZeeDK committed Jan 30, 2025
1 parent 6ac16c9 commit d8f093d
Show file tree
Hide file tree
Showing 7 changed files with 41 additions and 12 deletions.
8 changes: 3 additions & 5 deletions packages/router-component-store/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
// GlobalRouterStore
// Serializable route state
export * from './lib/@ngrx/router-store/minimal-activated-route-state-snapshot';
export * from './lib/global-router-store/provide-global-router-store';

// LocalRouterStore
export * from './lib/local-router-store/provide-local-router-store';

// RouterStore
export * from './lib/router-store';

// Serializable route state
export * from './lib/@ngrx/router-store/minimal-activated-route-state-snapshot';
export * from './lib/strict-query-params';
export * from './lib/strict-route-data';
export * from './lib/strict-route-params';
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import { ActivatedRouteSnapshot } from '@angular/router';
import { StrictQueryParams } from '../../strict-query-params';
import { StrictRouteData } from '../../strict-route-data';
import { StrictRouteParams } from '../../strict-route-params';

Expand All @@ -54,7 +55,7 @@ export interface MinimalActivatedRouteSnapshot {
/**
* The query parameters shared by all the routes.
*/
readonly queryParams: StrictRouteParams;
readonly queryParams: StrictQueryParams;
/**
* The URL fragment shared by all the routes.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { MinimalActivatedRouteSnapshot } from '../@ngrx/router-store/minimal-act
import { MinimalRouterStateSnapshot } from '../@ngrx/router-store/minimal-router-state-snapshot';
import { MinimalRouterStateSerializer } from '../@ngrx/router-store/minimal_serializer';
import { filterRouterEvents } from '../filter-router-event.operator';
import { InternalStrictQueryParams } from '../internal-strict-query-params';
import { InternalStrictRouteData } from '../internal-strict-route-data';
import { InternalStrictRouteParams } from '../internal-strict-route-params';
import { RouterStore } from '../router-store';
Expand Down Expand Up @@ -52,7 +53,7 @@ export class GlobalRouterStore
this.#rootRoute$,
(route) => route.fragment
);
queryParams$: Observable<InternalStrictRouteParams> = this.select(
queryParams$: Observable<InternalStrictQueryParams> = this.select(
this.#rootRoute$,
(route) => route.queryParams
);
Expand Down Expand Up @@ -101,7 +102,9 @@ export class GlobalRouterStore
})
);

selectQueryParam(param: string): Observable<string | undefined> {
selectQueryParam(
param: string
): Observable<string | readonly string[] | undefined> {
return this.select(this.queryParams$, (params) => params[param]);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Params } from '@angular/router';
import { StrictNoAny } from './util-types/strict-no-any';

/**
* @remarks We use this type to ensure compatibility with {@link Params}.
* @internal
*/
export type InternalStrictQueryParams = Readonly<
StrictNoAny<Params, string | readonly string[] | undefined>
>;
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { MinimalActivatedRouteSnapshot } from '../@ngrx/router-store/minimal-act
import { MinimalRouterStateSnapshot } from '../@ngrx/router-store/minimal-router-state-snapshot';
import { MinimalRouterStateSerializer } from '../@ngrx/router-store/minimal_serializer';
import { filterRouterEvents } from '../filter-router-event.operator';
import { InternalStrictQueryParams } from '../internal-strict-query-params';
import { InternalStrictRouteData } from '../internal-strict-route-data';
import { InternalStrictRouteParams } from '../internal-strict-route-params';
import { RouterStore } from '../router-store';
Expand Down Expand Up @@ -44,7 +45,7 @@ export class LocalRouterStore

currentRoute$: Observable<MinimalActivatedRouteSnapshot> = this.#localRoute;
fragment$: Observable<string | null>;
queryParams$: Observable<InternalStrictRouteParams>;
queryParams$: Observable<InternalStrictQueryParams>;
routeData$: Observable<InternalStrictRouteData>;
routeParams$: Observable<InternalStrictRouteParams>;
title$: Observable<string | undefined>;
Expand Down Expand Up @@ -87,7 +88,9 @@ export class LocalRouterStore
})
);

selectQueryParam(param: string): Observable<string | undefined> {
selectQueryParam(
param: string
): Observable<string | readonly string[] | undefined> {
return this.select(this.queryParams$, (params) => params[param]);
}

Expand Down
7 changes: 5 additions & 2 deletions packages/router-component-store/src/lib/router-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Injectable, Type } from '@angular/core';
import { Event as RouterEvent } from '@angular/router';
import { Observable } from 'rxjs';
import { MinimalActivatedRouteSnapshot } from './@ngrx/router-store/minimal-activated-route-state-snapshot';
import { StrictQueryParams } from './strict-query-params';
import { StrictRouteData } from './strict-route-data';
import { StrictRouteParams } from './strict-route-params';

Expand Down Expand Up @@ -46,7 +47,7 @@ export abstract class RouterStore {
/**
* Select the current route query parameters.
*/
abstract readonly queryParams$: Observable<StrictRouteParams>;
abstract readonly queryParams$: Observable<StrictQueryParams>;
/**
* Select the current route data.
*/
Expand Down Expand Up @@ -80,7 +81,9 @@ export abstract class RouterStore {
* @example <caption>Usage</caption>
* const order$ = routerStore.selectQueryParam('order');
*/
abstract selectQueryParam(param: string): Observable<string | undefined>;
abstract selectQueryParam(
param: string
): Observable<string | readonly string[] | undefined>;
/**
* Select the specified route parameter.
*
Expand Down
11 changes: 11 additions & 0 deletions packages/router-component-store/src/lib/strict-query-params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// [@typescript-eslint/no-unused-vars] Used in TSDoc.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Params } from '@angular/router';

/**
* Strict route query {@link Params} with read-only members where the `any` member
* type is converted to `string | readonly string[] | undefined`.
*/
export interface StrictQueryParams {
readonly [param: string]: string | readonly string[] | undefined;
}

0 comments on commit d8f093d

Please sign in to comment.