@ngrdt/router 0.0.98 → 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 +313 -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
|
}
|
|
@@ -850,6 +709,14 @@ class RdtRoute extends RdtRouteBase {
|
|
|
850
709
|
clone._queryParams = { ...this._queryParams };
|
|
851
710
|
return clone;
|
|
852
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
|
+
}
|
|
853
720
|
setRegex() {
|
|
854
721
|
const params = Object.keys(this._paramMap);
|
|
855
722
|
params.sort((a, b) => b.length - a.length);
|
|
@@ -859,7 +726,7 @@ class RdtRoute extends RdtRouteBase {
|
|
|
859
726
|
if (!type) {
|
|
860
727
|
throw new Error('Params is not set i');
|
|
861
728
|
}
|
|
862
|
-
substituted = substituted.replace(`:${p}`, RdtRoute.
|
|
729
|
+
substituted = substituted.replace(`:${p}`, RdtRoute.getGroup(type));
|
|
863
730
|
});
|
|
864
731
|
if (substituted === '') {
|
|
865
732
|
this._regex = null;
|
|
@@ -885,12 +752,228 @@ class RdtRoute extends RdtRouteBase {
|
|
|
885
752
|
return route;
|
|
886
753
|
}
|
|
887
754
|
static groups = {
|
|
888
|
-
string: '([
|
|
889
|
-
number: '(
|
|
755
|
+
string: '([^/]+)',
|
|
756
|
+
number: '(-?\\d+)',
|
|
890
757
|
};
|
|
891
758
|
}
|
|
892
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
|
+
|
|
893
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
|
+
*/
|
|
894
977
|
class RdtRouteBuilder extends RdtRouteBase {
|
|
895
978
|
get canBeEntered() {
|
|
896
979
|
return this._canBeEntered;
|
|
@@ -978,8 +1061,8 @@ class RdtRouteBuilder extends RdtRouteBase {
|
|
|
978
1061
|
}
|
|
979
1062
|
/**
|
|
980
1063
|
* Defines parameter type and lets framework parse it.
|
|
981
|
-
* @param paramName
|
|
982
|
-
* @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).
|
|
983
1066
|
*/
|
|
984
1067
|
withParam(paramName, type) {
|
|
985
1068
|
this._paramMap[paramName] = type;
|
|
@@ -1124,10 +1207,10 @@ class RdtAnyRouteActiveDirective {
|
|
|
1124
1207
|
return input;
|
|
1125
1208
|
}
|
|
1126
1209
|
}
|
|
1127
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1128
|
-
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 });
|
|
1129
1212
|
}
|
|
1130
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1213
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtAnyRouteActiveDirective, decorators: [{
|
|
1131
1214
|
type: Directive,
|
|
1132
1215
|
args: [{
|
|
1133
1216
|
selector: '[rdtAnyRouteActive]',
|
|
@@ -1167,10 +1250,10 @@ class RdtBackLinkDirective {
|
|
|
1167
1250
|
}
|
|
1168
1251
|
});
|
|
1169
1252
|
}
|
|
1170
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1171
|
-
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 });
|
|
1172
1255
|
}
|
|
1173
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1256
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtBackLinkDirective, decorators: [{
|
|
1174
1257
|
type: Directive,
|
|
1175
1258
|
args: [{
|
|
1176
1259
|
selector: '[rdtBackLink]',
|
|
@@ -1186,16 +1269,14 @@ class RdtRouterLinkDirective {
|
|
|
1186
1269
|
});
|
|
1187
1270
|
routerLinkRef = inject(RouterLink, { self: true });
|
|
1188
1271
|
envInjector = inject(EnvironmentInjector);
|
|
1189
|
-
routeInput = input.required({ alias: 'rdtRouterLink' });
|
|
1190
|
-
route = linkedSignal(() => this.routeInput());
|
|
1191
|
-
target = input('_self');
|
|
1192
|
-
params = input();
|
|
1193
|
-
queryParams = input();
|
|
1194
|
-
stateParams = input();
|
|
1195
|
-
disabled = input(false, {
|
|
1196
|
-
|
|
1197
|
-
transform: booleanAttribute,
|
|
1198
|
-
});
|
|
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 });
|
|
1199
1280
|
canBeEntered = computed(() => {
|
|
1200
1281
|
const route = this.route();
|
|
1201
1282
|
const disabled = this.disabled();
|
|
@@ -1208,7 +1289,7 @@ class RdtRouterLinkDirective {
|
|
|
1208
1289
|
state: this.stateParams(),
|
|
1209
1290
|
};
|
|
1210
1291
|
return route.canBeEntered(this.envInjector, combinedParams);
|
|
1211
|
-
});
|
|
1292
|
+
}, ...(ngDevMode ? [{ debugName: "canBeEntered" }] : []));
|
|
1212
1293
|
updateEffect = effect(() => {
|
|
1213
1294
|
if (this.buttonRef) {
|
|
1214
1295
|
this.updateButton();
|
|
@@ -1216,7 +1297,7 @@ class RdtRouterLinkDirective {
|
|
|
1216
1297
|
else {
|
|
1217
1298
|
this.updateRouterLink();
|
|
1218
1299
|
}
|
|
1219
|
-
});
|
|
1300
|
+
}, ...(ngDevMode ? [{ debugName: "updateEffect" }] : []));
|
|
1220
1301
|
updateButton() {
|
|
1221
1302
|
const route = this.route();
|
|
1222
1303
|
if (!route) {
|
|
@@ -1294,10 +1375,10 @@ class RdtRouterLinkDirective {
|
|
|
1294
1375
|
getNgHref(route) {
|
|
1295
1376
|
return route.createAbsoluteUrl(this.params());
|
|
1296
1377
|
}
|
|
1297
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1298
|
-
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 });
|
|
1299
1380
|
}
|
|
1300
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1381
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtRouterLinkDirective, decorators: [{
|
|
1301
1382
|
type: Directive,
|
|
1302
1383
|
args: [{
|
|
1303
1384
|
selector: '[rdtRouterLink]',
|
|
@@ -1309,19 +1390,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImpor
|
|
|
1309
1390
|
},
|
|
1310
1391
|
],
|
|
1311
1392
|
}]
|
|
1312
|
-
}] });
|
|
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 }] }] } });
|
|
1313
1394
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
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();
|
|
1317
1404
|
};
|
|
1318
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
|
+
*/
|
|
1319
1415
|
class GlobalRouteGuardService {
|
|
1320
1416
|
router;
|
|
1321
1417
|
constructor(router) {
|
|
1322
1418
|
this.router = router;
|
|
1323
1419
|
}
|
|
1324
|
-
// This will help ensure that routes are automatically protected by ensuring that global guards are applied to every route
|
|
1325
1420
|
ensureGlobalGuards() {
|
|
1326
1421
|
let lastUrl = '';
|
|
1327
1422
|
this.router.events.subscribe((e) => {
|
|
@@ -1354,10 +1449,10 @@ class GlobalRouteGuardService {
|
|
|
1354
1449
|
canDeactivateGuards.push(preventDataLossGuardFn);
|
|
1355
1450
|
}
|
|
1356
1451
|
}
|
|
1357
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1358
|
-
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' });
|
|
1359
1454
|
}
|
|
1360
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1455
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GlobalRouteGuardService, decorators: [{
|
|
1361
1456
|
type: Injectable,
|
|
1362
1457
|
args: [{ providedIn: 'root' }]
|
|
1363
1458
|
}], ctorParameters: () => [{ type: i1.Router }] });
|
|
@@ -1366,5 +1461,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImpor
|
|
|
1366
1461
|
* Generated bundle index. Do not edit.
|
|
1367
1462
|
*/
|
|
1368
1463
|
|
|
1369
|
-
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 };
|
|
1370
1465
|
//# sourceMappingURL=ngrdt-router.mjs.map
|