@mmstack/router-core 21.0.5 → 21.0.7
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.
|
@@ -5,7 +5,19 @@ import { Signal, InjectionToken, Provider, WritableSignal } from '@angular/core'
|
|
|
5
5
|
import { Observable } from 'rxjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* A flattened view of one route in the active router chain, used by the
|
|
9
|
+
* breadcrumb and title subsystems. Each `ResolvedLeafRoute` describes one
|
|
10
|
+
* "step" in the chain from root to current leaf.
|
|
11
|
+
*
|
|
12
|
+
* Exposed publicly because custom breadcrumb generators (see
|
|
13
|
+
* {@link BreadcrumbConfig}'s `generation` callback) receive instances of
|
|
14
|
+
* this type and need to read its fields.
|
|
15
|
+
*
|
|
16
|
+
* - `route` — the underlying `ActivatedRouteSnapshot`.
|
|
17
|
+
* - `segment.path` — the route config segment (e.g. `:userId`).
|
|
18
|
+
* - `segment.resolved` — the resolved value of that segment (e.g. `'42'`).
|
|
19
|
+
* - `path` — the full route-config path from root (with raw segments like `:userId`).
|
|
20
|
+
* - `link` — the full resolved URL from root (with substituted values).
|
|
9
21
|
*/
|
|
10
22
|
type ResolvedLeafRoute = {
|
|
11
23
|
route: ActivatedRouteSnapshot;
|
|
@@ -78,31 +90,33 @@ type BreadcrumbConfig = {
|
|
|
78
90
|
};
|
|
79
91
|
/**
|
|
80
92
|
* Provides configuration for the breadcrumb system.
|
|
81
|
-
*
|
|
93
|
+
*
|
|
94
|
+
* @param config A partial {@link BreadcrumbConfig}. The `generation` field controls
|
|
95
|
+
* automatic label generation: `'manual'` disables it (breadcrumbs only show when
|
|
96
|
+
* {@link createBreadcrumb} explicitly registers them); a function provides a
|
|
97
|
+
* custom label generator instead of the default route-title-based one.
|
|
98
|
+
* @returns A `Provider` to add to your app's providers array.
|
|
99
|
+
*
|
|
82
100
|
* @see BreadcrumbConfig
|
|
101
|
+
*
|
|
83
102
|
* @example
|
|
84
|
-
* ```
|
|
85
|
-
* //
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* //
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* // ...rest
|
|
102
|
-
* provideBreadcrumbConfig({
|
|
103
|
-
* generation: customLabelStrategy, // or 'manual' to disable auto-generation
|
|
104
|
-
* }),
|
|
105
|
-
* ]
|
|
103
|
+
* ```ts
|
|
104
|
+
* // Disable automatic generation — breadcrumbs only appear when createBreadcrumb is used
|
|
105
|
+
* bootstrapApplication(AppComponent, {
|
|
106
|
+
* providers: [
|
|
107
|
+
* provideRouter(routes),
|
|
108
|
+
* provideBreadcrumbConfig({ generation: 'manual' }),
|
|
109
|
+
* ],
|
|
110
|
+
* });
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* // Custom label strategy — outer fn runs in injection context, inner is reactive
|
|
116
|
+
* const customLabelStrategy = () => (leaf: ResolvedLeafRoute) =>
|
|
117
|
+
* leaf.route.data?.['navTitle'] ?? leaf.route.title ?? 'Unnamed';
|
|
118
|
+
*
|
|
119
|
+
* provideBreadcrumbConfig({ generation: customLabelStrategy });
|
|
106
120
|
* ```
|
|
107
121
|
*/
|
|
108
122
|
declare function provideBreadcrumbConfig(config: Partial<BreadcrumbConfig>): {
|
|
@@ -134,17 +148,24 @@ type CreateBreadcrumbOptions = {
|
|
|
134
148
|
awaitValue?: boolean;
|
|
135
149
|
};
|
|
136
150
|
/**
|
|
137
|
-
* Creates and registers a breadcrumb for a specific route.
|
|
138
|
-
*
|
|
151
|
+
* Creates and registers a breadcrumb for a specific route. Designed to be used
|
|
152
|
+
* as an Angular Route `ResolveFn` in the route's `resolve` map.
|
|
153
|
+
*
|
|
154
|
+
* Accepts a static label, a static options object, or a factory returning
|
|
155
|
+
* either — use a factory when you need `inject()` to read dynamic data.
|
|
139
156
|
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
157
|
+
* @param factoryOrValue One of: a literal label string (shorthand for
|
|
158
|
+
* `{ label: <string> }`), a static {@link CreateBreadcrumbOptions} object,
|
|
159
|
+
* or a factory `() => string | CreateBreadcrumbOptions` invoked inside an
|
|
160
|
+
* injection context (so it can use `inject()`).
|
|
161
|
+
* @returns An Angular `ResolveFn<void>` to wire into a route's `resolve` map.
|
|
162
|
+
* The resolver registers the breadcrumb as a side effect; the resolved value
|
|
163
|
+
* itself is unused.
|
|
142
164
|
*
|
|
143
|
-
* @param factoryOrValue A static label, a static `CreateBreadcrumbOptions`, or a factory returning either.
|
|
144
165
|
* @see CreateBreadcrumbOptions
|
|
145
166
|
*
|
|
146
167
|
* @example
|
|
147
|
-
* ```
|
|
168
|
+
* ```ts
|
|
148
169
|
* export const appRoutes: Routes = [
|
|
149
170
|
* {
|
|
150
171
|
* path: 'home',
|
|
@@ -161,7 +182,7 @@ type CreateBreadcrumbOptions = {
|
|
|
161
182
|
* breadcrumb: createBreadcrumb(() => {
|
|
162
183
|
* const userStore = inject(UserStore);
|
|
163
184
|
* return {
|
|
164
|
-
* label: () => userStore.user().name ?? 'Loading
|
|
185
|
+
* label: () => userStore.user().name ?? 'Loading…',
|
|
165
186
|
* };
|
|
166
187
|
* }),
|
|
167
188
|
* },
|
|
@@ -250,7 +271,56 @@ type MMLinkConfig = {
|
|
|
250
271
|
*/
|
|
251
272
|
useMouseDown: boolean;
|
|
252
273
|
};
|
|
274
|
+
/**
|
|
275
|
+
* Provide application-wide defaults for the `mmLink` directive. Each `[mmLink]`
|
|
276
|
+
* instance can still override per-link via its own `preloadOn` / `useMouseDown`
|
|
277
|
+
* inputs; this just shifts the default.
|
|
278
|
+
*
|
|
279
|
+
* @param config Partial override of `MMLinkConfig`. Unset keys fall back to:
|
|
280
|
+
* - `preloadOn: 'hover'` — preload triggered when the user hovers a link
|
|
281
|
+
* - `useMouseDown: false` — navigation triggered on click (not mousedown)
|
|
282
|
+
* @returns A `Provider` to add to your app's providers array.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```ts
|
|
286
|
+
* bootstrapApplication(AppComponent, {
|
|
287
|
+
* providers: [
|
|
288
|
+
* provideMMLinkDefaultConfig({ preloadOn: 'visible', useMouseDown: true }),
|
|
289
|
+
* ],
|
|
290
|
+
* });
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
253
293
|
declare function provideMMLinkDefaultConfig(config: Partial<MMLinkConfig>): Provider;
|
|
294
|
+
/**
|
|
295
|
+
* Drop-in replacement for `[routerLink]` that adds preloading on hover or
|
|
296
|
+
* visibility, optional mousedown-triggered navigation, and a `beforeNavigate`
|
|
297
|
+
* hook. Composes with Angular's `RouterLink` via `hostDirectives`, so every
|
|
298
|
+
* `RouterLink` input (`target`, `queryParams`, `fragment`, etc.) is forwarded.
|
|
299
|
+
*
|
|
300
|
+
* Preload behavior:
|
|
301
|
+
* - `preloadOn: 'hover'` (default) — preload when the user hovers the link
|
|
302
|
+
* - `preloadOn: 'visible'` — preload when the link scrolls into view
|
|
303
|
+
* - `preloadOn: null` — disable preloading on this link
|
|
304
|
+
*
|
|
305
|
+
* Navigation timing:
|
|
306
|
+
* - `useMouseDown: false` (default) — navigate on click
|
|
307
|
+
* - `useMouseDown: true` — navigate on mousedown (shaves ~50ms but breaks if the user
|
|
308
|
+
* moves off the link before mouseup)
|
|
309
|
+
*
|
|
310
|
+
* Requires {@link PreloadStrategy} to be wired via `provideRouter(routes, withComponentInputBinding(), withPreloading(PreloadStrategy))`.
|
|
311
|
+
* Set app-wide defaults with {@link provideMMLinkDefaultConfig}.
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* ```html
|
|
315
|
+
* <a [mmLink]="['/users', userId()]">View profile</a>
|
|
316
|
+
*
|
|
317
|
+
* <!-- Override per-link -->
|
|
318
|
+
* <a [mmLink]="'/heavy-page'" preloadOn="visible" useMouseDown>Heavy page</a>
|
|
319
|
+
*
|
|
320
|
+
* <!-- React to the preload starting -->
|
|
321
|
+
* <a [mmLink]="'/checkout'" (preloading)="onPreload()">Checkout</a>
|
|
322
|
+
* ```
|
|
323
|
+
*/
|
|
254
324
|
declare class Link {
|
|
255
325
|
private readonly routerLink;
|
|
256
326
|
private readonly req;
|
|
@@ -384,39 +454,97 @@ type NavConfig<TMeta = Record<string, unknown>> = {
|
|
|
384
454
|
defaults?: NavDefaultsForScope<TMeta> | Record<string, NavDefaultsForScope<TMeta>>;
|
|
385
455
|
};
|
|
386
456
|
/**
|
|
387
|
-
* Provides global configuration for the nav system.
|
|
457
|
+
* Provides global configuration for the nav system. The factory form runs in
|
|
458
|
+
* an injection context, so it can use `inject()` to build defaults from app
|
|
459
|
+
* state.
|
|
460
|
+
*
|
|
461
|
+
* @param config Either a literal {@link NavConfig} object or a factory
|
|
462
|
+
* `() => NavConfig`. Optional — without it, the nav system uses Angular's
|
|
463
|
+
* default `activeMatch` options and renders nothing in scopes that have no
|
|
464
|
+
* registered items.
|
|
465
|
+
* @returns A `Provider` to add to your app's providers array.
|
|
388
466
|
*
|
|
389
467
|
* @example
|
|
390
|
-
* ```
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
* {
|
|
395
|
-
*
|
|
468
|
+
* ```ts
|
|
469
|
+
* bootstrapApplication(AppComponent, {
|
|
470
|
+
* providers: [
|
|
471
|
+
* provideRouter(routes),
|
|
472
|
+
* provideNavConfig({
|
|
473
|
+
* activeMatch: { queryParams: 'ignored' },
|
|
474
|
+
* defaults: [
|
|
475
|
+
* { label: 'Home', link: '/' },
|
|
476
|
+
* { label: 'Docs', link: '/docs' },
|
|
477
|
+
* ],
|
|
478
|
+
* }),
|
|
396
479
|
* ],
|
|
397
|
-
* })
|
|
480
|
+
* });
|
|
481
|
+
* ```
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* ```ts
|
|
485
|
+
* // Factory form — read defaults from a service
|
|
486
|
+
* provideNavConfig(() => {
|
|
487
|
+
* const role = inject(AuthStore).role();
|
|
488
|
+
* return {
|
|
489
|
+
* defaults: {
|
|
490
|
+
* main: role === 'admin' ? adminNav : guestNav,
|
|
491
|
+
* },
|
|
492
|
+
* };
|
|
493
|
+
* });
|
|
398
494
|
* ```
|
|
399
495
|
*/
|
|
400
496
|
declare function provideNavConfig(config?: NavConfig | (() => NavConfig)): Provider;
|
|
401
497
|
|
|
402
498
|
/**
|
|
403
499
|
* Registers a set of nav items for the activating route under the given scope.
|
|
404
|
-
* Mirrors
|
|
405
|
-
* `resolve` map.
|
|
500
|
+
* Mirrors {@link createBreadcrumb} / {@link createTitle} — designed to be used
|
|
501
|
+
* in a route's `resolve` map.
|
|
502
|
+
*
|
|
503
|
+
* Scope override semantics: when multiple routes in the active chain register
|
|
504
|
+
* items under the same scope, the deepest active registration wins. Navigating
|
|
505
|
+
* away restores the shallower registration. To explicitly render an empty nav
|
|
506
|
+
* (shadowing a default), pass `[]`.
|
|
507
|
+
*
|
|
508
|
+
* @typeParam TMeta Optional per-item metadata type — flows through the
|
|
509
|
+
* registered items so consumers reading via {@link injectNavItems} get
|
|
510
|
+
* typed access to `item.meta`.
|
|
511
|
+
* @param itemsOrFactory Either a static array of {@link CreateNavItem} or a
|
|
512
|
+
* factory `() => CreateNavItem<TMeta>[]` invoked inside an injection
|
|
513
|
+
* context (so it can use `inject()` for dynamic items).
|
|
514
|
+
* @param options Optional `{ name }` for registering multiple scopes on a
|
|
515
|
+
* single route. Omit to target the default (unnamed) scope.
|
|
516
|
+
* @returns An Angular `ResolveFn<void>` to wire into a route's `resolve` map.
|
|
517
|
+
* The resolver registers items as a side effect; the resolved value itself
|
|
518
|
+
* is unused.
|
|
406
519
|
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
520
|
+
* @example
|
|
521
|
+
* ```ts
|
|
522
|
+
* // Single default-scope nav
|
|
523
|
+
* {
|
|
524
|
+
* path: 'app',
|
|
525
|
+
* resolve: {
|
|
526
|
+
* _nav: createNavItems([
|
|
527
|
+
* { label: 'Dashboard', link: 'dashboard' },
|
|
528
|
+
* { label: 'Reports', link: 'reports' },
|
|
529
|
+
* ]),
|
|
530
|
+
* },
|
|
531
|
+
* }
|
|
409
532
|
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
533
|
+
* // Multiple scopes
|
|
534
|
+
* {
|
|
535
|
+
* path: 'app',
|
|
536
|
+
* resolve: {
|
|
537
|
+
* mainNav: createNavItems([...], { name: 'main' }),
|
|
538
|
+
* sideNav: createNavItems([...], { name: 'side' }),
|
|
539
|
+
* },
|
|
414
540
|
* }
|
|
415
|
-
* ```
|
|
416
541
|
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
542
|
+
* // Factory using inject()
|
|
543
|
+
* createNavItems(() => {
|
|
544
|
+
* const auth = inject(AuthStore);
|
|
545
|
+
* return auth.canAdmin() ? adminItems : userItems;
|
|
546
|
+
* });
|
|
547
|
+
* ```
|
|
420
548
|
*/
|
|
421
549
|
declare function createNavItems<TMeta = Record<string, unknown>>(itemsOrFactory: CreateNavItem<TMeta>[] | (() => CreateNavItem<TMeta>[]), options?: {
|
|
422
550
|
name?: string;
|
|
@@ -449,6 +577,44 @@ declare function createNavItems<TMeta = Record<string, unknown>>(itemsOrFactory:
|
|
|
449
577
|
*/
|
|
450
578
|
declare function injectNavItems<TMeta = Record<string, unknown>>(name?: string): Signal<NavItem<TMeta>[]>;
|
|
451
579
|
|
|
580
|
+
/**
|
|
581
|
+
* Demand-driven preloading strategy for Angular's router. Unlike Angular's
|
|
582
|
+
* built-in `PreloadAllModules`, this strategy preloads a lazy route only
|
|
583
|
+
* when something explicitly requests it via {@link PreloadRequester} (e.g.
|
|
584
|
+
* the `mmLink` directive on hover or visibility, or {@link injectTriggerPreload}
|
|
585
|
+
* called imperatively).
|
|
586
|
+
*
|
|
587
|
+
* Skips preloading when:
|
|
588
|
+
* - the route has `data.preload === false`
|
|
589
|
+
* - the network is on `2g` or in `saveData` mode (cheap-data-mode users)
|
|
590
|
+
* - a load for the same path is already in flight
|
|
591
|
+
*
|
|
592
|
+
* Wire this into `provideRouter` to enable the `mmLink` preload pipeline:
|
|
593
|
+
*
|
|
594
|
+
* @example
|
|
595
|
+
* ```ts
|
|
596
|
+
* import { PreloadStrategy } from '@mmstack/router-core';
|
|
597
|
+
* import { provideRouter, withPreloading } from '@angular/router';
|
|
598
|
+
*
|
|
599
|
+
* bootstrapApplication(AppComponent, {
|
|
600
|
+
* providers: [
|
|
601
|
+
* provideRouter(routes, withPreloading(PreloadStrategy)),
|
|
602
|
+
* ],
|
|
603
|
+
* });
|
|
604
|
+
*
|
|
605
|
+
* // Then in templates, `mmLink` (or `injectTriggerPreload`) requests
|
|
606
|
+
* // preloads that this strategy executes:
|
|
607
|
+
* // <a [mmLink]="'/users'">Users</a>
|
|
608
|
+
* ```
|
|
609
|
+
*
|
|
610
|
+
* @example
|
|
611
|
+
* ```ts
|
|
612
|
+
* // Opt a route out of preloading:
|
|
613
|
+
* export const routes: Routes = [
|
|
614
|
+
* { path: 'admin', loadChildren: () => import('./admin'), data: { preload: false } },
|
|
615
|
+
* ];
|
|
616
|
+
* ```
|
|
617
|
+
*/
|
|
452
618
|
declare class PreloadStrategy implements PreloadingStrategy {
|
|
453
619
|
private readonly loading;
|
|
454
620
|
private readonly router;
|
|
@@ -560,19 +726,75 @@ type TitleConfig = {
|
|
|
560
726
|
keepLastKnownTitle?: boolean;
|
|
561
727
|
};
|
|
562
728
|
/**
|
|
563
|
-
*
|
|
729
|
+
* Provide application-wide configuration for the title subsystem. The config
|
|
730
|
+
* is only consumed when at least one route uses a {@link createTitle} resolver;
|
|
731
|
+
* routes without `createTitle` are unaffected.
|
|
732
|
+
*
|
|
733
|
+
* @param config Optional {@link TitleConfig}. All fields are optional — pass
|
|
734
|
+
* `prefix` to namespace titles (e.g. `"My App – "`), `initialTitle` to
|
|
735
|
+
* override the fallback (defaults to the `<title>` from `index.html`), and
|
|
736
|
+
* `keepLastKnownTitle: false` to clear the title on navigations to routes
|
|
737
|
+
* without a title (the default keeps the previous one).
|
|
738
|
+
* @returns A `Provider` to add to your app's providers array.
|
|
739
|
+
*
|
|
740
|
+
* @example
|
|
741
|
+
* ```ts
|
|
742
|
+
* bootstrapApplication(AppComponent, {
|
|
743
|
+
* providers: [
|
|
744
|
+
* provideRouter(routes),
|
|
745
|
+
* provideTitleConfig({
|
|
746
|
+
* prefix: (title) => `${title} • My App`,
|
|
747
|
+
* keepLastKnownTitle: true,
|
|
748
|
+
* }),
|
|
749
|
+
* ],
|
|
750
|
+
* });
|
|
751
|
+
* ```
|
|
564
752
|
*/
|
|
565
753
|
declare function provideTitleConfig(config?: TitleConfig): Provider;
|
|
566
754
|
|
|
567
755
|
/**
|
|
756
|
+
* Creates an Angular router `ResolveFn<string>` that registers a title for the
|
|
757
|
+
* route it's attached to. Titles can be static strings, factory functions
|
|
758
|
+
* (called in an injection context, so they can use `inject()`), or signal
|
|
759
|
+
* factories (for reactive titles that change when underlying data does).
|
|
760
|
+
*
|
|
761
|
+
* The resolved title flows through any `prefix` configured via
|
|
762
|
+
* {@link provideTitleConfig}, and is wired into Angular's `Title` service
|
|
763
|
+
* via an effect. Nested routes pick the most-specific leaf's title; if a
|
|
764
|
+
* deeper route has no title and `keepLastKnownTitle` is `true` (default),
|
|
765
|
+
* the previous title is preserved.
|
|
766
|
+
*
|
|
767
|
+
* @param factoryOrValue Either a literal string title, a `() => string`
|
|
768
|
+
* factory, or a `() => Signal<string>` factory for reactive titles. Factory
|
|
769
|
+
* callbacks run inside an injection context, so they can use `inject()`.
|
|
770
|
+
* @param awaitValue When `true`, the resolver waits until the title signal
|
|
771
|
+
* emits a truthy value before resolving — useful for SSR/SEO where the
|
|
772
|
+
* resolved title should not be empty. Defaults to `false`.
|
|
773
|
+
* @returns An Angular `ResolveFn<string>` to wire into a route's `title` field
|
|
774
|
+
* (or any other `resolve` slot — the return value isn't usually consumed).
|
|
568
775
|
*
|
|
569
|
-
*
|
|
776
|
+
* @example
|
|
777
|
+
* ```ts
|
|
778
|
+
* // Static title
|
|
779
|
+
* { path: 'about', component: AboutComponent, title: createTitle('About us') }
|
|
780
|
+
*
|
|
781
|
+
* // Factory using inject()
|
|
782
|
+
* {
|
|
783
|
+
* path: 'users/:id',
|
|
784
|
+
* component: UserComponent,
|
|
785
|
+
* title: createTitle(() => inject(ActivatedRoute).snapshot.params['id']),
|
|
786
|
+
* }
|
|
570
787
|
*
|
|
571
|
-
*
|
|
572
|
-
*
|
|
573
|
-
*
|
|
574
|
-
*
|
|
575
|
-
*
|
|
788
|
+
* // Reactive title from a signal store
|
|
789
|
+
* {
|
|
790
|
+
* path: 'dashboard',
|
|
791
|
+
* component: DashboardComponent,
|
|
792
|
+
* title: createTitle(() => {
|
|
793
|
+
* const user = inject(UserStore).current;
|
|
794
|
+
* return computed(() => `Dashboard – ${user()?.name ?? 'Guest'}`);
|
|
795
|
+
* }),
|
|
796
|
+
* }
|
|
797
|
+
* ```
|
|
576
798
|
*/
|
|
577
799
|
declare function createTitle(factoryOrValue: (() => string | (() => string)) | string, awaitValue?: boolean): ResolveFn<string>;
|
|
578
800
|
|