Releases: ngworker/router-component-store
@ngworker/router-signal-store 17.0.0
First release of the signal-based router store. It has similar features to @ngworker/router-component-store but uses Angular Signals instead of RxJS Observables and has a peer dependency on NgRx Signals (@ngrx/signals) instead of NgRx Component Store (@ngrx/component-store).
Features
RouterSignalStoreshared API for dependency injectionprovideLocalRouterSignalStorefor providing a local router signal store, a replacement forActivatedRouteprovideGlobalRouterSignalStorefor providing the global router signal store, a replacement for NgRx Router Store- Strong and strict typing with
StrictQueryParams,StrictRouteData, andStrictRouteParams - Serializable router state with
MinimalActivatedRouteSnapshot
Compatibility
- Require Angular 17.0
- Require
@ngrx/signals17.0 - Require TypeScript 5.2
17.0.0
16.0.0
Features
- Add
RouterStore#selectRouteDataParam(param: string)to match NgRx Router Store selector name (#341)
Deprecations
- Deprecate
RouterStore#selectRouteData- Use
RouterStore#selectRouteDataParaminstead - To be removed in version 18
- Use
BREAKING CHANGES
Compatibility
- Require Angular 16.0
- Require
@ngrx/component-store16.0 - Require RxJS 7.5
- Require TypeScript 4.9
15.0.0
First stable release. No functional or API changes from 15.0.0-rc.2.
Features
Provide local or global router store using provideLocalRouterStore or provideGlobalRouterStore, respectively
- A local router store matches the
ActivatedRouteservice's observable properties and follow the lifecycle of the component that provides it - The global router store matches the
@ngrx/router-storeselectors and is never destroyed
Both local and global stores implement a common RouterStore API:
currentRoute$fragment$queryParams$routeData$routeParams$title$url$selectQueryParam(param: string)selectRouteData(key: string)selectRouteParam(param: string)selectRouterEvents(...acceptedRouterEvents: RouterEvent[])
RouterStore is also the injection symbol usable through constructor injection, inject, TestBed.inject, and Injector.get. When RouterStore is injected, it resolves to the closest provided local or global router store according to element and environment injectors.
RouterStore uses a serializable router state called MinimalActivatedRouteSnapshot. It uses additional strict, immutable types like StrictQueryParams, StrictRouteData, and StrictRouteParams.
15.0.0-rc.2
Features
- Use
StrictQueryParamsfor query parameters instead ofStrictRouteParams(#331)
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
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:
// 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:
// 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) => size ?? []),
map((size) => (Array.isArray(size) ? size : [size]))
);
}RouterStore#selectQueryParam uses StrictQueryParams
RouterStore#selectQueryParam uses 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:
// 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:
// 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) => size ?? []),
map((size) => (Array.isArray(size) ? size : [size]))
);
}15.0.0-rc.1
15.0.0-rc.0
Features
LocalRouterStorematchesActivatedRoutemore closely (#309)- Use
ActivatedRouteto serialize the router state for the local router store implementation (LocalRouterStore) LocalRouterStore.currentRoute$matchesActivatedRoute.snapshot
- Use
- Remove optional type parameter from
RouterStore#selectRouteData(#316) - Replace
MinimalRouteDatawithStrictRouteData(#319) - Change
RouterStore#routeData$andMinimalActivatedRouteSnapshot#datatypes fromDatatoStrictRouteData(#319) - Use strict and immutable route parameters (#319, #321)
- Use strict and immutable query parameters (#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:
// 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:
// 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:
// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';
@Component({
// (...)
})
export class HeroesComponent {
#routerStore = inject(RouterStore);
limit$ = this.#routerStore.selectRouteData<number>('limit');
}AFTER:
// 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:
// 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:
// 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))
);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:
// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';
@Component({
// (...)
})
export class DashboardComponent {
#routerStore = inject(RouterStore);
limit$: Observable<number> = this.#routerStore.routeParams$.pipe(
map((params) => params['limit'])
);
}AFTER:
// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';
@Component({
// (...)
})
export class DashboardComponent {
#routerStore = inject(RouterStore);
limit$: Observable<number> = 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:
// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';
@Component({
// (...)
})
export class DashboardComponent {
#routerStore = inject(RouterStore);
limit$: Observable<number> = this.#routerStore.routeData$.pipe(
map((data) => {
data['limit'] = Number(data['limit']);
return data;
}),
map((data) => data['limit'])
);
}AFTER:
// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';
@Component({
// (...)
})
export class DashboardComponent {
#routerStore = inject(RouterStore);
limit$: Observable<number> = 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:
// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';
@Component({
// (...)
})
export class DashboardComponent {
#routerStore = inject(RouterStore);
limit$: Observable<number> = this.#routerStore.queryParams$.pipe(
map((params) => params['limit'])
);
}AFTER:
// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';
@Component({
// (...)
})
export class DashboardComponent {
#routerStore = inject(RouterStore);
limit$: Observable<number> = 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).
- Require Angular 15.0
- Require
@ngrx/component-store15.0 - Require RxJS 7.5
- Require TypeScript 4.8
0.3.2
0.3.1
0.3.0
Features
- Add factory for selecting specific route data:
RouterStore#selectRouteData - Add route title to
MinimalActivatedRouteSnapshot#title - Add route title selector:
RouterStore#title$ - Add type
MinimalRouteDatafor serializable route data
BREAKING CHANGES
Remove symbol keys from Route data
To keep route data serializable, we have removed support for the Angular Router's Data type's symbol index in MinimalActivatedRouteSnapshot#data. In particular, this is done to remove the Symbol(RouteTitle) entry added by the Angular Router for internal use. Use our MinimalRouteData type instead of Data from @angular/router for route data.
Provider factories return provider arrays
The provideGlobalRouterStore and provideLocalRouterStore functions now return an array of providers (Provider[]) instead of a single provider (Provider). No changes required in your providers metadata, for example the following usage remains the same.
@Component({
// (...)
providers: [provideLocalRouterStore()],
})
// (...)Compatibility
To support the stricter route title type introduced by the Angular Router, we now require at least the following peer dependencies.
- Require Angular 15.0
- Require
@ngrx/component-store15.0 - Require RxJS 7.4
- Require TypeScript 4.8
We have dropped TypeScript constructor parameter properties for ECMAScript compatibility, namely the useDefineForClassFields TypeScript compiler option is true (the default when targeting ES2022 or higher).
We have dropped TypeScript constructor parameter decorators for ECMAScript decorators compatibility.