Skip to content

Commit 976d10b

Browse files
committed
refactor: implement GlobalRouterSignalState using best NgRx Signals practices
1 parent b2ebb14 commit 976d10b

File tree

1 file changed

+99
-96
lines changed

1 file changed

+99
-96
lines changed
Lines changed: 99 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1-
import { computed, inject, Injectable, Signal, Type } from '@angular/core';
1+
import { InjectionToken, Signal, Type, computed, inject } from '@angular/core';
22
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
33
import {
4-
Event as RouterEvent,
54
NavigationCancel,
65
NavigationEnd,
76
NavigationError,
87
NavigationStart,
98
Router,
9+
Event as RouterEvent,
1010
RoutesRecognized,
1111
} from '@angular/router';
12-
import { patchState, signalStore, withState } from '@ngrx/signals';
13-
import { map, Observable } from 'rxjs';
12+
import {
13+
patchState,
14+
signalStore,
15+
withComputed,
16+
withHooks,
17+
withMethods,
18+
withState,
19+
} from '@ngrx/signals';
20+
import { Observable, map } from 'rxjs';
1421
import { MinimalActivatedRouteSnapshot } from '../@ngrx/router-store/minimal-activated-route-state-snapshot';
1522
import { MinimalRouterStateSnapshot } from '../@ngrx/router-store/minimal-router-state-snapshot';
1623
import { MinimalRouterStateSerializer } from '../@ngrx/router-store/minimal_serializer';
@@ -20,102 +27,98 @@ import { InternalStrictRouteData } from '../internal-strict-route-data';
2027
import { InternalStrictRouteParams } from '../internal-strict-route-params';
2128
import { RouterSignalStore } from '../router-signal-store';
2229

