@real-router/angular 0.11.1 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/package.json +5 -6
  2. package/src/components/NavigationAnnouncer.ts +0 -18
  3. package/src/components/RouteView.ts +0 -141
  4. package/src/components/RouterErrorBoundary.ts +0 -72
  5. package/src/directives/RealLink.ts +0 -144
  6. package/src/directives/RealLinkActive.ts +0 -77
  7. package/src/directives/RouteMatch.ts +0 -7
  8. package/src/directives/RouteNotFound.ts +0 -6
  9. package/src/directives/RouteSelf.ts +0 -6
  10. package/src/dom-utils/direction-tracker.ts +0 -70
  11. package/src/dom-utils/index.ts +0 -31
  12. package/src/dom-utils/link-utils.ts +0 -339
  13. package/src/dom-utils/route-announcer.ts +0 -215
  14. package/src/dom-utils/scroll-restore.ts +0 -511
  15. package/src/dom-utils/scroll-spy.ts +0 -688
  16. package/src/dom-utils/view-transitions.ts +0 -142
  17. package/src/functions/index.ts +0 -29
  18. package/src/functions/injectIsActiveRoute.ts +0 -31
  19. package/src/functions/injectNavigator.ts +0 -12
  20. package/src/functions/injectOrThrow.ts +0 -19
  21. package/src/functions/injectRoute.ts +0 -39
  22. package/src/functions/injectRouteEnter.ts +0 -117
  23. package/src/functions/injectRouteExit.ts +0 -118
  24. package/src/functions/injectRouteNode.ts +0 -19
  25. package/src/functions/injectRouteUtils.ts +0 -15
  26. package/src/functions/injectRouter.ts +0 -12
  27. package/src/functions/injectRouterTransition.ts +0 -17
  28. package/src/index.ts +0 -63
  29. package/src/internal/buildActiveRouteOptions.ts +0 -20
  30. package/src/internal/install.ts +0 -90
  31. package/src/internal/subscribeSourceToSignal.ts +0 -48
  32. package/src/providers.ts +0 -80
  33. package/src/providersFactory.ts +0 -316
  34. package/src/sourceToSignal.ts +0 -28
  35. package/src/types.ts +0 -13
