@ngrdt/router 0.0.97 → 0.0.99
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.
- package/fesm2022/ngrdt-router.mjs +315 -218
- package/fesm2022/ngrdt-router.mjs.map +1 -1
- package/package.json +9 -9
- package/{index.d.ts → types/ngrdt-router.d.ts} +139 -15
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject,
|
|
3
|
-
import {
|
|
4
|
-
import { PlatformLocation, Location } from '@angular/common';
|
|
2
|
+
import { InjectionToken, inject, EnvironmentInjector, runInInjectionContext, makeEnvironmentProviders, Injectable, DestroyRef, Renderer2, ElementRef, Input, Directive, afterNextRender, input, linkedSignal, booleanAttribute, computed, effect } from '@angular/core';
|
|
3
|
+
import { Location, PlatformLocation } from '@angular/common';
|
|
5
4
|
import * as i1 from '@angular/router';
|
|
6
5
|
import { Router, NavigationEnd, RouterModule, RouterLink, GuardsCheckStart } from '@angular/router';
|
|
7
6
|
import { RdtStringUtils } from '@ngrdt/utils';
|
|
7
|
+
import { filter, take, fromEvent } from 'rxjs';
|
|
8
8
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
9
9
|
import { RDT_BUTTON_BASE_PROVIDER } from '@ngrdt/button';
|
|
10
|
-
import { RdtComponentGuardStoreService } from '@ngrdt/core';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* Injection token that defines behavior when route cannot be entered (canBeEntered returns false).
|
|
@@ -15,15 +14,12 @@ import { RdtComponentGuardStoreService } from '@ngrdt/core';
|
|
|
15
14
|
*/
|
|
16
15
|
const RDT_CANNOT_BE_ENTERED_PROVIDER = new InjectionToken('RDT_CANNOT_BE_ENTERED', { factory: () => undefined, providedIn: 'root' });
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Type-safe container for route parameters keyed by route instance.
|
|
19
|
+
* Stores params for multiple routes in a hierarchy (e.g. parent + child params from a single URL).
|
|
20
|
+
* Parameters are keyed internally by absolute path, so two different `RdtRoute` instances
|
|
21
|
+
* with the same path share the same slot.
|
|
22
|
+
*/
|
|
27
23
|
class RdtParameters {
|
|
28
24
|
params;
|
|
29
25
|
constructor(params = {}) {
|
|
@@ -57,177 +53,23 @@ function ALWAYS_TRUE() {
|
|
|
57
53
|
return true;
|
|
58
54
|
}
|
|
59
55
|
|
|
60
|
-
const RDT_ROUTES_PROVIDER = new InjectionToken('RDT_ROUTES_PROVIDER');
|
|
61
|
-
|
|
62
|
-
const DEFAULT_TARGET = '_self';
|
|
63
|
-
const RDT_STATE_PARAMS_KEY = 'RdtStateParams';
|
|
64
|
-
class RdtRouterService {
|
|
65
|
-
allRoutes = inject(RDT_ROUTES_PROVIDER, { optional: true });
|
|
66
|
-
baseHref = inject(PlatformLocation).getBaseHrefFromDOM();
|
|
67
|
-
location = inject(Location);
|
|
68
|
-
router = inject(Router);
|
|
69
|
-
_previousUrl = null;
|
|
70
|
-
_currentUrl = null;
|
|
71
|
-
get previousUrl() {
|
|
72
|
-
return this._previousUrl;
|
|
73
|
-
}
|
|
74
|
-
get currentUrl() {
|
|
75
|
-
return this._currentUrl;
|
|
76
|
-
}
|
|
77
|
-
parsePreviousUrl() {
|
|
78
|
-
return this._previousUrl ? this.parseAbsoluteUrl(this._previousUrl) : null;
|
|
79
|
-
}
|
|
80
|
-
parseCurrentUrl() {
|
|
81
|
-
return this._currentUrl ? this.parseAbsoluteUrl(this._currentUrl) : null;
|
|
82
|
-
}
|
|
83
|
-
navigationEnd$ = this.router.events.pipe(filter((e) => e instanceof NavigationEnd));
|
|
84
|
-
nextNavigationEnd$ = this.navigationEnd$.pipe(take(1));
|
|
85
|
-
constructor() {
|
|
86
|
-
if (this.allRoutes === null) {
|
|
87
|
-
console.warn('All routes not provided. Make sure to provide RDT_ROUTES_PROVIDER with RdtRoute[].');
|
|
88
|
-
this.allRoutes = [];
|
|
89
|
-
}
|
|
90
|
-
this.navigationEnd$.subscribe((e) => {
|
|
91
|
-
this._previousUrl = this._currentUrl;
|
|
92
|
-
this._currentUrl = e.url;
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* @returns window.history.state extended by state parameters passed from parent window.
|
|
97
|
-
*/
|
|
98
|
-
getHistoryState() {
|
|
99
|
-
if ('RdtStateParams' in window &&
|
|
100
|
-
typeof window[RDT_STATE_PARAMS_KEY] === 'object') {
|
|
101
|
-
return { ...window[RDT_STATE_PARAMS_KEY], ...(history.state ?? {}) };
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
return history.state ?? {};
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Navigates to the specified route.
|
|
109
|
-
* @param link Route to navigate to.
|
|
110
|
-
* @param params Parameter of route passed as first argument or RdtParameters object.
|
|
111
|
-
* @param extras Allows you to specify additional parameters for navigation. These extras have higher priority than those defined in the route.
|
|
112
|
-
* @returns Promise that resolves when navigation is complete.
|
|
113
|
-
*/
|
|
114
|
-
navigate(link, params, extras) {
|
|
115
|
-
let url;
|
|
116
|
-
if (params instanceof RdtParameters) {
|
|
117
|
-
let linkCpy = link;
|
|
118
|
-
for (let r = linkCpy; r !== null; r = r.parent) {
|
|
119
|
-
const p = params.get(r);
|
|
120
|
-
if (p) {
|
|
121
|
-
linkCpy = linkCpy.withStaticParams(r, p);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
url = linkCpy.createAbsoluteUrl();
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
url = params ? link.createAbsoluteUrl(params) : link.createAbsoluteUrl();
|
|
128
|
-
}
|
|
129
|
-
const target = extras?.target ?? DEFAULT_TARGET;
|
|
130
|
-
const queryParams = extras?.query ?? link.queryParams;
|
|
131
|
-
const stateParams = extras?.state ?? link.stateParams;
|
|
132
|
-
if (target === '_self') {
|
|
133
|
-
return this.router.navigate([url], {
|
|
134
|
-
queryParams: queryParams,
|
|
135
|
-
state: stateParams,
|
|
136
|
-
replaceUrl: extras?.replaceUrl,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
const absolutePath = RdtStringUtils.createAbsoluteUrl(url, this.baseHref);
|
|
140
|
-
const pathWithParams = RdtStringUtils.appendQueryParams(absolutePath, queryParams);
|
|
141
|
-
const win = window.open(pathWithParams, target);
|
|
142
|
-
if (win) {
|
|
143
|
-
win[RDT_STATE_PARAMS_KEY] = stateParams;
|
|
144
|
-
}
|
|
145
|
-
return Promise.resolve(true);
|
|
146
|
-
}
|
|
147
|
-
navigateHome(extras) {
|
|
148
|
-
return this.router.navigateByUrl(RdtStringUtils.appendQueryParams('/', extras?.query ?? {}), {
|
|
149
|
-
state: extras?.state,
|
|
150
|
-
replaceUrl: extras?.replaceUrl,
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
navigateBack(extras) {
|
|
154
|
-
const parsed = this.parseAbsoluteUrl();
|
|
155
|
-
if (parsed) {
|
|
156
|
-
let route = parsed.route.withStaticParams(parsed.params);
|
|
157
|
-
do {
|
|
158
|
-
route = route.parent;
|
|
159
|
-
} while (route && route.entryDisabled);
|
|
160
|
-
// In case route has no ancestor with allowed entry,
|
|
161
|
-
if (!route) {
|
|
162
|
-
return this.navigateHome(extras);
|
|
163
|
-
}
|
|
164
|
-
if (extras) {
|
|
165
|
-
if (extras.query) {
|
|
166
|
-
route = route.withQueryParams(extras.query);
|
|
167
|
-
}
|
|
168
|
-
if (extras.state) {
|
|
169
|
-
route = route.withStateParams(extras.state);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return this.navigate(route, extras);
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
console.warn(`Cannot go back from ${this.location.path()} because no route matches current url`);
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* This method will try to find matching route for given absolute URL, extract its parameters and return them.
|
|
181
|
-
* If no route matches the URL, it returns null. Parameters are extracted only for the last matching route, not ancestors.
|
|
182
|
-
* @param url Absolute URL to parse (e.g. '/home/details/123?query=abc').
|
|
183
|
-
* @returns Object containing the matching route and its parameters, or null if no route is matching.
|
|
184
|
-
*/
|
|
185
|
-
parseAbsoluteUrl(url = this.location.path()) {
|
|
186
|
-
const stripped = RdtStringUtils.stripQueryParams(url);
|
|
187
|
-
if (this.allRoutes) {
|
|
188
|
-
for (const route of this.allRoutes) {
|
|
189
|
-
const params = route.parseAbsoluteUrl(stripped);
|
|
190
|
-
if (params) {
|
|
191
|
-
return { route, params };
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
extractAllParams(currentRoute) {
|
|
198
|
-
if (!currentRoute) {
|
|
199
|
-
const parsed = this.parseAbsoluteUrl();
|
|
200
|
-
if (!parsed) {
|
|
201
|
-
console.warn('No route matches current url.');
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
currentRoute = parsed.route;
|
|
205
|
-
}
|
|
206
|
-
const url = this.location.path();
|
|
207
|
-
return currentRoute.parseAbsoluteUrl(url);
|
|
208
|
-
}
|
|
209
|
-
isParentOfCurrentLocation(route) {
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
removeQueryParams(...paramNames) {
|
|
213
|
-
const regex = new RegExp(`[?&](${paramNames.join('|')})=[^&]*`, 'g');
|
|
214
|
-
return history.replaceState(history.state, '', location.pathname + location.search.replace(regex, '').replace(/^&/, '?'));
|
|
215
|
-
}
|
|
216
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: RdtRouterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
217
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: RdtRouterService, providedIn: 'root' });
|
|
218
|
-
}
|
|
219
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: RdtRouterService, decorators: [{
|
|
220
|
-
type: Injectable,
|
|
221
|
-
args: [{
|
|
222
|
-
providedIn: 'root',
|
|
223
|
-
}]
|
|
224
|
-
}], ctorParameters: () => [] });
|
|
225
|
-
|
|
226
56
|
var RdtNavigationSource;
|
|
227
57
|
(function (RdtNavigationSource) {
|
|
228
58
|
RdtNavigationSource["BREADCRUMB"] = "breadcrumb";
|
|
229
59
|
})(RdtNavigationSource || (RdtNavigationSource = {}));
|
|
230
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Bridges an `RdtRoute` to Angular's `Route` config.
|
|
63
|
+
* Obtained via `RdtRoute.toAngularRoute()`. Use the fluent API to attach
|
|
64
|
+
* components, guards, resolvers, and providers, then call `build()` to produce
|
|
65
|
+
* the Angular `Route` object.
|
|
66
|
+
*
|
|
67
|
+
* `build()` validates that children defined on the `RdtRoute` match the children
|
|
68
|
+
* passed via `withChildren()` — a mismatch throws at startup, catching misconfiguration early.
|
|
69
|
+
*
|
|
70
|
+
* When a route has both a component and children, `build()` automatically restructures
|
|
71
|
+
* the output into Angular's required wrapper format (parent with empty-path child for the component).
|
|
72
|
+
*/
|
|
231
73
|
class RdtAngularRoute {
|
|
232
74
|
route;
|
|
233
75
|
children = [];
|
|
@@ -427,8 +269,8 @@ class RdtAngularRoute {
|
|
|
427
269
|
}
|
|
428
270
|
getCanMatch() {
|
|
429
271
|
const paramMap = this.route.paramMap;
|
|
430
|
-
const
|
|
431
|
-
if (!
|
|
272
|
+
const hasConstrainedParams = Object.keys(paramMap).some((p) => paramMap[p] === 'number' || Array.isArray(paramMap[p]));
|
|
273
|
+
if (!hasConstrainedParams) {
|
|
432
274
|
return undefined;
|
|
433
275
|
}
|
|
434
276
|
const routeSegmentLength = this.route.path.split('/').length;
|
|
@@ -564,6 +406,19 @@ class RdtRouteBase {
|
|
|
564
406
|
}
|
|
565
407
|
}
|
|
566
408
|
|
|
409
|
+
/**
|
|
410
|
+
* Immutable, type-safe route definition. Created via `RdtRouteBuilder.build()`.
|
|
411
|
+
*
|
|
412
|
+
* Each instance represents a single route in the hierarchy and holds its path pattern,
|
|
413
|
+
* parameter types, parent reference, and access guard. Use `withStaticParams()`,
|
|
414
|
+
* `withQueryParams()`, or `withStateParams()` to create derived instances with
|
|
415
|
+
* pre-filled values (the original is never mutated).
|
|
416
|
+
*
|
|
417
|
+
* To bridge to Angular's router, call `toAngularRoute()` to get an `RdtAngularRoute`
|
|
418
|
+
* builder that produces an Angular `Route` config object.
|
|
419
|
+
*
|
|
420
|
+
* @typeParam T - Shape of this route's own parameters.
|
|
421
|
+
*/
|
|
567
422
|
class RdtRoute extends RdtRouteBase {
|
|
568
423
|
_absoluteRegex;
|
|
569
424
|
_staticParams = {};
|
|
@@ -712,7 +567,7 @@ class RdtRoute extends RdtRouteBase {
|
|
|
712
567
|
for (let i = 0; i < reversedParams.length && i < values.length; i++) {
|
|
713
568
|
const key = reversedParams[i];
|
|
714
569
|
const mappedKey = this._paramMappings.find((m) => m.urlName === key)?.tableName ?? key;
|
|
715
|
-
let val = values[i];
|
|
570
|
+
let val = decodeURIComponent(values[i]);
|
|
716
571
|
if (this._paramMap[key] === 'number') {
|
|
717
572
|
val = parseInt(val);
|
|
718
573
|
}
|
|
@@ -755,7 +610,11 @@ class RdtRoute extends RdtRouteBase {
|
|
|
755
610
|
throw new Error(`Param ${param} is missing for route ${this.absolutePath}. Please pass object with required parameters.`);
|
|
756
611
|
}
|
|
757
612
|
const value = extendedParamMap[param];
|
|
758
|
-
|
|
613
|
+
const type = this._paramMap[param];
|
|
614
|
+
if (Array.isArray(type) && !type.includes(`${value}`)) {
|
|
615
|
+
throw new Error(`Value "${value}" is not allowed for enum param "${param}" in route ${this.absolutePath}. Allowed values: ${type.join(', ')}`);
|
|
616
|
+
}
|
|
617
|
+
path = path.replace(`:${param}`, encodeURIComponent(`${value}`));
|
|
759
618
|
});
|
|
760
619
|
return path;
|
|
761
620
|
}
|
|
@@ -846,8 +705,18 @@ class RdtRoute extends RdtRouteBase {
|
|
|
846
705
|
clone._paramMappings = this._paramMappings;
|
|
847
706
|
clone._staticParams = { ...this._staticParams };
|
|
848
707
|
clone._stateParams = { ...this._stateParams };
|
|
708
|
+
clone._canBeEntered = this._canBeEntered;
|
|
709
|
+
clone._queryParams = { ...this._queryParams };
|
|
849
710
|
return clone;
|
|
850
711
|
}
|
|
712
|
+
static getGroup(type) {
|
|
713
|
+
if (Array.isArray(type)) {
|
|
714
|
+
return ('(' +
|
|
715
|
+
type.map((v) => v.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') +
|
|
716
|
+
')');
|
|
717
|
+
}
|
|
718
|
+
return RdtRoute.groups[type];
|
|
719
|
+
}
|
|
851
720
|
setRegex() {
|
|
852
721
|
const params = Object.keys(this._paramMap);
|
|
853
722
|
params.sort((a, b) => b.length - a.length);
|
|
@@ -857,7 +726,7 @@ class RdtRoute extends RdtRouteBase {
|
|
|
857
726
|
if (!type) {
|
|
858
727
|
throw new Error('Params is not set i');
|
|
859
728
|
}
|
|
860
|
-
substituted = substituted.replace(`:${p}`, RdtRoute.
|
|
729
|
+
substituted = substituted.replace(`:${p}`, RdtRoute.getGroup(type));
|
|
861
730
|
});
|
|
862
731
|
if (substituted === '') {
|
|
863
732
|
this._regex = null;
|
|
@@ -883,12 +752,228 @@ class RdtRoute extends RdtRouteBase {
|
|
|
883
752
|
return route;
|
|
884
753
|
}
|
|
885
754
|
static groups = {
|
|
886
|
-
string: '([
|
|
887
|
-
number: '(
|
|
755
|
+
string: '([^/]+)',
|
|
756
|
+
number: '(-?\\d+)',
|
|
888
757
|
};
|
|
889
758
|
}
|
|
890
759
|
|
|
760
|
+
/**
|
|
761
|
+
* Injection token holding all application routes as a flat array.
|
|
762
|
+
* Used internally by `RdtRouterService` to match URLs and enable type-safe navigation.
|
|
763
|
+
* Prefer using `provideRdtRoutes()` instead of providing this token directly.
|
|
764
|
+
*/
|
|
765
|
+
const RDT_ROUTES_PROVIDER = new InjectionToken('RDT_ROUTES_PROVIDER');
|
|
766
|
+
/**
|
|
767
|
+
* Provides all application routes to `RdtRouterService`.
|
|
768
|
+
* Accepts either a route module object (`import * as routes from './routes'`)
|
|
769
|
+
* or a plain array of `RdtRoute` instances.
|
|
770
|
+
*
|
|
771
|
+
* @example
|
|
772
|
+
* ```ts
|
|
773
|
+
* import * as ALL_ROUTES from './rdt-routes';
|
|
774
|
+
*
|
|
775
|
+
* export const appConfig: ApplicationConfig = {
|
|
776
|
+
* providers: [
|
|
777
|
+
* provideRdtRoutes(ALL_ROUTES),
|
|
778
|
+
* ],
|
|
779
|
+
* };
|
|
780
|
+
* ```
|
|
781
|
+
*/
|
|
782
|
+
function provideRdtRoutes(routes) {
|
|
783
|
+
const routeArray = Array.isArray(routes)
|
|
784
|
+
? routes
|
|
785
|
+
: Object.values(routes).filter((v) => v instanceof RdtRoute);
|
|
786
|
+
return makeEnvironmentProviders([
|
|
787
|
+
{ provide: RDT_ROUTES_PROVIDER, useValue: routeArray },
|
|
788
|
+
]);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const DEFAULT_TARGET = '_self';
|
|
792
|
+
const RDT_STATE_PARAMS_KEY = 'RdtStateParams';
|
|
793
|
+
/**
|
|
794
|
+
* Central navigation service for type-safe routing.
|
|
795
|
+
* Wraps Angular's `Router` with `RdtRoute`-based navigation, URL parsing, and history tracking.
|
|
796
|
+
* Requires `RDT_ROUTES_PROVIDER` to be configured with all application routes.
|
|
797
|
+
*/
|
|
798
|
+
class RdtRouterService {
|
|
799
|
+
allRoutes = inject(RDT_ROUTES_PROVIDER, { optional: true });
|
|
800
|
+
baseHref = inject(PlatformLocation).getBaseHrefFromDOM();
|
|
801
|
+
location = inject(Location);
|
|
802
|
+
router = inject(Router);
|
|
803
|
+
_previousUrl = null;
|
|
804
|
+
_currentUrl = null;
|
|
805
|
+
get previousUrl() {
|
|
806
|
+
return this._previousUrl;
|
|
807
|
+
}
|
|
808
|
+
get currentUrl() {
|
|
809
|
+
return this._currentUrl;
|
|
810
|
+
}
|
|
811
|
+
parsePreviousUrl() {
|
|
812
|
+
return this._previousUrl ? this.parseAbsoluteUrl(this._previousUrl) : null;
|
|
813
|
+
}
|
|
814
|
+
parseCurrentUrl() {
|
|
815
|
+
return this._currentUrl ? this.parseAbsoluteUrl(this._currentUrl) : null;
|
|
816
|
+
}
|
|
817
|
+
navigationEnd$ = this.router.events.pipe(filter((e) => e instanceof NavigationEnd));
|
|
818
|
+
nextNavigationEnd$ = this.navigationEnd$.pipe(take(1));
|
|
819
|
+
constructor() {
|
|
820
|
+
if (this.allRoutes === null) {
|
|
821
|
+
console.warn('All routes not provided. Make sure to provide RDT_ROUTES_PROVIDER with RdtRoute[].');
|
|
822
|
+
this.allRoutes = [];
|
|
823
|
+
}
|
|
824
|
+
this.navigationEnd$.subscribe((e) => {
|
|
825
|
+
this._previousUrl = this._currentUrl;
|
|
826
|
+
this._currentUrl = e.url;
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* @returns window.history.state extended by state parameters passed from parent window.
|
|
831
|
+
*/
|
|
832
|
+
getHistoryState() {
|
|
833
|
+
if ('RdtStateParams' in window &&
|
|
834
|
+
typeof window[RDT_STATE_PARAMS_KEY] === 'object') {
|
|
835
|
+
return { ...window[RDT_STATE_PARAMS_KEY], ...(history.state ?? {}) };
|
|
836
|
+
}
|
|
837
|
+
else {
|
|
838
|
+
return history.state ?? {};
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Navigates to the specified route.
|
|
843
|
+
* @param link Route to navigate to.
|
|
844
|
+
* @param params Parameter of route passed as first argument or RdtParameters object.
|
|
845
|
+
* @param extras Allows you to specify additional parameters for navigation. These extras have higher priority than those defined in the route.
|
|
846
|
+
* @returns Promise that resolves when navigation is complete.
|
|
847
|
+
*/
|
|
848
|
+
navigate(link, params, extras) {
|
|
849
|
+
let url;
|
|
850
|
+
if (params instanceof RdtParameters) {
|
|
851
|
+
let linkCpy = link;
|
|
852
|
+
for (let r = linkCpy; r !== null; r = r.parent) {
|
|
853
|
+
const p = params.get(r);
|
|
854
|
+
if (p) {
|
|
855
|
+
linkCpy = linkCpy.withStaticParams(r, p);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
url = linkCpy.createAbsoluteUrl();
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
url = params ? link.createAbsoluteUrl(params) : link.createAbsoluteUrl();
|
|
862
|
+
}
|
|
863
|
+
const target = extras?.target ?? DEFAULT_TARGET;
|
|
864
|
+
const queryParams = extras?.query ?? link.queryParams;
|
|
865
|
+
const stateParams = extras?.state ?? link.stateParams;
|
|
866
|
+
if (target === '_self') {
|
|
867
|
+
return this.router.navigate([url], {
|
|
868
|
+
queryParams: queryParams,
|
|
869
|
+
state: stateParams,
|
|
870
|
+
replaceUrl: extras?.replaceUrl,
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
const absolutePath = RdtStringUtils.createAbsoluteUrl(url, this.baseHref);
|
|
874
|
+
const pathWithParams = RdtStringUtils.appendQueryParams(absolutePath, queryParams);
|
|
875
|
+
const win = window.open(pathWithParams, target);
|
|
876
|
+
if (win) {
|
|
877
|
+
win[RDT_STATE_PARAMS_KEY] = stateParams;
|
|
878
|
+
}
|
|
879
|
+
return Promise.resolve(true);
|
|
880
|
+
}
|
|
881
|
+
navigateHome(extras) {
|
|
882
|
+
return this.router.navigateByUrl(RdtStringUtils.appendQueryParams('/', extras?.query ?? {}), {
|
|
883
|
+
state: extras?.state,
|
|
884
|
+
replaceUrl: extras?.replaceUrl,
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
navigateBack(extras) {
|
|
888
|
+
const parsed = this.parseAbsoluteUrl();
|
|
889
|
+
if (parsed) {
|
|
890
|
+
let route = parsed.route.withStaticParams(parsed.params);
|
|
891
|
+
do {
|
|
892
|
+
route = route.parent;
|
|
893
|
+
} while (route && route.entryDisabled);
|
|
894
|
+
// In case route has no ancestor with allowed entry,
|
|
895
|
+
if (!route) {
|
|
896
|
+
return this.navigateHome(extras);
|
|
897
|
+
}
|
|
898
|
+
if (extras) {
|
|
899
|
+
if (extras.query) {
|
|
900
|
+
route = route.withQueryParams(extras.query);
|
|
901
|
+
}
|
|
902
|
+
if (extras.state) {
|
|
903
|
+
route = route.withStateParams(extras.state);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
return this.navigate(route, extras);
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
console.warn(`Cannot go back from ${this.location.path()} because no route matches current url`);
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* This method will try to find matching route for given absolute URL, extract its parameters and return them.
|
|
915
|
+
* If no route matches the URL, it returns null. Parameters are extracted only for the last matching route, not ancestors.
|
|
916
|
+
* @param url Absolute URL to parse (e.g. '/home/details/123?query=abc').
|
|
917
|
+
* @returns Object containing the matching route and its parameters, or null if no route is matching.
|
|
918
|
+
*/
|
|
919
|
+
parseAbsoluteUrl(url = this.location.path()) {
|
|
920
|
+
const stripped = RdtStringUtils.stripQueryParams(url);
|
|
921
|
+
if (this.allRoutes) {
|
|
922
|
+
for (const route of this.allRoutes) {
|
|
923
|
+
const params = route.parseAbsoluteUrl(stripped);
|
|
924
|
+
if (params) {
|
|
925
|
+
return { route, params };
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
extractAllParams(currentRoute) {
|
|
932
|
+
if (!currentRoute) {
|
|
933
|
+
const parsed = this.parseAbsoluteUrl();
|
|
934
|
+
if (!parsed) {
|
|
935
|
+
console.warn('No route matches current url.');
|
|
936
|
+
return null;
|
|
937
|
+
}
|
|
938
|
+
currentRoute = parsed.route;
|
|
939
|
+
}
|
|
940
|
+
const url = this.location.path();
|
|
941
|
+
return currentRoute.parseAbsoluteUrl(url);
|
|
942
|
+
}
|
|
943
|
+
isParentOfCurrentLocation(route) {
|
|
944
|
+
return true;
|
|
945
|
+
}
|
|
946
|
+
removeQueryParams(...paramNames) {
|
|
947
|
+
const regex = new RegExp(`[?&](${paramNames.join('|')})=[^&]*`, 'g');
|
|
948
|
+
return history.replaceState(history.state, '', location.pathname + location.search.replace(regex, '').replace(/^&/, '?'));
|
|
949
|
+
}
|
|
950
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtRouterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
951
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtRouterService, providedIn: 'root' });
|
|
952
|
+
}
|
|
953
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtRouterService, decorators: [{
|
|
954
|
+
type: Injectable,
|
|
955
|
+
args: [{
|
|
956
|
+
providedIn: 'root',
|
|
957
|
+
}]
|
|
958
|
+
}], ctorParameters: () => [] });
|
|
959
|
+
|
|
891
960
|
const DEFAULT_PARAM_TYPE = 'number';
|
|
961
|
+
/**
|
|
962
|
+
* Fluent builder for creating `RdtRoute` instances.
|
|
963
|
+
* Routes are defined once in a central file and referenced everywhere by object, eliminating string-based path typos.
|
|
964
|
+
*
|
|
965
|
+
* @example
|
|
966
|
+
* ```ts
|
|
967
|
+
* const USER_DETAIL = new RdtRouteBuilder<{ userId: number }>()
|
|
968
|
+
* .withName('User Detail')
|
|
969
|
+
* .withPath(':userId')
|
|
970
|
+
* .withParam('userId', 'number')
|
|
971
|
+
* .withCanBeEntered((route, params) => params.params.userId !== 0)
|
|
972
|
+
* .build();
|
|
973
|
+
* ```
|
|
974
|
+
*
|
|
975
|
+
* @typeParam T - Shape of the route's parameters. Enforced at compile time when navigating or creating URLs.
|
|
976
|
+
*/
|
|
892
977
|
class RdtRouteBuilder extends RdtRouteBase {
|
|
893
978
|
get canBeEntered() {
|
|
894
979
|
return this._canBeEntered;
|
|
@@ -976,8 +1061,8 @@ class RdtRouteBuilder extends RdtRouteBase {
|
|
|
976
1061
|
}
|
|
977
1062
|
/**
|
|
978
1063
|
* Defines parameter type and lets framework parse it.
|
|
979
|
-
* @param paramName
|
|
980
|
-
* @param type
|
|
1064
|
+
* @param paramName Name of the parameter in the path.
|
|
1065
|
+
* @param type 'string', 'number', or an array of allowed string values (enum).
|
|
981
1066
|
*/
|
|
982
1067
|
withParam(paramName, type) {
|
|
983
1068
|
this._paramMap[paramName] = type;
|
|
@@ -1122,10 +1207,10 @@ class RdtAnyRouteActiveDirective {
|
|
|
1122
1207
|
return input;
|
|
1123
1208
|
}
|
|
1124
1209
|
}
|
|
1125
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1126
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "
|
|
1210
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtAnyRouteActiveDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1211
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.2", type: RdtAnyRouteActiveDirective, isStandalone: true, selector: "[rdtAnyRouteActive]", inputs: { anyRouteActive: ["rdtAnyRouteActive", "anyRouteActive"], watchedRoutes: "watchedRoutes", anyRouteActiveOptions: "anyRouteActiveOptions", ariaCurrentWhenActive: "ariaCurrentWhenActive" }, providers: [RouterModule], usesOnChanges: true, ngImport: i0 });
|
|
1127
1212
|
}
|
|
1128
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1213
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtAnyRouteActiveDirective, decorators: [{
|
|
1129
1214
|
type: Directive,
|
|
1130
1215
|
args: [{
|
|
1131
1216
|
selector: '[rdtAnyRouteActive]',
|
|
@@ -1165,10 +1250,10 @@ class RdtBackLinkDirective {
|
|
|
1165
1250
|
}
|
|
1166
1251
|
});
|
|
1167
1252
|
}
|
|
1168
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1169
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "
|
|
1253
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtBackLinkDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1254
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.2", type: RdtBackLinkDirective, isStandalone: true, selector: "[rdtBackLink]", ngImport: i0 });
|
|
1170
1255
|
}
|
|
1171
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1256
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtBackLinkDirective, decorators: [{
|
|
1172
1257
|
type: Directive,
|
|
1173
1258
|
args: [{
|
|
1174
1259
|
selector: '[rdtBackLink]',
|
|
@@ -1184,16 +1269,14 @@ class RdtRouterLinkDirective {
|
|
|
1184
1269
|
});
|
|
1185
1270
|
routerLinkRef = inject(RouterLink, { self: true });
|
|
1186
1271
|
envInjector = inject(EnvironmentInjector);
|
|
1187
|
-
routeInput = input.required({ alias: 'rdtRouterLink' });
|
|
1188
|
-
route = linkedSignal(() => this.routeInput());
|
|
1189
|
-
target = input('_self');
|
|
1190
|
-
params = input();
|
|
1191
|
-
queryParams = input();
|
|
1192
|
-
stateParams = input();
|
|
1193
|
-
disabled = input(false, {
|
|
1194
|
-
|
|
1195
|
-
transform: booleanAttribute,
|
|
1196
|
-
});
|
|
1272
|
+
routeInput = input.required({ ...(ngDevMode ? { debugName: "routeInput" } : {}), alias: 'rdtRouterLink' });
|
|
1273
|
+
route = linkedSignal(() => this.routeInput(), ...(ngDevMode ? [{ debugName: "route" }] : []));
|
|
1274
|
+
target = input('_self', ...(ngDevMode ? [{ debugName: "target" }] : []));
|
|
1275
|
+
params = input(...(ngDevMode ? [undefined, { debugName: "params" }] : []));
|
|
1276
|
+
queryParams = input(...(ngDevMode ? [undefined, { debugName: "queryParams" }] : []));
|
|
1277
|
+
stateParams = input(...(ngDevMode ? [undefined, { debugName: "stateParams" }] : []));
|
|
1278
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : {}), alias: 'rdtDisabled',
|
|
1279
|
+
transform: booleanAttribute });
|
|
1197
1280
|
canBeEntered = computed(() => {
|
|
1198
1281
|
const route = this.route();
|
|
1199
1282
|
const disabled = this.disabled();
|
|
@@ -1206,7 +1289,7 @@ class RdtRouterLinkDirective {
|
|
|
1206
1289
|
state: this.stateParams(),
|
|
1207
1290
|
};
|
|
1208
1291
|
return route.canBeEntered(this.envInjector, combinedParams);
|
|
1209
|
-
});
|
|
1292
|
+
}, ...(ngDevMode ? [{ debugName: "canBeEntered" }] : []));
|
|
1210
1293
|
updateEffect = effect(() => {
|
|
1211
1294
|
if (this.buttonRef) {
|
|
1212
1295
|
this.updateButton();
|
|
@@ -1214,7 +1297,7 @@ class RdtRouterLinkDirective {
|
|
|
1214
1297
|
else {
|
|
1215
1298
|
this.updateRouterLink();
|
|
1216
1299
|
}
|
|
1217
|
-
});
|
|
1300
|
+
}, ...(ngDevMode ? [{ debugName: "updateEffect" }] : []));
|
|
1218
1301
|
updateButton() {
|
|
1219
1302
|
const route = this.route();
|
|
1220
1303
|
if (!route) {
|
|
@@ -1292,10 +1375,10 @@ class RdtRouterLinkDirective {
|
|
|
1292
1375
|
getNgHref(route) {
|
|
1293
1376
|
return route.createAbsoluteUrl(this.params());
|
|
1294
1377
|
}
|
|
1295
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1296
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "
|
|
1378
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtRouterLinkDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1379
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.2", type: RdtRouterLinkDirective, isStandalone: true, selector: "[rdtRouterLink]", inputs: { routeInput: { classPropertyName: "routeInput", publicName: "rdtRouterLink", isSignal: true, isRequired: true, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null }, queryParams: { classPropertyName: "queryParams", publicName: "queryParams", isSignal: true, isRequired: false, transformFunction: null }, stateParams: { classPropertyName: "stateParams", publicName: "stateParams", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "rdtDisabled", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: i1.RouterLink, inputs: ["target", "target", "replaceUrl", "replaceUrl"] }], ngImport: i0 });
|
|
1297
1380
|
}
|
|
1298
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1381
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtRouterLinkDirective, decorators: [{
|
|
1299
1382
|
type: Directive,
|
|
1300
1383
|
args: [{
|
|
1301
1384
|
selector: '[rdtRouterLink]',
|
|
@@ -1307,19 +1390,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImpor
|
|
|
1307
1390
|
},
|
|
1308
1391
|
],
|
|
1309
1392
|
}]
|
|
1310
|
-
}] });
|
|
1393
|
+
}], propDecorators: { routeInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "rdtRouterLink", required: true }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], params: [{ type: i0.Input, args: [{ isSignal: true, alias: "params", required: false }] }], queryParams: [{ type: i0.Input, args: [{ isSignal: true, alias: "queryParams", required: false }] }], stateParams: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateParams", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "rdtDisabled", required: false }] }] } });
|
|
1311
1394
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1395
|
+
/**
|
|
1396
|
+
* `CanDeactivate` guard that blocks navigation when there are unsaved changes.
|
|
1397
|
+
* The routed component must implement `RdtGuardedContainer` from `@ngrdt/core`.
|
|
1398
|
+
* Add to routes via `RdtAngularRoute.addCanDeactivate(preventDataLossGuardFn)`.
|
|
1399
|
+
*/
|
|
1400
|
+
const preventDataLossGuardFn = (component) => {
|
|
1401
|
+
if (!component.guardRegistry.hasLeaveBlockers())
|
|
1402
|
+
return true;
|
|
1403
|
+
return component.guardRegistry.canLeaveAll();
|
|
1315
1404
|
};
|
|
1316
1405
|
|
|
1406
|
+
/**
|
|
1407
|
+
* Ensures that `preventDataLossGuardFn` is applied to every route at runtime,
|
|
1408
|
+
* even if it was not explicitly added in the route definition.
|
|
1409
|
+
* Call `ensureGlobalGuards()` once during app initialization.
|
|
1410
|
+
*
|
|
1411
|
+
* Also patches the browser back button behavior: replaces the URL back to the
|
|
1412
|
+
* previous value during guard checks so the address bar doesn't flash the new URL
|
|
1413
|
+
* before the guard potentially rejects it.
|
|
1414
|
+
*/
|
|
1317
1415
|
class GlobalRouteGuardService {
|
|
1318
1416
|
router;
|
|
1319
1417
|
constructor(router) {
|
|
1320
1418
|
this.router = router;
|
|
1321
1419
|
}
|
|
1322
|
-
// This will help ensure that routes are automatically protected by ensuring that global guards are applied to every route
|
|
1323
1420
|
ensureGlobalGuards() {
|
|
1324
1421
|
let lastUrl = '';
|
|
1325
1422
|
this.router.events.subscribe((e) => {
|
|
@@ -1352,10 +1449,10 @@ class GlobalRouteGuardService {
|
|
|
1352
1449
|
canDeactivateGuards.push(preventDataLossGuardFn);
|
|
1353
1450
|
}
|
|
1354
1451
|
}
|
|
1355
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1356
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
1452
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GlobalRouteGuardService, deps: [{ token: i1.Router }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1453
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GlobalRouteGuardService, providedIn: 'root' });
|
|
1357
1454
|
}
|
|
1358
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1455
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GlobalRouteGuardService, decorators: [{
|
|
1359
1456
|
type: Injectable,
|
|
1360
1457
|
args: [{ providedIn: 'root' }]
|
|
1361
1458
|
}], ctorParameters: () => [{ type: i1.Router }] });
|
|
@@ -1364,5 +1461,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImpor
|
|
|
1364
1461
|
* Generated bundle index. Do not edit.
|
|
1365
1462
|
*/
|
|
1366
1463
|
|
|
1367
|
-
export { GlobalRouteGuardService, PERMISSION_DISABLED_KEY, RDT_CANNOT_BE_ENTERED_PROVIDER,
|
|
1464
|
+
export { GlobalRouteGuardService, PERMISSION_DISABLED_KEY, RDT_CANNOT_BE_ENTERED_PROVIDER, RDT_ROUTES_PROVIDER, RdtAngularRoute, RdtAnyRouteActiveDirective, RdtBackLinkDirective, RdtNavigationSource, RdtParameters, RdtRoute, RdtRouteBuilder, RdtRouterLinkDirective, RdtRouterService, preventDataLossGuardFn, provideRdtRoutes };
|
|
1368
1465
|
//# sourceMappingURL=ngrdt-router.mjs.map
|