@benjavicente/angular-router-experimental 1.142.12 → 1.142.14

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.
@@ -22,6 +22,40 @@ export declare function injectRouteErrorHandler<TRouter extends AnyRouter = Regi
22
22
  export { }
23
23
 
24
24
 
25
+ declare module '@benjavicente/router-core' {
26
+ interface LazyRoute<in out TRoute extends AnyRoute> {
27
+ injectMatch: InjectMatchRoute<TRoute['id']>;
28
+ injectRouteContext: InjectRouteContextRoute<TRoute['id']>;
29
+ injectSearch: InjectSearchRoute<TRoute['id']>;
30
+ injectParams: InjectParamsRoute<TRoute['id']>;
31
+ injectLoaderDeps: InjectLoaderDepsRoute<TRoute['id']>;
32
+ injectLoaderData: InjectLoaderDataRoute<TRoute['id']>;
33
+ injectNavigate: () => UseNavigateResult<TRoute['fullPath']>;
34
+ }
35
+ }
36
+
37
+
38
+ declare module '@benjavicente/router-core' {
39
+ interface UpdatableRouteOptionsExtensions {
40
+ component?: RouteComponent;
41
+ errorComponent?: false | null | undefined | ErrorRouteComponent;
42
+ notFoundComponent?: NotFoundRouteComponent;
43
+ pendingComponent?: RouteComponent;
44
+ }
45
+ interface RootRouteOptionsExtensions {
46
+ }
47
+ interface RouteExtensions<in out TId extends string, in out TFullPath extends string> {
48
+ injectMatch: InjectMatchRoute<TId>;
49
+ injectRouteContext: InjectRouteContextRoute<TId>;
50
+ injectSearch: InjectSearchRoute<TId>;
51
+ injectParams: InjectParamsRoute<TId>;
52
+ injectLoaderDeps: InjectLoaderDepsRoute<TId>;
53
+ injectLoaderData: InjectLoaderDataRoute<TId>;
54
+ injectNavigate: () => UseNavigateResult<TFullPath>;
55
+ }
56
+ }
57
+
58
+
25
59
  declare module '@benjavicente/router-core' {
26
60
  interface RouterOptionsExtensions {
27
61
  /**
@@ -58,39 +92,9 @@ declare module '@benjavicente/router-core' {
58
92
  }
59
93
 
60
94
 
61
- declare module '@benjavicente/router-core' {
62
- interface UpdatableRouteOptionsExtensions {
63
- component?: RouteComponent;
64
- errorComponent?: false | null | undefined | ErrorRouteComponent;
65
- notFoundComponent?: NotFoundRouteComponent;
66
- pendingComponent?: RouteComponent;
67
- }
68
- interface RootRouteOptionsExtensions {
69
- shellComponent?: Angular.Type<{
70
- children: any;
71
- }>;
72
- }
73
- interface RouteExtensions<in out TId extends string, in out TFullPath extends string> {
74
- injectMatch: InjectMatchRoute<TId>;
75
- injectRouteContext: InjectRouteContextRoute<TId>;
76
- injectSearch: InjectSearchRoute<TId>;
77
- injectParams: InjectParamsRoute<TId>;
78
- injectLoaderDeps: InjectLoaderDepsRoute<TId>;
79
- injectLoaderData: InjectLoaderDataRoute<TId>;
80
- injectNavigate: () => UseNavigateResult<TFullPath>;
81
- }
82
- }
83
-
84
-
85
- declare module '@benjavicente/router-core' {
86
- interface LazyRoute<in out TRoute extends AnyRoute> {
87
- injectMatch: InjectMatchRoute<TRoute['id']>;
88
- injectRouteContext: InjectRouteContextRoute<TRoute['id']>;
89
- injectSearch: InjectSearchRoute<TRoute['id']>;
90
- injectParams: InjectParamsRoute<TRoute['id']>;
91
- injectLoaderDeps: InjectLoaderDepsRoute<TRoute['id']>;
92
- injectLoaderData: InjectLoaderDataRoute<TRoute['id']>;
93
- injectNavigate: () => UseNavigateResult<TRoute['fullPath']>;
95
+ declare global {
96
+ interface Window {
97
+ __TSR_ROUTER_INJECTION_KEY__?: Angular.InjectionToken<AnyRouter>;
94
98
  }
95
99
  }
96
100
 
@@ -105,10 +109,3 @@ declare module '@benjavicente/router-core' {
105
109
  pendingRouteIds: RouterReadableStore<Record<string, boolean>>;
106
110
  }
107
111
  }
108
-
109
-
110
- declare global {
111
- interface Window {
112
- __TSR_ROUTER_INJECTION_KEY__?: Angular.InjectionToken<AnyRouter>;
113
- }
114
- }
@@ -232,6 +232,8 @@ export declare function injectErrorState(): {
232
232
  };
233
233
  };
234
234
 
235
+ export declare function injectIsShell(): Signal<boolean>;
236
+
235
237
  export declare function injectLoaderData<TRouter extends AnyRouter = RegisteredRouter, const TFrom extends string | undefined = undefined, TStrict extends boolean = true, TSelected = unknown>(opts: InjectLoaderDataOptions<TRouter, TFrom, TStrict, TSelected>): Angular.Signal<UseLoaderDataResult<TRouter, TFrom, TStrict, TSelected>>;
236
238
 
237
239
  export declare interface InjectLoaderDataBaseOptions<TRouter extends AnyRouter, TFrom, TStrict extends boolean, TSelected> {
@@ -497,7 +499,7 @@ export { Register }
497
499
  export { RegisteredRouter }
498
500
 
499
501
  declare type RenderValue = {
500
- key?: string;
502
+ key?: unknown;
501
503
  component: Type<any> | null | undefined;
502
504
  inputs?: Record<string, () => unknown>;
503
505
  providers?: Array<Provider>;
@@ -573,7 +575,7 @@ export declare class RouteMatch {
573
575
  hasPendingMatch: Signal<boolean>;
574
576
  pendingRouteIds: Signal<Record<string, boolean>>;
575
577
  nearestMatchContext: NearestMatchContextValue;
576
- isCatchingError: Signal<boolean>;
578
+ catchingErrorMatch: Signal<AnyRouteMatch | undefined>;
577
579
  render: void;
578
580
  onRendered: void;
579
581
  }
@@ -655,6 +657,40 @@ export declare type UseBlockerOpts<TRouter extends AnyRouter = RegisteredRouter,
655
657
  export { }
656
658
 
657
659
 
660
+ declare module '@benjavicente/router-core' {
661
+ interface LazyRoute<in out TRoute extends AnyRoute> {
662
+ injectMatch: InjectMatchRoute<TRoute['id']>;
663
+ injectRouteContext: InjectRouteContextRoute<TRoute['id']>;
664
+ injectSearch: InjectSearchRoute<TRoute['id']>;
665
+ injectParams: InjectParamsRoute<TRoute['id']>;
666
+ injectLoaderDeps: InjectLoaderDepsRoute<TRoute['id']>;
667
+ injectLoaderData: InjectLoaderDataRoute<TRoute['id']>;
668
+ injectNavigate: () => UseNavigateResult<TRoute['fullPath']>;
669
+ }
670
+ }
671
+
672
+
673
+ declare module '@benjavicente/router-core' {
674
+ interface UpdatableRouteOptionsExtensions {
675
+ component?: RouteComponent;
676
+ errorComponent?: false | null | undefined | ErrorRouteComponent;
677
+ notFoundComponent?: NotFoundRouteComponent;
678
+ pendingComponent?: RouteComponent;
679
+ }
680
+ interface RootRouteOptionsExtensions {
681
+ }
682
+ interface RouteExtensions<in out TId extends string, in out TFullPath extends string> {
683
+ injectMatch: InjectMatchRoute<TId>;
684
+ injectRouteContext: InjectRouteContextRoute<TId>;
685
+ injectSearch: InjectSearchRoute<TId>;
686
+ injectParams: InjectParamsRoute<TId>;
687
+ injectLoaderDeps: InjectLoaderDepsRoute<TId>;
688
+ injectLoaderData: InjectLoaderDataRoute<TId>;
689
+ injectNavigate: () => UseNavigateResult<TFullPath>;
690
+ }
691
+ }
692
+
693
+
658
694
  declare module '@benjavicente/router-core' {
659
695
  interface RouterOptionsExtensions {
660
696
  /**
@@ -691,39 +727,9 @@ declare module '@benjavicente/router-core' {
691
727
  }
692
728
 
693
729
 
694
- declare module '@benjavicente/router-core' {
695
- interface UpdatableRouteOptionsExtensions {
696
- component?: RouteComponent;
697
- errorComponent?: false | null | undefined | ErrorRouteComponent;
698
- notFoundComponent?: NotFoundRouteComponent;
699
- pendingComponent?: RouteComponent;
700
- }
701
- interface RootRouteOptionsExtensions {
702
- shellComponent?: Angular.Type<{
703
- children: any;
704
- }>;
705
- }
706
- interface RouteExtensions<in out TId extends string, in out TFullPath extends string> {
707
- injectMatch: InjectMatchRoute<TId>;
708
- injectRouteContext: InjectRouteContextRoute<TId>;
709
- injectSearch: InjectSearchRoute<TId>;
710
- injectParams: InjectParamsRoute<TId>;
711
- injectLoaderDeps: InjectLoaderDepsRoute<TId>;
712
- injectLoaderData: InjectLoaderDataRoute<TId>;
713
- injectNavigate: () => UseNavigateResult<TFullPath>;
714
- }
715
- }
716
-
717
-
718
- declare module '@benjavicente/router-core' {
719
- interface LazyRoute<in out TRoute extends AnyRoute> {
720
- injectMatch: InjectMatchRoute<TRoute['id']>;
721
- injectRouteContext: InjectRouteContextRoute<TRoute['id']>;
722
- injectSearch: InjectSearchRoute<TRoute['id']>;
723
- injectParams: InjectParamsRoute<TRoute['id']>;
724
- injectLoaderDeps: InjectLoaderDepsRoute<TRoute['id']>;
725
- injectLoaderData: InjectLoaderDataRoute<TRoute['id']>;
726
- injectNavigate: () => UseNavigateResult<TRoute['fullPath']>;
730
+ declare global {
731
+ interface Window {
732
+ __TSR_ROUTER_INJECTION_KEY__?: Angular.InjectionToken<AnyRouter>;
727
733
  }
728
734
  }
729
735
 
@@ -738,10 +744,3 @@ declare module '@benjavicente/router-core' {
738
744
  pendingRouteIds: RouterReadableStore<Record<string, boolean>>;
739
745
  }
740
746
  }
741
-
742
-
743
- declare global {
744
- interface Window {
745
- __TSR_ROUTER_INJECTION_KEY__?: Angular.InjectionToken<AnyRouter>;
746
- }
747
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@benjavicente/angular-router-experimental",
3
- "version": "1.142.12",
3
+ "version": "1.142.14",
4
4
  "description": "Modern and scalable routing for Angular applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -50,11 +50,11 @@
50
50
  "node": ">=20.19"
51
51
  },
52
52
  "dependencies": {
53
- "@benjavicente/history": "^1.161.6",
54
- "@benjavicente/router-core": "^1.169.2",
55
53
  "@tanstack/store": "^0.9.3",
56
54
  "isbot": "^5.1.22",
57
- "tslib": "^2.3.0"
55
+ "tslib": "^2.3.0",
56
+ "@benjavicente/router-core": "1.169.2",
57
+ "@benjavicente/history": "1.161.6"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@analogjs/vitest-angular": "^2.2.1",
package/src/Match.ts CHANGED
@@ -21,7 +21,7 @@ import { DefaultNotFoundComponent } from './DefaultNotFound'
21
21
  import { MATCH_CONTEXT_INJECTOR_TOKEN } from './matchInjectorToken'
22
22
  import { injectRender } from './renderer/injectRender'
23
23
  import { ERROR_STATE_INJECTOR_TOKEN } from './injectErrorState'
24
- import { injectIsCatchingError } from './renderer/injectIsCatchingError'
24
+ import { injectCatchingErrorMatch } from './renderer/injectIsCatchingError'
25
25
  import type { Signal } from '@angular/core'
26
26
  import type { NearestMatchContextValue } from './matchInjectorToken'
27
27
  import type { AnyRouteMatch } from '@benjavicente/router-core'
@@ -31,6 +31,26 @@ const dummyMatchStore = {
31
31
  subscribe: () => ({ unsubscribe: () => {} }),
32
32
  }
33
33
 
34
+ @Component({
35
+ selector: 'tanstack-router-default-error',
36
+ template: `
37
+ <div role="alert">
38
+ <p>Something went wrong.</p>
39
+ <pre>{{ message() }}</pre>
40
+ </div>
41
+ `,
42
+ standalone: true,
43
+ })
44
+ export class DefaultErrorComponent {
45
+ state = inject(ERROR_STATE_INJECTOR_TOKEN)
46
+
47
+ message = computed(() => {
48
+ const error = this.state.error
49
+ if (error instanceof Error) return error.stack ?? error.message
50
+ return String(error)
51
+ })
52
+ }
53
+
34
54
  function injectOnRendered({
35
55
  parentRouteIsRoot,
36
56
  }: {
@@ -167,7 +187,7 @@ export class RouteMatch {
167
187
  hasPending: this.hasPendingMatch,
168
188
  }
169
189
 
170
- isCatchingError = injectIsCatchingError({
190
+ catchingErrorMatch = injectCatchingErrorMatch({
171
191
  matchId: this.matchId,
172
192
  })
173
193
 
@@ -175,11 +195,15 @@ export class RouteMatch {
175
195
  const matchData = this.matchData()
176
196
  if (!matchData) return null
177
197
 
198
+ const { match, route } = matchData
199
+
178
200
  if (this.shouldClientOnly() && this.router.isServer) {
179
- return null
180
- }
201
+ const PendingComponent =
202
+ getComponent(route.options.pendingComponent) ??
203
+ getComponent(this.router.options.defaultPendingComponent)
181
204
 
182
- const { match, route } = matchData
205
+ return PendingComponent ? { component: PendingComponent } : null
206
+ }
183
207
 
184
208
  if (match.status === 'notFound') {
185
209
  const NotFoundComponent = getNotFoundComponent(this.router, route)
@@ -187,18 +211,18 @@ export class RouteMatch {
187
211
  return {
188
212
  component: NotFoundComponent,
189
213
  }
190
- } else if (match.status === 'error' || this.isCatchingError()) {
191
- const RouteErrorComponent =
192
- getComponent(route.options.errorComponent) ??
193
- getComponent(this.router.options.defaultErrorComponent)
214
+ } else if (match.status === 'error' || this.catchingErrorMatch()) {
215
+ const caughtMatch = this.catchingErrorMatch()
216
+ const RouteErrorComponent = getErrorComponent(this.router, route)
194
217
 
195
218
  return {
196
- component: RouteErrorComponent || null,
219
+ component: RouteErrorComponent,
197
220
  providers: [
198
221
  {
199
222
  provide: ERROR_STATE_INJECTOR_TOKEN,
200
223
  useValue: {
201
- error: match.error,
224
+ error:
225
+ match.status === 'error' ? match.error : caughtMatch?.error,
202
226
  reset: () => {
203
227
  this.router.invalidate()
204
228
  },
@@ -351,3 +375,21 @@ function getNotFoundComponent(router: AnyRouter, route: AnyRoute) {
351
375
 
352
376
  return DefaultNotFoundComponent
353
377
  }
378
+
379
+ function getErrorComponent(router: AnyRouter, route: AnyRoute) {
380
+ const RouteErrorComponent =
381
+ getComponent(route.options.errorComponent) ??
382
+ getComponent(router.options.defaultErrorComponent)
383
+
384
+ if (RouteErrorComponent) {
385
+ return RouteErrorComponent
386
+ }
387
+
388
+ if (isDevMode() && !route.options.errorComponent) {
389
+ console.warn(
390
+ `An error was encountered on the route with ID "${route.id}", but an errorComponent option was not configured, nor was a router level defaultErrorComponent configured. Consider configuring at least one of these to avoid TanStack Router's generic defaultErrorComponent.`,
391
+ )
392
+ }
393
+
394
+ return DefaultErrorComponent
395
+ }
@@ -1,4 +1,3 @@
1
- import { DOCUMENT } from '@angular/common'
2
1
  import * as Angular from '@angular/core'
3
2
  import { injectStore } from '../store/injectStore'
4
3
  import { buildMatchManagedDocumentContent } from './build-match-managed-document'
@@ -42,7 +41,7 @@ function applyManagedDocumentContent({
42
41
  * Used by `provideTanstackDocument` when all features are enabled.
43
42
  */
44
43
  export function installUnifiedTanstackDocumentSync(injectedRouter: AnyRouter) {
45
- const document = Angular.inject(DOCUMENT)
44
+ const document = Angular.inject(Angular.DOCUMENT)
46
45
  const rendererFactory = Angular.inject(Angular.RendererFactory2, {
47
46
  optional: true,
48
47
  })
@@ -1,4 +1,3 @@
1
- import { DOCUMENT } from '@angular/common'
2
1
  import * as Angular from '@angular/core'
3
2
  import { injectStore } from '../store/injectStore'
4
3
  import { buildMatchManagedDocumentContent } from './build-match-managed-document'
@@ -14,7 +13,7 @@ import type { ManagedTag } from './managed-document-types'
14
13
  * on the server only (call from an injection context).
15
14
  */
16
15
  export function installTanstackBodyManagedTags(injectedRouter: AnyRouter) {
17
- const document = Angular.inject(DOCUMENT)
16
+ const document = Angular.inject(Angular.DOCUMENT)
18
17
  const rendererFactory = Angular.inject(Angular.RendererFactory2, {
19
18
  optional: true,
20
19
  })
@@ -1,4 +1,3 @@
1
- import { DOCUMENT } from '@angular/common'
2
1
  import * as Angular from '@angular/core'
3
2
  import { injectStore } from '../store/injectStore'
4
3
  import { buildMatchManagedDocumentContent } from './build-match-managed-document'
@@ -7,7 +6,7 @@ import type { AnyRouter } from '@benjavicente/router-core'
7
6
 
8
7
  /** Sync `document.title` from the router (call from an injection context). */
9
8
  export function installTanstackDocumentTitle(injectedRouter: AnyRouter) {
10
- const document = Angular.inject(DOCUMENT)
9
+ const document = Angular.inject(Angular.DOCUMENT)
11
10
  const destroyRef = Angular.inject(Angular.DestroyRef)
12
11
  const activeMatches = injectStore(
13
12
  injectedRouter.stores.matches,
@@ -1,4 +1,3 @@
1
- import { DOCUMENT } from '@angular/common'
2
1
  import * as Angular from '@angular/core'
3
2
  import { injectStore } from '../store/injectStore'
4
3
  import { buildMatchManagedDocumentContent } from './build-match-managed-document'
@@ -10,7 +9,7 @@ import type { ManagedTag } from './managed-document-types'
10
9
 
11
10
  /** Managed `<head>` tags from active matches (call from an injection context). */
12
11
  export function installTanstackHeadManagedTags(injectedRouter: AnyRouter) {
13
- const document = Angular.inject(DOCUMENT)
12
+ const document = Angular.inject(Angular.DOCUMENT)
14
13
  const rendererFactory = Angular.inject(Angular.RendererFactory2, {
15
14
  optional: true,
16
15
  })
package/src/index.ts CHANGED
@@ -61,6 +61,7 @@ export {
61
61
 
62
62
  // Injection functions
63
63
  export { injectRouter } from './injectRouter'
64
+ export { injectIsShell } from './injectIsShell'
64
65
 
65
66
  export {
66
67
  injectRouterState,
@@ -0,0 +1,7 @@
1
+ import { computed } from '@angular/core'
2
+ import { injectRouter } from './injectRouter'
3
+
4
+ export function injectIsShell() {
5
+ const router = injectRouter()
6
+ return computed(() => router.isShell())
7
+ }
@@ -1,13 +1,13 @@
1
1
  import * as Angular from '@angular/core'
2
2
  import { injectRouter } from '../injectRouter'
3
3
  import { injectRouterState } from '../injectRouterState'
4
- import type { AnyRoute } from '@benjavicente/router-core'
4
+ import type { AnyRoute, AnyRouteMatch } from '@benjavicente/router-core'
5
5
 
6
- export function injectIsCatchingError({
6
+ export function injectCatchingErrorMatch({
7
7
  matchId,
8
8
  }: {
9
9
  matchId: Angular.Signal<string | undefined>
10
- }): Angular.Signal<boolean> {
10
+ }): Angular.Signal<AnyRouteMatch | undefined> {
11
11
  const router = injectRouter()
12
12
 
13
13
  const matches = injectRouterState({
@@ -20,21 +20,33 @@ export function injectIsCatchingError({
20
20
 
21
21
  return Angular.computed(() => {
22
22
  // The child route will handle the error with the default error component.
23
- if (router.options.defaultErrorComponent != null) return false;
23
+ if (router.options.defaultErrorComponent != null) return undefined
24
24
 
25
25
  const startingIndex = matchIndex()
26
- if (startingIndex === -1) return false
26
+ if (startingIndex === -1) return undefined
27
27
  const matchesList = matches()
28
28
 
29
29
  for (let i = startingIndex + 1; i < matchesList.length; i++) {
30
30
  const descendant = matchesList[i]
31
31
  const route = router.routesById[descendant?.routeId] as AnyRoute
32
32
  // Is catched by a child route with an error component.
33
- if (route.options.errorComponent != null) return false
33
+ if (route.options.errorComponent != null) return undefined
34
34
 
35
35
  // Found error status without error component in between.
36
- if (descendant?.status === "error") return true
36
+ if (descendant?.status === 'error') return descendant
37
37
  }
38
- return false
38
+ return undefined
39
+ })
40
+ }
41
+
42
+ export function injectIsCatchingError({
43
+ matchId,
44
+ }: {
45
+ matchId: Angular.Signal<string | undefined>
46
+ }): Angular.Signal<boolean> {
47
+ const catchingErrorMatch = injectCatchingErrorMatch({ matchId })
48
+
49
+ return Angular.computed(() => {
50
+ return catchingErrorMatch() !== undefined
39
51
  })
40
52
  }
@@ -10,7 +10,7 @@ import type { Provider, Type } from '@angular/core'
10
10
 
11
11
  export type RenderValue =
12
12
  | {
13
- key?: string
13
+ key?: unknown
14
14
  component: Type<any> | null | undefined
15
15
  inputs?: Record<string, () => unknown>
16
16
  providers?: Array<Provider>
@@ -57,7 +57,9 @@ export function injectRender(renderValueFn: () => RenderValue): void {
57
57
  function resolvedKey(value: RenderValue) {
58
58
  const component = value?.component
59
59
  if (!value || !component) return []
60
- return [component, value.key]
60
+ return Array.isArray(value.key)
61
+ ? [component, ...value.key]
62
+ : [component, value.key]
61
63
  }
62
64
 
63
65
  function keysAreEqual(a: Array<any>, b: Array<any>) {
package/src/route.ts CHANGED
@@ -53,9 +53,6 @@ declare module '@benjavicente/router-core' {
53
53
  }
54
54
 
55
55
  export interface RootRouteOptionsExtensions {
56
- shellComponent?: Angular.Type<{
57
- children: any
58
- }>
59
56
  }
60
57
 
61
58
  export interface RouteExtensions<
@@ -1,4 +1,4 @@
1
- import { DOCUMENT } from '@angular/common'
1
+ import { DOCUMENT } from '@angular/core'
2
2
  import { EnvironmentInjector, afterNextRender, inject } from '@angular/core'
3
3
  import { getScrollRestorationScriptForRouter } from '@benjavicente/router-core/scroll-restoration-script'
4
4
  import { injectRouter } from './injectRouter'