Skip to content

Commit

Permalink
feat!: use strict route data (#316)
Browse files Browse the repository at this point in the history
## Features

- Remove optional type parameter from `RouterStore#selectRouteData`
- Replace `MinimalRouteData` with `StrictRouteData`
- Change `RouterStore#routeData$` type from `Data` to `StrictRouteData`

**BREAKING CHANGES**

The type parameter is removed from `RouterStore#selectRouteData` for
stricter typing and to enforce coercion.

BEFORE:

*(Router Component Store <=0.3.2)*

```typescript
// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';
@component({
  // (...)
})
export class HeroesComponent {
  #routerStore = inject(RouterStore);
  limit$ = this.#routerStore.selectRouteData<number>('limit');
}
```

AFTER:

*(Router Component Store >0.3.2)*

```typescript
// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';
@component({
  // (...)
})
export class HeroesComponent {
  #routerStore = inject(RouterStore);
  limit$ = this.#routerStore.selectRouteData('limit').pipe(x => Number(x));
```

The `RouterStore#routeData$` selector emits `StrictRouteData` instead of
`Data`.

BEFORE:

_(Router Component Store <=0.3.2)_

```typescript
// heroes.component.ts
// (...)
import { RouterStore } from "@ngworker/router-component-store";
@component({
  // (...)
})
export class HeroesComponent {
  #routerStore = inject(RouterStore);
  limit$: Observable<number> = this.#routerStore.routeData$.pipe(
    map((routeData) => routeData["limit"])
  );
}
```

AFTER:

_(Router Component Store >0.3.2)_

```typescript
// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';
@component({
  // (...)
})
export class HeroesComponent {
  #routerStore = inject(RouterStore);
  limit$: Observable<number> = this.#routerStore.routeData$.pipe(
    map(routeData => routeData['limit']),
    map(x => Number(x))
  );
```
  • Loading branch information
LayZeeDK authored Aug 31, 2024
2 parents 6cadb32 + 36de5c3 commit 4d93a54
Show file tree
Hide file tree
Showing 16 changed files with 74 additions and 52 deletions.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ A `RouterStore` service has the following public properties.
| `currentRoute$: Observable<MinimalActivatedRouteSnapshot>` | Select the current route. |
| `fragment$: Observable<string \| null>` | Select the current route fragment. |
| `queryParams$: Observable<Params>` | Select the current route query parameters. |
| `routeData$: Observable<Data>` | Select the current route data. |
| `routeData$: Observable<StrictRouteData>` | Select the current route data. |
| `routeParams$: Observable<Params>` | Select the current route parameters. |
| `title$: Observable<string \| undefined>` | Select the resolved route title. |
| `url$: Observable<string>` | Select the current URL. |
| `selectQueryParam(param: string): Observable<string \| undefined>` | Select the specified query parameter. |
| `selectRouteData<TValue>(key: string): Observable<TValue \| undefined>` | Select the specified route data. |
| `selectRouteData(key: string): Observable<unknown>` | Select the specified route data. |
| `selectRouteParam(param: string): Observable<string \| undefined>` | Select the specified route parameter. |
| `selectRouterEvents(...acceptedRouterEvents: RouterEvent[]): Observable<RouterEvent>` | Select router events of the specified router event types. |

Expand Down Expand Up @@ -169,7 +169,7 @@ The `MinimalActivatedRouteSnapshot` interface is used for the observable `Router
| API | Description |
| --------------------------------------------------- | ------------------------------------------------ |
| `children: MinimalActivatedRouteSnapshot[]` | The children of this route in the route tree. |
| `data: MinimalRouteData` | The static and resolved data of this route. |
| `data: StrictRouteData` | The static and resolved data of this route. |
| `firstChild: MinimalActivatedRouteSnapshot \| null` | The first child of this route in the route tree. |
| `fragment: string \| null` | The URL fragment shared by all routes. |
| `outlet: string` | The outlet name of the route. |
Expand All @@ -179,12 +179,14 @@ The `MinimalActivatedRouteSnapshot` interface is used for the observable `Router
| `title: string \| undefined` | The resolved route title. |
| `url: UrlSegment[]` | The URL segments matched by this route. |

#### MinimalRouteData
#### StrictRouteData

The `MinimalRouteData` interface is used for the `RouterStore#data$` property. This interface is a serializable subset of the Angular Router's `Data` type. In particular, the `symbol` index in the Angular Router's `Data` type is removed. `MinimalRouteData` has the following signature.
The `StrictRouteData` interface is used for the `MinimalActivatedRouteSnapshot#data$` and `RouterStore#routeData$` properties. This interface is a serializable subset of the Angular Router's `Data` type. In particular, the `symbol` index in the Angular Router's `Data` type is removed. Additionally, the `any` member type is replaced with `unknown` for stricter typing.

`StrictRouteData` has the following signature.

```typescript
export type MinimalRouteData = {
[key: string]: any;
export type StrictRouteData = {
[key: string]: unknown;
};
```
2 changes: 1 addition & 1 deletion packages/router-component-store/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ export * from './lib/router-store';

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

/**
* Contains the information about a route associated with a component loaded in
Expand Down Expand Up @@ -67,7 +67,7 @@ export interface MinimalActivatedRouteSnapshot {
* the Angular `Router`. Instead, we access the resolved route title through
* `MinimalActivatedRouteSnapshot['title']`.
*/
readonly data: MinimalRouteData;
readonly data: StrictRouteData;
/**
* The outlet name of the route.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
Data,
RouterStateSnapshot,
} from '@angular/router';
import { MinimalRouteData } from '../../minimal-route-data';
import { StrictRouteData } from '../../strict-route-data';
import { MinimalActivatedRouteSnapshot } from './minimal-activated-route-state-snapshot';
import { MinimalRouterStateSnapshot } from './minimal-router-state-snapshot';

Expand All @@ -43,7 +43,7 @@ export class MinimalRouterStateSerializer {
};
}

#serializeRouteData(routeData: Data): MinimalRouteData {
#serializeRouteData(routeData: Data): StrictRouteData {
return Object.fromEntries(Object.entries(routeData));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { RouterConfigOptions, Routes } from '@angular/router';
import { firstValueFrom } from 'rxjs';
import { MinimalRouteData } from '../minimal-route-data';
import { RouterStore } from '../router-store';
import { StrictRouteData } from '../strict-route-data';
import { GlobalRouterStore } from './global-router-store';
import { globalRouterStoreSetup } from './test-util/global-router-store-setup';
import {
Expand Down Expand Up @@ -103,7 +103,7 @@ describe(`${GlobalRouterStore.name} componentless nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
componentlessBeforeParent:
'componentless-route-data-before-parent',
parent: 'parent-route-data',
Expand Down Expand Up @@ -167,7 +167,7 @@ describe(`${GlobalRouterStore.name} componentless nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
componentlessBeforeParent:
'componentless-route-data-before-parent',
parent: 'parent-route-data',
Expand Down Expand Up @@ -218,7 +218,7 @@ describe(`${GlobalRouterStore.name} componentless nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
componentlessBeforeParent: 'componentless-route-data-before-parent',
parent: 'parent-route-data',
componentlessBeforeChild: 'componentless-route-data-before-child',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { inject, Injectable, Type } from '@angular/core';
import {
Data,
Event as RouterEvent,
NavigationCancel,
NavigationEnd,
Expand All @@ -17,6 +16,7 @@ import { MinimalRouterStateSnapshot } from '../@ngrx/router-store/minimal-router
import { MinimalRouterStateSerializer } from '../@ngrx/router-store/minimal_serializer';
import { filterRouterEvents } from '../filter-router-event.operator';
import { RouterStore } from '../router-store';
import { StrictRouteData } from '../strict-route-data';

interface GlobalRouterState {
readonly routerState: MinimalRouterStateSnapshot;
Expand Down Expand Up @@ -56,7 +56,7 @@ export class GlobalRouterStore
this.#rootRoute$,
(route) => route.queryParams
);
routeData$: Observable<Data> = this.select(
routeData$: Observable<StrictRouteData> = this.select(
this.currentRoute$,
(route) => route.data
);
Expand Down Expand Up @@ -105,7 +105,7 @@ export class GlobalRouterStore
return this.select(this.queryParams$, (params) => params[param]);
}

selectRouteData<TValue>(key: string): Observable<TValue | undefined> {
selectRouteData(key: string): Observable<unknown> {
return this.select(this.routeData$, (data) => data[key]);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Routes } from '@angular/router';
import { firstValueFrom } from 'rxjs';
import { MinimalRouteData } from '../minimal-route-data';
import { RouterStore } from '../router-store';
import { StrictRouteData } from '../strict-route-data';
import { GlobalRouterStore } from './global-router-store';
import { globalRouterStoreSetup } from './test-util/global-router-store-setup';
import {
Expand Down Expand Up @@ -63,7 +63,7 @@ describe(`${GlobalRouterStore.name} nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
grandchild: 'grandchild-route-data',
shadowed: 'grandchild-route-data',
};
Expand Down Expand Up @@ -104,7 +104,7 @@ describe(`${GlobalRouterStore.name} nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
child: 'child-route-data',
shadowed: 'child-route-data',
};
Expand Down Expand Up @@ -136,7 +136,7 @@ describe(`${GlobalRouterStore.name} nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
parent: 'parent-route-data',
shadowed: 'parent-route-data',
};
Expand Down Expand Up @@ -181,7 +181,7 @@ describe(`${GlobalRouterStore.name} nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
parent: 'parent-route-data',
child: 'child-route-data',
grandchild: 'grandchild-route-data',
Expand Down Expand Up @@ -229,7 +229,7 @@ describe(`${GlobalRouterStore.name} nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
parent: 'parent-route-data',
child: 'child-route-data',
shadowed: 'child-route-data',
Expand Down Expand Up @@ -264,7 +264,7 @@ describe(`${GlobalRouterStore.name} nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
parent: 'parent-route-data',
shadowed: 'parent-route-data',
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import { provideStore, Store } from '@ngrx/store';
import { createFeatureHarness } from '@ngworker/spectacular';
import { filter, firstValueFrom, take, toArray } from 'rxjs';
import { MinimalActivatedRouteSnapshot } from '../@ngrx/router-store/minimal-activated-route-state-snapshot';
import { MinimalRouteData } from '../minimal-route-data';
import { RouterStore } from '../router-store';
import { StrictRouteData } from '../strict-route-data';
import { GlobalRouterStore } from './global-router-store';
import { provideGlobalRouterStore } from './provide-global-router-store';

Expand Down Expand Up @@ -215,7 +215,7 @@ describe(`${GlobalRouterStore.name} selectors`, () => {
});

it('exposes a selector for route data', async () => {
const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
testData: 'test-data',
};
const { harness, ngrxRouterStore } = setup({
Expand Down Expand Up @@ -249,9 +249,7 @@ describe(`${GlobalRouterStore.name} selectors`, () => {
);

await expect(
firstValueFrom(
harness.inject(RouterStore).selectRouteData<string>('testData')
)
firstValueFrom(harness.inject(RouterStore).selectRouteData('testData'))
).resolves.toBe(expectedTestData);
await expect(
firstValueFrom(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { RouterConfigOptions, Routes } from '@angular/router';
import { firstValueFrom } from 'rxjs';
import { MinimalRouteData } from '../minimal-route-data';
import { RouterStore } from '../router-store';
import { StrictRouteData } from '../strict-route-data';
import { LocalRouterStore } from './local-router-store';
import { localRouterStoreSetup } from './test-util/local-router-store-setup';
import {
Expand Down Expand Up @@ -93,7 +93,7 @@ describe(`${LocalRouterStore.name} componentless nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
componentlessBeforeParent:
'componentless-route-data-before-parent',
parent: 'parent-route-data',
Expand Down Expand Up @@ -136,7 +136,7 @@ describe(`${LocalRouterStore.name} componentless nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
componentlessBeforeParent:
'componentless-route-data-before-parent',
parent: 'parent-route-data',
Expand Down Expand Up @@ -185,7 +185,7 @@ describe(`${LocalRouterStore.name} componentless nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
componentlessBeforeParent: 'componentless-route-data-before-parent',
parent: 'parent-route-data',
componentlessBeforeChild: 'componentless-route-data-before-child',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { inject, Injectable, Type } from '@angular/core';
import {
ActivatedRoute,
createUrlTreeFromSnapshot,
Data,
Event as RouterEvent,
NavigationCancel,
NavigationEnd,
Expand All @@ -20,6 +19,7 @@ import { MinimalRouterStateSnapshot } from '../@ngrx/router-store/minimal-router
import { MinimalRouterStateSerializer } from '../@ngrx/router-store/minimal_serializer';
import { filterRouterEvents } from '../filter-router-event.operator';
import { RouterStore } from '../router-store';
import { StrictRouteData } from '../strict-route-data';

interface LocalRouterState {
readonly routerState: MinimalRouterStateSnapshot;
Expand All @@ -45,7 +45,7 @@ export class LocalRouterStore
currentRoute$: Observable<MinimalActivatedRouteSnapshot> = this.#localRoute;
fragment$: Observable<string | null>;
queryParams$: Observable<Params>;
routeData$: Observable<Data>;
routeData$: Observable<StrictRouteData>;
routeParams$: Observable<Params>;
title$: Observable<string | undefined>;
url$: Observable<string> = this.select(
Expand Down Expand Up @@ -91,7 +91,7 @@ export class LocalRouterStore
return this.select(this.queryParams$, (params) => params[param]);
}

selectRouteData<TValue>(key: string): Observable<TValue | undefined> {
selectRouteData(key: string): Observable<unknown> {
return this.select(this.routeData$, (data) => data[key]);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { RouterConfigOptions, Routes } from '@angular/router';
import { firstValueFrom } from 'rxjs';
import { MinimalRouteData } from '../minimal-route-data';
import { RouterStore } from '../router-store';
import { StrictRouteData } from '../strict-route-data';
import { LocalRouterStore } from './local-router-store';
import { localRouterStoreSetup } from './test-util/local-router-store-setup';
import {
Expand Down Expand Up @@ -63,7 +63,7 @@ describe(`${LocalRouterStore.name} nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
parent: 'parent-route-data',
shadowed: 'parent-route-data',
};
Expand Down Expand Up @@ -100,7 +100,7 @@ describe(`${LocalRouterStore.name} nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
child: 'child-route-data',
shadowed: 'child-route-data',
};
Expand Down Expand Up @@ -132,7 +132,7 @@ describe(`${LocalRouterStore.name} nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
grandchild: 'grandchild-route-data',
shadowed: 'grandchild-route-data',
};
Expand Down Expand Up @@ -170,7 +170,7 @@ describe(`${LocalRouterStore.name} nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
parent: 'parent-route-data',
child: 'child-route-data',
shadowed: 'child-route-data',
Expand Down Expand Up @@ -208,7 +208,7 @@ describe(`${LocalRouterStore.name} nested route data`, () => {
routes,
});

const expectedRouteData: MinimalRouteData = {
const expectedRouteData: StrictRouteData = {
parent: 'parent-route-data',
child: 'child-route-data',
grandchild: 'grandchild-route-data',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ describe(`${LocalRouterStore.name} selectors`, () => {
firstValueFrom(
injectorFor(DummyAuthComponent)
.get(RouterStore)
.selectRouteData<string>('testData')
.selectRouteData('testData')
)
).resolves.toBe('test-data');
});
Expand Down
Loading

0 comments on commit 4d93a54

Please sign in to comment.