@@ -1,90 +0,0 @@
1
- import { ApplicationRef, DestroyRef, inject } from "@angular/core";
2
-
3
- import {
4
- createScrollRestoration,
5
- createScrollSpy,
6
- createViewTransitions,
7
- } from "../dom-utils";
8
- import { ROUTER } from "../providers";
9
-
10
- import type { ScrollRestorationOptions, ScrollSpyOptions } from "../dom-utils";
11
-
12
- /**
13
- * Shared installation helpers for `provideRealRouter` and
14
- * `provideRealRouterFactory`. Must be called inside the body of a
15
- * `provideEnvironmentInitializer(() => { ... })` callback so the active
16
- * injection context resolves `ROUTER`, `ApplicationRef`, and `DestroyRef`.
17
- *
18
- * Closes review-2026-05-10 §8.1 MED — eliminates duplicate wiring between
19
- * `providers.ts` and `providersFactory.ts` (high drift risk noted in the
20
- * audit: the comment blocks were identical down to the punctuation).
21
- */
22
-
23
- export function installScrollRestoration(
24
- options: ScrollRestorationOptions,
25
- ): void {
26
- const router = inject(ROUTER);
27
- const sr = createScrollRestoration(router, options);
28
-
29
- inject(DestroyRef).onDestroy(() => {
30
- sr.destroy();
31
- });
32
- }
33
-
34
- export function installScrollSpy(options: ScrollSpyOptions): void {
35
- const router = inject(ROUTER);
36
- const spy = createScrollSpy(router, options);
37
-
38
- inject(DestroyRef).onDestroy(() => {
39
- spy.destroy();
40
- });
41
- }
42
-
43
- export function installViewTransitions(): void {
44
- const router = inject(ROUTER);
45
-
46
- // Feature-detect `document.startViewTransition` once at install time. The
47
- // `appRef.tick()` listener exists ONLY to feed Angular's zoneless CD into
48
- // the VT utility's `setTimeout(0)`-driven snapshot capture (see comment
49
- // below). When `startViewTransition` is unavailable (Firefox as of 2026-04,
50
- // SSR, older browsers), `createViewTransitions` short-circuits to its
51
- // frozen NOOP_INSTANCE — no leave subscriber registered, no
52
- // `setTimeout(0)` invariant to satisfy. Installing the per-navigation
53
- // tick listener anyway would force a synchronous CD pass on every
54
- // navigation with zero benefit, doubling CD work in zoneless apps.
55
- // Closes review-2026-05-10 §8.2 MED (view-transitions hot path).
56
- const vtAvailable =
57
- typeof document !== "undefined" &&
58
- typeof document.startViewTransition === "function";
59
-
60
- let offTick: (() => void) | undefined;
61
-
62
- if (vtAvailable) {
63
- // Force synchronous change detection on every transition success BEFORE
64
- // the VT utility resolves its deferred. The utility uses `setTimeout(0)`
65
- // to release the new-snapshot capture, which is load-bearing because
66
- // Chromium blocks rAF callbacks while VT sits in the
67
- // `update-callback-called` phase. Angular's zoneless CD is rAF-driven by
68
- // default — without this synchronous tick the new DOM is not committed
69
- // when the browser captures the new snapshot, so old and new snapshots
70
- // end up identical and animations finish in ~0 ms with no visible work
71
- // (the inner-route `products.list ↔ products.detail` morph in the
72
- // example app was the canary).
73
- //
74
- // Subscribers fire in registration order; this one runs BEFORE
75
- // `createViewTransitions` registers its own subscriber, guaranteeing CD
76
- // completes first.
77
- const appRef = inject(ApplicationRef);
78
-
79
- offTick = router.subscribe(() => {
80
- appRef.tick();
81
- });
82
- }
83
-
84
- const vt = createViewTransitions(router);
85
-
86
- inject(DestroyRef).onDestroy(() => {
87
- offTick?.();
88
- vt.destroy();
89
- });
90
- }
@@ -1,48 +0,0 @@
1
- import type { RouterSource } from "@real-router/sources";
2
-
3
- /**
4
- * Subscribe a `RouterSource<T>` to a write-callback and return a cleanup
5
- * function. The shape is the per-effect-run pattern that `RealLink`,
6
- * `RealLinkActive`, and `RouteView` all share inside their constructor
7
- * `effect(...)` (review-2026-05-16 §8a MEDIUM — identical 8-line block
8
- * repeated in 3 directives):
9
- *
10
- * 1. Read initial snapshot and apply it via `onSnapshot(snap)`.
11
- * 2. Subscribe — every subsequent emission calls `onSnapshot(snap)` again.
12
- * 3. Return a cleanup that unsubscribes and destroys the source. For
13
- * cached factories from `@real-router/sources` (`createActiveRouteSource`,
14
- * `createRouteNodeSource`, `getTransitionSource`, `getErrorSource`,
15
- * `createDismissableError`) `destroy()` is a no-op on the shared
16
- * wrapper, so this helper is safe to invoke from rapid effect re-runs
17
- * under signal-input changes.
18
- *
19
- * Callers pass the result to `onCleanup(...)` from Angular's `effect()`.
20
- *
21
- * @example
22
- * ```ts
23
- * effect((onCleanup) => {
24
- * const source = createActiveRouteSource(router, routeName(), params());
25
- * onCleanup(
26
- * subscribeSourceToSignal(source, (snap) => {
27
- * this.isActive.set(snap);
28
- * this.updateDom();
29
- * }),
30
- * );
31
- * });
32
- * ```
33
- */
34
- export function subscribeSourceToSignal<T>(
35
- source: RouterSource<T>,
36
- onSnapshot: (snapshot: T) => void,
37
- ): () => void {
38
- onSnapshot(source.getSnapshot());
39
-
40
- const unsub = source.subscribe(() => {
41
- onSnapshot(source.getSnapshot());
42
- });
43
-
44
- return () => {
45
- unsub();
46
- source.destroy();
47
- };
48
- }
package/src/providers.ts DELETED
@@ -1,80 +0,0 @@
1
- import {
2
- InjectionToken,
3
- makeEnvironmentProviders,
4
- provideEnvironmentInitializer,
5
- type EnvironmentProviders,
6
- } from "@angular/core";
7
- import { getNavigator, type Router, type Navigator } from "@real-router/core";
8
- import { createRouteSource } from "@real-router/sources";
9
-
10
- import {
11
- installScrollRestoration,
12
- installScrollSpy,
13
- installViewTransitions,
14
- } from "./internal/install";
15
- import { sourceToSignal } from "./sourceToSignal";
16
-
17
- import type { ScrollRestorationOptions, ScrollSpyOptions } from "./dom-utils";
18
- import type { RouteSignals } from "./types";
19
-
20
- export const ROUTER = new InjectionToken<Router>("ROUTER");
21
-
22
- export const NAVIGATOR = new InjectionToken<Navigator>("NAVIGATOR");
23
-
24
- export const ROUTE = new InjectionToken<RouteSignals>("ROUTE");
25
-
26
- export interface RealRouterOptions {
27
- scrollRestoration?: ScrollRestorationOptions;
28
- scrollSpy?: ScrollSpyOptions;
29
- viewTransitions?: boolean;
30
- }
31
-
32
- export function provideRealRouter(
33
- router: Router,
34
- options?: RealRouterOptions,
35
- ): EnvironmentProviders {
36
- const navigator = getNavigator(router);
37
-
38
- // `Parameters<typeof makeEnvironmentProviders>[0]` is the actual union
39
- // `(Provider | EnvironmentProviders | EnvironmentProviders[])[]` —
40
- // `provideEnvironmentInitializer()` returns `EnvironmentProviders`, so the
41
- // narrower `Provider[]` would force a cast at every push (review §8a — the
42
- // proposed Provider[] swap was retracted after discovering this).
43
- const providers: Parameters<typeof makeEnvironmentProviders>[0] = [
44
- { provide: ROUTER, useValue: router },
45
- { provide: NAVIGATOR, useValue: navigator },
46
- {
47
- provide: ROUTE,
48
- useFactory: (): RouteSignals => ({
49
- routeState: sourceToSignal(createRouteSource(router)),
50
- navigator,
51
- }),
52
- },
53
- ];
54
-
55
- if (options?.scrollRestoration) {
56
- const scrollOpts = options.scrollRestoration;
57
-
58
- providers.push(
59
- provideEnvironmentInitializer(() => {
60
- installScrollRestoration(scrollOpts);
61
- }),
62
- );
63
- }
64
-
65
- if (options?.scrollSpy && options.scrollSpy.selector !== "") {
66
- const spyOpts = options.scrollSpy;
67
-
68
- providers.push(
69
- provideEnvironmentInitializer(() => {
70
- installScrollSpy(spyOpts);
71
- }),
72
- );
73
- }
74
-
75
- if (options?.viewTransitions === true) {
76
- providers.push(provideEnvironmentInitializer(installViewTransitions));
77
- }
78
-
79
- return makeEnvironmentProviders(providers);
80
- }
@@ -1,316 +0,0 @@
1
- import {
2
- DestroyRef,
3
- REQUEST,
4
- TransferState,
5
- inject,
6
- makeEnvironmentProviders,
7
- makeStateKey,
8
- provideAppInitializer,
9
- provideEnvironmentInitializer,
10
- type EnvironmentProviders,
11
- } from "@angular/core";
12
- import {
13
- getNavigator,
14
- type DefaultDependencies,
15
- type PluginFactory,
16
- type Router,
17
- } from "@real-router/core";
18
- import { cloneRouter } from "@real-router/core/api";
19
- import { hydrateRouter, serializeRouterState } from "@real-router/core/utils";
20
- import { createRouteSource } from "@real-router/sources";
21
-
22
- import {
23
- installScrollRestoration,
24
- installScrollSpy,
25
- installViewTransitions,
26
- } from "./internal/install";
27
- import { NAVIGATOR, ROUTE, ROUTER } from "./providers";
28
- import { sourceToSignal } from "./sourceToSignal";
29
-
30
- import type { ScrollRestorationOptions, ScrollSpyOptions } from "./dom-utils";
31
- import type { RouteSignals } from "./types";
32
-
33
- /**
34
- * `TransferState` key carrying the SSR-resolved router state from server to
35
- * client as an XSS-safe JSON string (produced by `serializeRouterState`).
36
- * Populated server-side by the `provideAppInitializer` callback after
37
- * `router.start()` resolves; consumed client-side after hydration. Mirrors the
38
- * `<script>window.__SSR_STATE__ = …</script>` pattern used by every other
39
- * adapter — Angular's idiomatic transport is `TransferState` (#599).
40
- *
41
- * Stored as `string`: `serializeRouterState(state)` already produces JSON;
42
- * `hydrateRouter(router, json)` accepts a JSON string and parses it once
43
- * internally. Storing the parsed object would force a double round-trip
44
- * (TransferState wraps every value in JSON for transport).
45
- *
46
- * Internal implementation detail. Not re-exported.
47
- */
48
- const ROUTER_STATE_KEY = makeStateKey<string>("@real-router/angular:ssrState");
49
-
50
- /**
51
- * Factory function for deriving per-request dependencies from an SSR `Request`.
52
- *
53
- * - **Server:** receives the real `Request` exposed via Angular's `REQUEST` token.
54
- * - **SSG:** receives a mocked `Request` injected via `platformProviders`.
55
- * - **Client:** receives `null` — derive deps from `document.cookie` etc.
56
- *
57
- * The returned object becomes the second argument to
58
- * `cloneRouter(baseRouter, deps)`. Returning `undefined` clones the router with
59
- * no extra deps (cloneRouter accepts the optional 2nd argument).
60
- */
61
- export type RequestDepsFactory<
62
- TDeps extends DefaultDependencies = DefaultDependencies,
63
- > = (request: Request | null) => TDeps | undefined;
64
-
65
- /**
66
- * Function form for conditional plugins (different sets server vs client).
67
- *
68
- * Use this when the plugin set must differ — typically because some plugins
69
- * (e.g. `browser-plugin`, `navigation-plugin`, `hash-plugin`) touch
70
- * `window.history` / `window.location` and cannot run on the server.
71
- */
72
- export type RequestPluginsFactory<
73
- TDeps extends DefaultDependencies = DefaultDependencies,
74
- > = (request: Request | null) => readonly PluginFactory<TDeps>[];
75
-
76
- export interface RealRouterFactoryOptions<
77
- TDeps extends DefaultDependencies = DefaultDependencies,
78
- > {
79
- /**
80
- * Base router instance — created once at app bootstrap (typically inside
81
- * `app.config.ts` module scope). Each request clones this router via
82
- * `cloneRouter(baseRouter, deps?.(request))`, producing an isolated
83
- * router with its own state, plugins, and subscriptions.
84
- *
85
- * **Important:** the `baseRouter` MUST NOT be started ahead of time —
86
- * `provideAppInitializer` is responsible for calling `router.start(url)`
87
- * inside the per-request DI scope.
88
- */
89
- baseRouter: Router<TDeps>;
90
-
91
- /**
92
- * Plugins applied to every per-request router clone.
93
- *
94
- * **Static form** — same plugins on both sides:
95
- * ```ts
96
- * plugins: [ssrDataPluginFactory(loaders)]
97
- * ```
98
- *
99
- * **Function form** — conditional client vs server (recommended when any
100
- * browser-only plugin is involved):
101
- * ```ts
102
- * plugins: (request) => request
103
- * ? [ssrDataPluginFactory(loaders)]
104
- * : [browserPluginFactory(), ssrDataPluginFactory(loaders)],
105
- * ```
106
- *
107
- * Function form is required if the plugin list contains
108
- * `browser-plugin`, `navigation-plugin`, or `hash-plugin` — those plugins
109
- * read `window.history` / `window.location` and crash on the server.
110
- */
111
- plugins?: readonly PluginFactory<TDeps>[] | RequestPluginsFactory<TDeps>;
112
-
113
- /**
114
- * Derive request-scoped deps (e.g. `currentUser` from cookies). The result
115
- * is passed to `cloneRouter(baseRouter, deps)` and merged with any deps
116
- * already registered on the base router.
117
- *
118
- * Receives `request: Request | null`:
119
- * - non-null on server (real `Request` from `@angular/ssr` runtime)
120
- * - non-null on SSG (mocked `Request` via `platformProviders`)
121
- * - null on client (derive deps externally — e.g. parse `document.cookie`)
122
- */
123
- deps?: RequestDepsFactory<TDeps>;
124
-
125
- /** Optional scroll restoration — same semantics as `provideRealRouter`. */
126
- scrollRestoration?: ScrollRestorationOptions;
127
-
128
- /** Optional scroll spy — same semantics as `provideRealRouter`. */
129
- scrollSpy?: ScrollSpyOptions;
130
-
131
- /** Optional view transitions — same semantics as `provideRealRouter`. */
132
- viewTransitions?: boolean;
133
- }
134
-
135
- /**
136
- * `provideRealRouterFactory` — environment providers for SSR / SSG scenarios.
137
- *
138
- * Unlike `provideRealRouter(router)` (single instance via `useValue`), this
139
- * factory uses `useFactory` to produce a per-request router clone:
140
- *
141
- * 1. Reads Angular's `REQUEST` token (`{ optional: true }`).
142
- * 2. Calls `cloneRouter(baseRouter, deps?.(request))` to create a request-scoped clone.
143
- * 3. Applies plugins (`plugins` array or `plugins(request)` factory).
144
- * 4. Registers `provideAppInitializer` that calls `await router.start(url)`.
145
- * 5. Schedules `router.dispose()` via `DestroyRef.onDestroy` — the request
146
- * Injector is destroyed after the response is sent, releasing all
147
- * subscriptions and plugins.
148
- *
149
- * Use cases:
150
- * - Angular SSR with `@angular/ssr` (`outputMode: "server"`).
151
- * - SSG build-time render via `renderApplication` + `platformProviders` `REQUEST` mock.
152
- * - Multi-tenant request-scoped routing.
153
- *
154
- * Existing single-instance scenarios (SPA, SSG client after hydration) continue
155
- * to use `provideRealRouter(router)` — both APIs ship in parallel.
156
- *
157
- * @param options - Factory configuration — see `RealRouterFactoryOptions`.
158
- * @returns `EnvironmentProviders` to spread into `ApplicationConfig.providers`.
159
- */
160
- export function provideRealRouterFactory<
161
- TDeps extends DefaultDependencies = DefaultDependencies,
162
- >(options: RealRouterFactoryOptions<TDeps>): EnvironmentProviders {
163
- const {
164
- baseRouter,
165
- plugins,
166
- deps,
167
- scrollRestoration,
168
- scrollSpy,
169
- viewTransitions,
170
- } = options;
171
-
172
- const providers: Parameters<typeof makeEnvironmentProviders>[0] = [
173
- {
174
- provide: ROUTER,
175
- useFactory: (): Router => {
176
- const request = inject(REQUEST, { optional: true });
177
- const requestDeps = deps?.(request);
178
- const router = cloneRouter(baseRouter, requestDeps);
179
-
180
- const pluginList =
181
- typeof plugins === "function" ? plugins(request) : plugins;
182
-
183
- if (pluginList && pluginList.length > 0) {
184
- // Variadic — `usePlugin` accepts `(PluginFactory<D> | false | null | undefined)[]`.
185
- router.usePlugin(...pluginList);
186
- }
187
-
188
- // Per-request cleanup. The application Injector is destroyed:
189
- // - On server: after `writeResponseToNodeResponse` finishes the response
190
- // (request scope ends).
191
- // - On client: at `ApplicationRef.destroy` (rare in SPA, common in TestBed).
192
- // - In SSG build: after each `renderApplication` resolves.
193
- inject(DestroyRef).onDestroy(() => {
194
- router.dispose();
195
- });
196
-
197
- return router as unknown as Router;
198
- },
199
- },
200
- {
201
- provide: NAVIGATOR,
202
- useFactory: () => getNavigator(inject(ROUTER)),
203
- },
204
- {
205
- provide: ROUTE,
206
- useFactory: (): RouteSignals => {
207
- const router = inject(ROUTER);
208
-
209
- return {
210
- routeState: sourceToSignal(createRouteSource(router)),
211
- navigator: inject(NAVIGATOR),
212
- };
213
- },
214
- },
215
- // Async bootstrap — runs before the first component renders. Three
216
- // branches based on TransferState population:
217
- //
218
- // 1. **Client after hydration** — server populated TransferState with
219
- // the SSR-resolved router state. Consume it via `hydrateRouter`,
220
- // which deposits the parsed state into the one-shot
221
- // `RouterInternals.hydrationState` scratchpad before invoking
222
- // `router.start(state.path)`. SSR loader plugins
223
- // (`@real-router/ssr-data-plugin`, `@real-router/rsc-server-plugin`)
224
- // read the scratchpad and skip the loader on first paint — parity
225
- // with the other 5 adapters that consume `<script>__SSR_STATE__</script>` (#596, #599).
226
- //
227
- // 2. **Server / SSG** — TransferState empty; run the regular
228
- // `router.start(path)`. After it resolves, write the serialized
229
- // state back into TransferState so the matching client run lands
230
- // in branch 1. Angular's `TransferState` infrastructure
231
- // (provided by `provideClientHydration()`) carries this blob to
232
- // the client as a `<script id="ng-state">` payload.
233
- //
234
- // 3. **Pure CSR** — TransferState empty (never populated by a server
235
- // pass), and `inject(REQUEST, { optional: true })` returns null.
236
- // Falls into the same `router.start(path)` branch as server-side
237
- // but skips the TransferState write (no client to hand off to).
238
- //
239
- // Errors propagate (Option A from RFC §10): the bootstrap fails and the
240
- // server returns 500. Custom error pages should be wired via
241
- // `RouterErrorBoundary` on subsequent renders.
242
- provideAppInitializer(async () => {
243
- const router = inject(ROUTER);
244
- const request = inject(REQUEST, { optional: true });
245
- const transferState = inject(TransferState);
246
-
247
- const ssrJson = transferState.get(ROUTER_STATE_KEY, null);
248
-
249
- if (ssrJson !== null) {
250
- // Branch 1: client after hydration — reuse server-resolved state.
251
- await hydrateRouter(router, ssrJson);
252
- // One-shot semantic, parity with `delete window.__SSR_STATE__`.
253
- transferState.remove(ROUTER_STATE_KEY);
254
-
255
- return;
256
- }
257
-
258
- // Branches 2 & 3: regular start.
259
- // Browser-plugin's `start` interceptor (when registered) wraps this call
260
- // with location-derived path. We always pass an explicit string — the
261
- // interceptor uses the explicit value because `next(path ?? location)`
262
- // short-circuits when `path` is non-nullish.
263
- const path = deriveStartPath(request);
264
- const state = await router.start(path);
265
-
266
- if (request !== null) {
267
- // Branch 2: running inside `@angular/ssr`'s request handler — write
268
- // serialized state to TransferState so the matching client run can
269
- // skip the loader on first paint.
270
- transferState.set(ROUTER_STATE_KEY, serializeRouterState(state));
271
- }
272
- }),
273
- ];
274
-
275
- if (scrollRestoration) {
276
- providers.push(
277
- provideEnvironmentInitializer(() => {
278
- installScrollRestoration(scrollRestoration);
279
- }),
280
- );
281
- }
282
-
283
- if (scrollSpy && scrollSpy.selector !== "") {
284
- providers.push(
285
- provideEnvironmentInitializer(() => {
286
- installScrollSpy(scrollSpy);
287
- }),
288
- );
289
- }
290
-
291
- if (viewTransitions === true) {
292
- providers.push(provideEnvironmentInitializer(installViewTransitions));
293
- }
294
-
295
- return makeEnvironmentProviders(providers);
296
- }
297
-
298
- /**
299
- * Derive the path passed to `router.start(path)`:
300
- * - Server / SSG: `request.url` → pathname + search.
301
- * - Client: `window.location` if available.
302
- * - Fallback: `"/"` (only reachable in synthetic non-browser non-SSR setups).
303
- */
304
- function deriveStartPath(request: Request | null): string {
305
- if (request) {
306
- const url = new URL(request.url);
307
-
308
- return url.pathname + url.search;
309
- }
310
-
311
- if (typeof globalThis.window !== "undefined") {
312
- return globalThis.location.pathname + globalThis.location.search;
313
- }
314
-
315
- return "/";
316
- }
@@ -1,28 +0,0 @@
1
- import { signal, type Signal, inject, DestroyRef } from "@angular/core";
2
-
3
- import type { RouterSource } from "@real-router/sources";
4
-
5
- /** Must be called within an injection context (constructor, field initializer, runInInjectionContext). */
6
- export function sourceToSignal<T>(source: RouterSource<T>): Signal<T> {
7
- const sig = signal<T>(source.getSnapshot());
8
- const destroyRef = inject(DestroyRef);
9
-
10
- const unsubscribe = source.subscribe(() => {
11
- sig.set(source.getSnapshot());
12
- });
13
-
14
- destroyRef.onDestroy(() => {
15
- // `try/finally` guarantees `source.destroy()` runs even if `unsubscribe`
16
- // throws. Cached sources from `@real-router/sources` keep `destroy()` as
17
- // a no-op (so they survive multi-consumer teardown), but non-cached
18
- // sources rely on this call to release their router subscription —
19
- // skipping it on an unsubscribe throw would leak the listener.
20
- try {
21
- unsubscribe();
22
- } finally {
23
- source.destroy();
24
- }
25
- });
26
-
27
- return sig.asReadonly();
28
- }
package/src/types.ts DELETED
@@ -1,13 +0,0 @@
1
- import type { Signal } from "@angular/core";
2
- import type { Navigator, Params, RouterError } from "@real-router/core";
3
- import type { RouteSnapshot } from "@real-router/sources";
4
-
5
- export interface RouteSignals<P extends Params = Params> {
6
- readonly routeState: Signal<RouteSnapshot<P>>;
7
- readonly navigator: Navigator;
8
- }
9
-
10
- export interface ErrorContext {
11
- $implicit: RouterError;
12
- resetError: () => void;
13
- }