23-
interface GlobalRouterState {
24-
readonly routerState: MinimalRouterStateSnapshot;
30+
interface GlobalRouterSignalState {
31+
readonly _routerState: MinimalRouterStateSnapshot;
2532
}
2633

27-
const GlobalRouterSignalStoreBase = signalStore(
28-
withState<GlobalRouterState>({
29-
routerState: {} as MinimalRouterStateSnapshot,
30-
})
31-
);
32-
33-
@Injectable()
34-
export class GlobalRouterSignalStore
35-
extends GlobalRouterSignalStoreBase
36-
implements RouterSignalStore
37-
{
38-
#router = inject(Router);
39-
#serializer = inject(MinimalRouterStateSerializer);
34+
function createInitialGlobalRouterSignalState(): GlobalRouterSignalState {
35+
const router = inject(Router);
36+
const serializer = inject(MinimalRouterStateSerializer);
4037

41-
#rootRoute: Signal<MinimalActivatedRouteSnapshot> = computed(
42-
() => this.routerState().root
43-
);
38+
return {
39+
_routerState: serializer.serialize(router.routerState.snapshot),
40+
};
41+
}
4442

45-
currentRoute: Signal<MinimalActivatedRouteSnapshot> = computed(() => {
46-
let route = this.#rootRoute();
47-
while (route.firstChild) {
48-
route = route.firstChild;
43+
const initialGlobalRouterSignalStateToken =
44+
new InjectionToken<GlobalRouterSignalState>(
45+
'initialGlobalRouterSignalStateToken',
46+
{
47+
factory: createInitialGlobalRouterSignalState,
4948
}
50-
return route;
51-
});
52-
53-
fragment: Signal<string | null> = computed(() => this.#rootRoute().fragment);
54-
55-
queryParams: Signal<InternalStrictQueryParams> = computed(
56-
() => this.#rootRoute().queryParams
5749
);
5850

59-
routeData: Signal<InternalStrictRouteData> = computed(
60-
() => this.currentRoute().data
61-
);
62-
63-
routeParams: Signal<InternalStrictRouteParams> = computed(
64-
() => this.currentRoute().params
65-
);
66-
67-
title: Signal<string | undefined> = computed(() => this.currentRoute().title);
68-
69-
url: Signal<string> = computed(() => this.routerState().url);
70-
71-
constructor() {
72-
super();
73-
74-
// Initialize state with current router snapshot
75-
patchState(this, {
76-
routerState: this.#serializer.serialize(
77-
this.#router.routerState.snapshot
78-
),
79-
});
80-
81-
// Subscribe to router events and update state
82-
this.selectRouterEvents(
83-
NavigationStart,
84-
RoutesRecognized,
85-
NavigationEnd,
86-
NavigationCancel,
87-
NavigationError
88-
)
89-
.pipe(
90-
map(() =>
91-
this.#serializer.serialize(this.#router.routerState.snapshot)
92-
),
93-
takeUntilDestroyed()
94-
)
95-
.subscribe((routerState) => {
96-
patchState(this, { routerState });
97-
});
98-
}
99-
100-
selectQueryParam(
101-
param: string
102-
): Signal<string | readonly string[] | undefined> {
103-
return computed(() => this.queryParams()[param]);
104-
}
105-
106-
selectRouteDataParam(key: string): Signal<unknown> {
107-
return computed(() => this.routeData()[key]);
108-
}
109-
110-
selectRouteParam(param: string): Signal<string | undefined> {
111-
return computed(() => this.routeParams()[param]);
112-
}
113-
114-
selectRouterEvents<TAcceptedRouterEvents extends Type<RouterEvent>[]>(
115-
...acceptedEventTypes: [...TAcceptedRouterEvents]
116-
): Observable<InstanceType<TAcceptedRouterEvents[number]>> {
117-
return this.#router.events.pipe(
118-
filterRouterEvents(...acceptedEventTypes)
119-
) as Observable<InstanceType<TAcceptedRouterEvents[number]>>;
120-
}
121-
}
51+
export const GlobalRouterSignalStore: Type<RouterSignalStore> = signalStore(
52+
withState(() => inject(initialGlobalRouterSignalStateToken)),
53+
withComputed(({ _routerState }) => ({
54+
_rootRoute: computed(
55+
(): MinimalActivatedRouteSnapshot => _routerState().root
56+
),
57+
})),
58+
withComputed(({ _rootRoute }) => ({
59+
currentRoute: computed((): MinimalActivatedRouteSnapshot => {
60+
let route = _rootRoute();
61+
62+
while (route.firstChild) {
63+
route = route.firstChild;
64+
}
65+
66+
return route;
67+
}),
68+
})),
69+
withComputed(({ _rootRoute, _routerState, currentRoute }) => ({
70+
fragment: computed((): string | null => _rootRoute().fragment),
71+
queryParams: computed(
72+
(): InternalStrictQueryParams => _rootRoute().queryParams
73+
),
74+
routeData: computed((): InternalStrictRouteData => currentRoute().data),
75+
routeParams: computed(
76+
(): InternalStrictRouteParams => currentRoute().params
77+
),
78+
title: computed((): string | undefined => currentRoute().title),
79+
url: computed((): string => _routerState().url),
80+
})),
81+
withMethods((store, router = inject(Router)) => ({
82+
selectQueryParam(
83+
param: string
84+
): Signal<string | readonly string[] | undefined> {
85+
return computed(() => store.queryParams()[param]);
86+
},
87+
selectRouteDataParam(key: string): Signal<unknown> {
88+
return computed(() => store.routeData()[key]);
89+
},
90+
selectRouteParam(param: string): Signal<string | undefined> {
91+
return computed(() => store.routeParams()[param]);
92+
},
93+
selectRouterEvents<TAcceptedRouterEvents extends Type<RouterEvent>[]>(
94+
...acceptedEventTypes: [...TAcceptedRouterEvents]
95+
): Observable<InstanceType<TAcceptedRouterEvents[number]>> {
96+
return router.events.pipe(
97+
filterRouterEvents(...acceptedEventTypes)
98+
) as Observable<InstanceType<TAcceptedRouterEvents[number]>>;
99+
},
100+
})),
101+
withHooks({
102+
onInit(
103+
store,
104+
router = inject(Router),
105+
serializer = inject(MinimalRouterStateSerializer)
106+
): void {
107+
store
108+
.selectRouterEvents(
109+
NavigationStart,
110+
RoutesRecognized,
111+
NavigationEnd,
112+
NavigationCancel,
113+
NavigationError
114+
)
115+
.pipe(
116+
map(() => serializer.serialize(router.routerState.snapshot)),
117+
takeUntilDestroyed()
118+
)
119+
.subscribe((routerState) =>
120+
patchState(store, { _routerState: routerState })
121+
);
122+
},
123+
})
124+
);

0 commit comments

Comments
 (0)