Skip to content

Commit ea6ff13

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

File tree

2 files changed

+133
-105
lines changed

2 files changed

+133
-105
lines changed

packages/router-signal-store/src/lib/global-router-signal-store/global-router-signal-store.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ const initialGlobalRouterSignalStateToken =
4949
);
5050

5151
export const GlobalRouterSignalStore: Type<RouterSignalStore> = signalStore(
52-
withState(() => inject(initialGlobalRouterSignalStateToken)),
52+
withState<GlobalRouterSignalState>(() =>
53+
inject(initialGlobalRouterSignalStateToken)
54+
),
5355
withComputed(({ _routerState }) => ({
5456
_rootRoute: computed(
5557
(): MinimalActivatedRouteSnapshot => _routerState().root
Lines changed: 130 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,160 @@
1-
import { computed, inject, Injectable, Signal, Type } from '@angular/core';
1+
import { InjectionToken, Signal, Type, computed, inject } from '@angular/core';
22
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
33
import {
44
ActivatedRoute,
5-
createUrlTreeFromSnapshot,
6-
Event as RouterEvent,
75
NavigationCancel,
86
NavigationEnd,
97
NavigationError,
108
NavigationStart,
119
Router,
10+
Event as RouterEvent,
1211
RouterStateSnapshot,
1312
RoutesRecognized,
13+
createUrlTreeFromSnapshot,
1414
} from '@angular/router';
15-
import { patchState, signalStore, withState } from '@ngrx/signals';
16-
import { map, Observable } from 'rxjs';
17-
import { MinimalActivatedRouteSnapshot } from '../@ngrx/router-store/minimal-activated-route-state-snapshot';
15+
import {
16+
patchState,
17+
signalStore,
18+
withComputed,
19+
withHooks,
20+
withMethods,
21+
withState,
22+
} from '@ngrx/signals';
23+
import { Observable, map } from 'rxjs';
1824
import { MinimalRouterStateSnapshot } from '../@ngrx/router-store/minimal-router-state-snapshot';
1925
import { MinimalRouterStateSerializer } from '../@ngrx/router-store/minimal_serializer';
2026
import { filterRouterEvents } from '../filter-router-event.operator';
27+
import { RouterSignalStore } from '../router-signal-store';
28+
import { MinimalActivatedRouteSnapshot } from '@ngrx/router-store';
2129
import { InternalStrictQueryParams } from '../internal-strict-query-params';
2230
import { InternalStrictRouteData } from '../internal-strict-route-data';
2331
import { InternalStrictRouteParams } from '../internal-strict-route-params';
24-
import { RouterSignalStore } from '../router-signal-store';
2532

26-
interface LocalRouterState {
27-
readonly routerState: MinimalRouterStateSnapshot;
33+
interface LocalRouterSignalState {
34+
readonly _routerState: MinimalRouterStateSnapshot;
2835
}
2936

30-
const LocalRouterSignalStoreBase = signalStore(
31-
withState<LocalRouterState>({
32-
routerState: {} as MinimalRouterStateSnapshot,
33-
})
34-
);
35-
36-
@Injectable()
37-
export class LocalRouterSignalStore
38-
extends LocalRouterSignalStoreBase
39-
implements RouterSignalStore
40-
{
41-
#route = inject(ActivatedRoute);
42-
#router = inject(Router);
43-
#serializer = inject(MinimalRouterStateSerializer);
44-
45-
#localRoute: Signal<MinimalActivatedRouteSnapshot> = computed(
46-
() => this.routerState().root
47-
);
48-
49-
currentRoute: Signal<MinimalActivatedRouteSnapshot> = this.#localRoute;
50-
51-
fragment: Signal<string | null> = toSignal(this.#route.fragment, {
52-
initialValue: null,
53-
});
54-
queryParams: Signal<InternalStrictQueryParams> = toSignal(
55-
this.#route.queryParams,
56-
{ initialValue: {} }
57-
);
58-
routeData: Signal<InternalStrictRouteData> = toSignal(this.#route.data, {
59-
initialValue: {},
60-
});
61-
routeParams: Signal<InternalStrictRouteParams> = toSignal(
62-
this.#route.params,
63-
{ initialValue: {} }
64-
);
65-
title: Signal<string | undefined> = computed(() => this.currentRoute().title);
66-
67-
url: Signal<string> = computed(() => this.routerState().url);
37+
function createInitialLocalRouterSignalState(): LocalRouterSignalState {
38+
const route = inject(ActivatedRoute);
39+
const router = inject(Router);
40+
const serializer = inject(MinimalRouterStateSerializer);
6841

69-
constructor() {
70-
super();
71-
72-
// Initialize state with current router snapshot
73-
patchState(this, {
74-
routerState: this.#serializeRouterState(this.#route),
75-
});
42+
return {
43+
_routerState: serializeRouterState(route, router, serializer),
44+
};
45+
}
7646

77-
// Subscribe to router events and update state
78-
this.selectRouterEvents(
79-
NavigationStart,
80-
RoutesRecognized,
81-
NavigationEnd,
82-
NavigationCancel,
83-
NavigationError
84-
)
85-
.pipe(
86-
map(() => this.#route),
87-
map((route) => this.#serializeRouterState(route)),
88-
takeUntilDestroyed()
47+
function createRouterStateSnapshot(
48+
route: ActivatedRoute,
49+
router: Router
50+
): RouterStateSnapshot {
51+
return {
52+
root: route.snapshot,
53+
url: router.serializeUrl(
54+
createUrlTreeFromSnapshot(
55+
route.snapshot,
56+
[],
57+
route.snapshot.queryParams,
58+
route.snapshot.fragment
8959
)
90-
.subscribe((routerState) => {
91-
patchState(this, { routerState });
92-
});
93-
}
94-
95-
selectQueryParam(
96-
param: string
97-
): Signal<string | readonly string[] | undefined> {
98-
return computed(() => this.queryParams()[param]);
99-
}
60+
),
61+
};
62+
}
10063

101-
selectRouteDataParam(key: string): Signal<unknown> {
102-
return computed(() => this.routeData()[key]);
103-
}
64+
function serializeRouterState(
65+
route: ActivatedRoute,
66+
router: Router,
67+
serializer: MinimalRouterStateSerializer
68+
): MinimalRouterStateSnapshot {
69+
return serializer.serialize(createRouterStateSnapshot(route, router));
70+
}
10471

105-
selectRouteParam(param: string): Signal<string | undefined> {
106-
return computed(() => this.routeParams()[param]);
107-
}
72+
const initialLocalRouterSignalStateToken =
73+
new InjectionToken<LocalRouterSignalState>(
74+
'initialLocalRouterSignalStateToken',
75+
{
76+
factory: createInitialLocalRouterSignalState,
77+
}
78+
);
10879

109-
selectRouterEvents<TAcceptedRouterEvents extends Type<RouterEvent>[]>(
110-
...acceptedEventTypes: [...TAcceptedRouterEvents]
111-
): Observable<InstanceType<TAcceptedRouterEvents[number]>> {
112-
return this.#router.events.pipe(
113-
filterRouterEvents(...acceptedEventTypes)
114-
) as Observable<InstanceType<TAcceptedRouterEvents[number]>>;
115-
}
80+
export const LocalRouterSignalStore: Type<RouterSignalStore> = signalStore(
81+
withState<LocalRouterSignalState>(() =>
82+
inject(initialLocalRouterSignalStateToken)
83+
),
84+
withComputed(({ _routerState }) => {
85+
const route = inject(ActivatedRoute);
11686

117-
#createRouterStateSnapshot(route: ActivatedRoute): RouterStateSnapshot {
11887
return {
119-
root: route.snapshot,
120-
url: this.#router.serializeUrl(
121-
createUrlTreeFromSnapshot(
122-
route.snapshot,
123-
[],
124-
route.snapshot.queryParams,
125-
route.snapshot.fragment
126-
)
88+
currentRoute: computed(
89+
(): MinimalActivatedRouteSnapshot => _routerState().root
12790
),
91+
fragment: toSignal(route.fragment, {
92+
requireSync: true,
93+
}) satisfies Signal<string | null>,
94+
queryParams: toSignal(route.queryParams, {
95+
requireSync: true,
96+
}) satisfies Signal<InternalStrictQueryParams>,
97+
routeData: toSignal(route.data, {
98+
requireSync: true,
99+
}) satisfies Signal<InternalStrictRouteData>,
100+
routeParams: toSignal(route.params, {
101+
requireSync: true,
102+
}) satisfies Signal<InternalStrictRouteParams>,
103+
title: toSignal(route.title, { requireSync: true }) satisfies Signal<
104+
string | undefined
105+
>,
106+
url: computed((): string => _routerState().url),
128107
};
129-
}
130-
131-
#serializeRouterState(route: ActivatedRoute): MinimalRouterStateSnapshot {
132-
return this.#serializer.serialize(this.#createRouterStateSnapshot(route));
133-
}
134-
}
108+
}),
109+
withMethods(
110+
(
111+
_store,
112+
router = inject(Router),
113+
serializer = inject(MinimalRouterStateSerializer)
114+
) => ({
115+
_serializeRouterState(route: ActivatedRoute): MinimalRouterStateSnapshot {
116+
return serializeRouterState(route, router, serializer);
117+
},
118+
})
119+
),
120+
withMethods((store, router = inject(Router)) => ({
121+
selectQueryParam(
122+
param: string
123+
): Signal<string | readonly string[] | undefined> {
124+
return computed(() => store.queryParams()[param]);
125+
},
126+
selectRouteDataParam(key: string): Signal<unknown> {
127+
return computed(() => store.routeData()[key]);
128+
},
129+
selectRouteParam(param: string): Signal<string | undefined> {
130+
return computed(() => store.routeParams()[param]);
131+
},
132+
selectRouterEvents<TAcceptedRouterEvents extends Type<RouterEvent>[]>(
133+
...acceptedEventTypes: [...TAcceptedRouterEvents]
134+
): Observable<InstanceType<TAcceptedRouterEvents[number]>> {
135+
return router.events.pipe(
136+
filterRouterEvents(...acceptedEventTypes)
137+
) as Observable<InstanceType<TAcceptedRouterEvents[number]>>;
138+
},
139+
})),
140+
withHooks({
141+
onInit(store, route = inject(ActivatedRoute)): void {
142+
store
143+
.selectRouterEvents(
144+
NavigationStart,
145+
RoutesRecognized,
146+
NavigationEnd,
147+
NavigationCancel,
148+
NavigationError
149+
)
150+
.pipe(
151+
map(() => route),
152+
map((route) => store._serializeRouterState(route)),
153+
takeUntilDestroyed()
154+
)
155+
.subscribe((routerState) =>
156+
patchState(store, { _routerState: routerState })
157+
);
158+
},
159+
})
160+
);

0 commit comments

Comments
 (0)