From df6dd236d51718e28564a6c7e9de4714a3c0d423 Mon Sep 17 00:00:00 2001 From: Lars Gyrup Brink Nielsen Date: Mon, 2 Sep 2024 22:58:27 +0200 Subject: [PATCH 1/3] docs: update tagline in package description --- packages/router-component-store/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/router-component-store/package.json b/packages/router-component-store/package.json index 9b968e7..0a46a4f 100644 --- a/packages/router-component-store/package.json +++ b/packages/router-component-store/package.json @@ -1,7 +1,7 @@ { "name": "@ngworker/router-component-store", "version": "0.3.2", - "description": "An Angular Router-connecting NgRx component store.", + "description": "A strictly typed lightweight alternative to NgRx Router Store (@ngrx/router-store) and ActivatedRoute", "license": "MIT", "peerDependencies": { "@angular/core": "^15.0.0", From 21b0dca11ec67c4f939f43750b82e0832b6a47e1 Mon Sep 17 00:00:00 2001 From: Lars Gyrup Brink Nielsen Date: Mon, 2 Sep 2024 22:59:13 +0200 Subject: [PATCH 2/3] chore: bump package version to `15.0.0-rc.0` --- packages/router-component-store/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/router-component-store/package.json b/packages/router-component-store/package.json index 0a46a4f..59e8261 100644 --- a/packages/router-component-store/package.json +++ b/packages/router-component-store/package.json @@ -1,6 +1,6 @@ { "name": "@ngworker/router-component-store", - "version": "0.3.2", + "version": "15.0.0-rc.0", "description": "A strictly typed lightweight alternative to NgRx Router Store (@ngrx/router-store) and ActivatedRoute", "license": "MIT", "peerDependencies": { From 3520133aa23b85327310d127d6aa8c30534236a1 Mon Sep 17 00:00:00 2001 From: Lars Gyrup Brink Nielsen Date: Mon, 2 Sep 2024 23:18:04 +0200 Subject: [PATCH 3/3] chore: log prerelease `15.0.0-rc.0` changes --- CHANGELOG.md | 286 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a0a015..7670646 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,291 @@ # Router Component Store changelog +## 15.0.0-rc.0 (2024-09-03) + +### Features + +- `LocalRouterStore` matches `ActivatedRoute` more closely ([#309](https://github.com/ngworker/router-component-store/pull/309)) + - Use `ActivatedRoute` to serialize the router state for the local router store implementation (`LocalRouterStore`) + - `LocalRouterStore.currentRoute$` matches `ActivatedRoute.snapshot` +- Remove optional type parameter from `RouterStore#selectRouteData` ([#316](https://github.com/ngworker/router-component-store/pull/316)) +- Replace `MinimalRouteData` with `StrictRouteData` ([#319](https://github.com/ngworker/router-component-store/pull/319)) +- Change `RouterStore#routeData$` and `MinimalActivatedRouteSnapshot#data` types from `Data` to `StrictRouteData` ([#319](https://github.com/ngworker/router-component-store/pull/319)) +- Use strict and immutable route parameters ([#319](https://github.com/ngworker/router-component-store/pull/319), [#321](https://github.com/ngworker/router-component-store/pull/321)) +- Use strict and immutable query parameters ([#320](https://github.com/ngworker/router-component-store/pull/320)) + +**BREAKING CHANGES** + +**`LocalRouterStore.currentRoute$` matches `ActivatedRoute.snapshot`** + +This change in implementation will make the local router store more closely match `ActivatedRoute` while the global router store matches NgRx Router Store selectors. Through complex route configurations, the router store implementations are exercised to identify edge case differences between them and any breaking changes introduced to the local router store. + +BEFORE: + +```typescript +// URL: /parent/child/grandchild + +@Component({ + /* (...) */ + providers: [provideLocalRouterStore()], +}) +export class ChildComponent implements OnInit { + #route = inject(ActivatedRoute); + #routerStore = inject(RouterStore); + + ngOnInit() { + const currentRouteSnapshot = this.#route.snapshot; + console.log(currentRouteSnapshot.routeConfig.path); + // -> "child" + console.log(currentRouteSnapshot.url[0].path); + // -> "child" + + firstValueFrom(this.#routerStore.currentRoute$).then((currentRoute) => { + console.log(currentRoute.routeConfig.path); + // -> "grandchild" + console.log(currentRoute.url[0].path); + // -> "grandchild" + }); + } +} +``` + +AFTER: + +```typescript +// URL: /parent/child/grandchild + +@Component({ + /* (...) */ + providers: [provideLocalRouterStore()], +}) +export class ChildComponent implements OnInit { + #route = inject(ActivatedRoute); + #routerStore = inject(RouterStore); + + ngOnInit() { + const currentRouteSnapshot = this.#route.snapshot; + console.log(currentRouteSnapshot.routeConfig.path); + // -> "child" + console.log(currentRouteSnapshot.url[0].path); + // -> "child" + + firstValueFrom(this.#routerStore.currentRoute$).then((currentRoute) => { + console.log(currentRoute.routeConfig.path); + // -> "child" + console.log(currentRoute.url[0].path); + // -> "child" + }); + } +} +``` + +**The type parameter is removed from `RouterStore#selectRouteData` for stricter typing and to enforce coercion** + +BEFORE: + +```typescript +// heroes.component.ts +// (...) +import { RouterStore } from '@ngworker/router-component-store'; +@Component({ + // (...) +}) +export class HeroesComponent { + #routerStore = inject(RouterStore); + limit$ = this.#routerStore.selectRouteData('limit'); +} +``` + +AFTER: + +```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: + +```typescript +// heroes.component.ts +// (...) +import { RouterStore } from '@ngworker/router-component-store'; +@Component({ + // (...) +}) +export class HeroesComponent { + #routerStore = inject(RouterStore); + limit$: Observable = this.#routerStore.routeData$.pipe( + map((routeData) => routeData['limit']) + ); +} +``` + +AFTER: + +```typescript +// heroes.component.ts +// (...) +import { RouterStore } from '@ngworker/router-component-store'; +@Component({ + // (...) +}) +export class HeroesComponent { + #routerStore = inject(RouterStore); + limit$: Observable = this.#routerStore.routeData$.pipe( + map(routeData => routeData['limit']), + map(x => Number(x)) + ); +``` + +**`RouterStore#routeParams$` and `MinimalActivatedRouteSnapshot#params` use `StrictRouteData` instead of `Params`. Members are read-only and of type `string | undefined` instead of `any`** + +TypeScript will fail to compile application code that has assumed a route type parameter type other than `string | undefined`. + +BEFORE: + +```typescript +// heroes.component.ts +// (...) +import { RouterStore } from '@ngworker/router-component-store'; + +@Component({ + // (...) +}) +export class DashboardComponent { + #routerStore = inject(RouterStore); + + limit$: Observable = this.#routerStore.routeParams$.pipe( + map((params) => params['limit']) + ); +} +``` + +AFTER: + +```typescript +// heroes.component.ts +// (...) +import { RouterStore } from '@ngworker/router-component-store'; + +@Component({ + // (...) +}) +export class DashboardComponent { + #routerStore = inject(RouterStore); + + limit$: Observable = this.#routerStore.routeParams$.pipe( + map((params) => Number(params['limit'] ?? 10)) + ); +} +``` + +**`StrictRouteData` members are now read-only** + +TypeScript will fail to compile application code that mutates route data data structures. + +BEFORE: + +```typescript +// heroes.component.ts +// (...) +import { RouterStore } from '@ngworker/router-component-store'; + +@Component({ + // (...) +}) +export class DashboardComponent { + #routerStore = inject(RouterStore); + + limit$: Observable = this.#routerStore.routeData$.pipe( + map((data) => { + data['limit'] = Number(data['limit']); + + return data; + }), + map((data) => data['limit']) + ); +} +``` + +AFTER: + +```typescript +// heroes.component.ts +// (...) +import { RouterStore } from '@ngworker/router-component-store'; + +@Component({ + // (...) +}) +export class DashboardComponent { + #routerStore = inject(RouterStore); + + limit$: Observable = this.#routerStore.routeData$.pipe( + map((data) => Number(data['limit'])) + ); +} +``` + +**`RouterStore#queryParams$` and `MinimalActivatedRouteSnapshot#queryParams` use `StrictRouteParams` instead of `Params`. Members are read-only and of type `string | undefined` instead of `any`** + +TypeScript will fail to compile application code that has assumed a query parameter type other than `string | undefined`. + +BEFORE: + +```typescript +// heroes.component.ts +// (...) +import { RouterStore } from '@ngworker/router-component-store'; + +@Component({ + // (...) +}) +export class DashboardComponent { + #routerStore = inject(RouterStore); + + limit$: Observable = this.#routerStore.queryParams$.pipe( + map((params) => params['limit']) + ); +} +``` + +AFTER: + +```typescript +// heroes.component.ts +// (...) +import { RouterStore } from '@ngworker/router-component-store'; + +@Component({ + // (...) +}) +export class DashboardComponent { + #routerStore = inject(RouterStore); + + limit$: Observable = this.#routerStore.queryParams$.pipe( + map((params) => Number(params['limit'] ?? 10)) + ); +} +``` + +**Compatibility** + +To avoid compatibility issues, we now require the same RxJS peer dependency as NgRx ComponentStore, namely at least RxJS version 7.5 ([#311](https://github.com/ngworker/router-component-store/pull/311)). + +- Require Angular 15.0 +- Require `@ngrx/component-store` 15.0 +- Require RxJS 7.5 +- Require TypeScript 4.8 + ## 0.3.2 (2023-01-03) ### Performance optimizations