1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | import * as i0 from '@angular/core';
|
8 | import { ɵisObservable, ɵisPromise, ɵRuntimeError, Injectable, EventEmitter, inject, ViewContainerRef, ChangeDetectorRef, EnvironmentInjector, Directive, Input, Output, Component, createEnvironmentInjector, ɵisNgModule, isStandalone, ComponentFactoryResolver, ɵisInjectable, InjectionToken, InjectFlags, NgModuleFactory, ɵConsole, NgZone, ɵcoerceToBoolean, ɵɵsanitizeUrlOrResourceUrl, Attribute, HostBinding, HostListener, Optional, ContentChildren, makeEnvironmentProviders, APP_BOOTSTRAP_LISTENER, ENVIRONMENT_INITIALIZER, Injector, ApplicationRef, APP_INITIALIZER, NgProbeToken, SkipSelf, NgModule, Inject, Version } from '@angular/core';
|
9 | import { from, of, BehaviorSubject, EmptyError, combineLatest, concat, defer, pipe, throwError, Observable, EMPTY, ConnectableObservable, Subject } from 'rxjs';
|
10 | import * as i3 from '@angular/common';
|
11 | import { Location, ViewportScroller, LOCATION_INITIALIZED, LocationStrategy, HashLocationStrategy, PathLocationStrategy } from '@angular/common';
|
12 | import { map, switchMap, take, startWith, filter, mergeMap, first, concatMap, tap, catchError, scan, last as last$1, takeWhile, defaultIfEmpty, takeLast, mapTo, finalize, refCount, mergeAll } from 'rxjs/operators';
|
13 | import * as i1 from '@angular/platform-browser';
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 | const PRIMARY_OUTLET = 'primary';
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | const RouteTitleKey = Symbol('RouteTitle');
|
27 | class ParamsAsMap {
|
28 | constructor(params) {
|
29 | this.params = params || {};
|
30 | }
|
31 | has(name) {
|
32 | return Object.prototype.hasOwnProperty.call(this.params, name);
|
33 | }
|
34 | get(name) {
|
35 | if (this.has(name)) {
|
36 | const v = this.params[name];
|
37 | return Array.isArray(v) ? v[0] : v;
|
38 | }
|
39 | return null;
|
40 | }
|
41 | getAll(name) {
|
42 | if (this.has(name)) {
|
43 | const v = this.params[name];
|
44 | return Array.isArray(v) ? v : [v];
|
45 | }
|
46 | return [];
|
47 | }
|
48 | get keys() {
|
49 | return Object.keys(this.params);
|
50 | }
|
51 | }
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 | function convertToParamMap(params) {
|
60 | return new ParamsAsMap(params);
|
61 | }
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 | function defaultUrlMatcher(segments, segmentGroup, route) {
|
78 | const parts = route.path.split('/');
|
79 | if (parts.length > segments.length) {
|
80 |
|
81 | return null;
|
82 | }
|
83 | if (route.pathMatch === 'full' &&
|
84 | (segmentGroup.hasChildren() || parts.length < segments.length)) {
|
85 |
|
86 | return null;
|
87 | }
|
88 | const posParams = {};
|
89 |
|
90 | for (let index = 0; index < parts.length; index++) {
|
91 | const part = parts[index];
|
92 | const segment = segments[index];
|
93 | const isParameter = part.startsWith(':');
|
94 | if (isParameter) {
|
95 | posParams[part.substring(1)] = segment;
|
96 | }
|
97 | else if (part !== segment.path) {
|
98 |
|
99 | return null;
|
100 | }
|
101 | }
|
102 | return { consumed: segments.slice(0, parts.length), posParams };
|
103 | }
|
104 |
|
105 | function shallowEqualArrays(a, b) {
|
106 | if (a.length !== b.length)
|
107 | return false;
|
108 | for (let i = 0; i < a.length; ++i) {
|
109 | if (!shallowEqual(a[i], b[i]))
|
110 | return false;
|
111 | }
|
112 | return true;
|
113 | }
|
114 | function shallowEqual(a, b) {
|
115 |
|
116 |
|
117 | const k1 = a ? Object.keys(a) : undefined;
|
118 | const k2 = b ? Object.keys(b) : undefined;
|
119 | if (!k1 || !k2 || k1.length != k2.length) {
|
120 | return false;
|
121 | }
|
122 | let key;
|
123 | for (let i = 0; i < k1.length; i++) {
|
124 | key = k1[i];
|
125 | if (!equalArraysOrString(a[key], b[key])) {
|
126 | return false;
|
127 | }
|
128 | }
|
129 | return true;
|
130 | }
|
131 |
|
132 |
|
133 |
|
134 | function equalArraysOrString(a, b) {
|
135 | if (Array.isArray(a) && Array.isArray(b)) {
|
136 | if (a.length !== b.length)
|
137 | return false;
|
138 | const aSorted = [...a].sort();
|
139 | const bSorted = [...b].sort();
|
140 | return aSorted.every((val, index) => bSorted[index] === val);
|
141 | }
|
142 | else {
|
143 | return a === b;
|
144 | }
|
145 | }
|
146 |
|
147 |
|
148 |
|
149 | function flatten(arr) {
|
150 | return Array.prototype.concat.apply([], arr);
|
151 | }
|
152 |
|
153 |
|
154 |
|
155 | function last(a) {
|
156 | return a.length > 0 ? a[a.length - 1] : null;
|
157 | }
|
158 |
|
159 |
|
160 |
|
161 | function and(bools) {
|
162 | return !bools.some(v => !v);
|
163 | }
|
164 | function forEach(map, callback) {
|
165 | for (const prop in map) {
|
166 | if (map.hasOwnProperty(prop)) {
|
167 | callback(map[prop], prop);
|
168 | }
|
169 | }
|
170 | }
|
171 | function wrapIntoObservable(value) {
|
172 | if (ɵisObservable(value)) {
|
173 | return value;
|
174 | }
|
175 | if (ɵisPromise(value)) {
|
176 |
|
177 |
|
178 |
|
179 | return from(Promise.resolve(value));
|
180 | }
|
181 | return of(value);
|
182 | }
|
183 |
|
184 | const NG_DEV_MODE$b = typeof ngDevMode === 'undefined' || ngDevMode;
|
185 | const pathCompareMap = {
|
186 | 'exact': equalSegmentGroups,
|
187 | 'subset': containsSegmentGroup,
|
188 | };
|
189 | const paramCompareMap = {
|
190 | 'exact': equalParams,
|
191 | 'subset': containsParams,
|
192 | 'ignored': () => true,
|
193 | };
|
194 | function containsTree(container, containee, options) {
|
195 | return pathCompareMap[options.paths](container.root, containee.root, options.matrixParams) &&
|
196 | paramCompareMap[options.queryParams](container.queryParams, containee.queryParams) &&
|
197 | !(options.fragment === 'exact' && container.fragment !== containee.fragment);
|
198 | }
|
199 | function equalParams(container, containee) {
|
200 |
|
201 | return shallowEqual(container, containee);
|
202 | }
|
203 | function equalSegmentGroups(container, containee, matrixParams) {
|
204 | if (!equalPath(container.segments, containee.segments))
|
205 | return false;
|
206 | if (!matrixParamsMatch(container.segments, containee.segments, matrixParams)) {
|
207 | return false;
|
208 | }
|
209 | if (container.numberOfChildren !== containee.numberOfChildren)
|
210 | return false;
|
211 | for (const c in containee.children) {
|
212 | if (!container.children[c])
|
213 | return false;
|
214 | if (!equalSegmentGroups(container.children[c], containee.children[c], matrixParams))
|
215 | return false;
|
216 | }
|
217 | return true;
|
218 | }
|
219 | function containsParams(container, containee) {
|
220 | return Object.keys(containee).length <= Object.keys(container).length &&
|
221 | Object.keys(containee).every(key => equalArraysOrString(container[key], containee[key]));
|
222 | }
|
223 | function containsSegmentGroup(container, containee, matrixParams) {
|
224 | return containsSegmentGroupHelper(container, containee, containee.segments, matrixParams);
|
225 | }
|
226 | function containsSegmentGroupHelper(container, containee, containeePaths, matrixParams) {
|
227 | if (container.segments.length > containeePaths.length) {
|
228 | const current = container.segments.slice(0, containeePaths.length);
|
229 | if (!equalPath(current, containeePaths))
|
230 | return false;
|
231 | if (containee.hasChildren())
|
232 | return false;
|
233 | if (!matrixParamsMatch(current, containeePaths, matrixParams))
|
234 | return false;
|
235 | return true;
|
236 | }
|
237 | else if (container.segments.length === containeePaths.length) {
|
238 | if (!equalPath(container.segments, containeePaths))
|
239 | return false;
|
240 | if (!matrixParamsMatch(container.segments, containeePaths, matrixParams))
|
241 | return false;
|
242 | for (const c in containee.children) {
|
243 | if (!container.children[c])
|
244 | return false;
|
245 | if (!containsSegmentGroup(container.children[c], containee.children[c], matrixParams)) {
|
246 | return false;
|
247 | }
|
248 | }
|
249 | return true;
|
250 | }
|
251 | else {
|
252 | const current = containeePaths.slice(0, container.segments.length);
|
253 | const next = containeePaths.slice(container.segments.length);
|
254 | if (!equalPath(container.segments, current))
|
255 | return false;
|
256 | if (!matrixParamsMatch(container.segments, current, matrixParams))
|
257 | return false;
|
258 | if (!container.children[PRIMARY_OUTLET])
|
259 | return false;
|
260 | return containsSegmentGroupHelper(container.children[PRIMARY_OUTLET], containee, next, matrixParams);
|
261 | }
|
262 | }
|
263 | function matrixParamsMatch(containerPaths, containeePaths, options) {
|
264 | return containeePaths.every((containeeSegment, i) => {
|
265 | return paramCompareMap[options](containerPaths[i].parameters, containeeSegment.parameters);
|
266 | });
|
267 | }
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 |
|
297 |
|
298 | class UrlTree {
|
299 | constructor(
|
300 | /** The root segment group of the URL tree */
|
301 | root = new UrlSegmentGroup([], {}),
|
302 |
|
303 | queryParams = {},
|
304 |
|
305 | fragment = null) {
|
306 | this.root = root;
|
307 | this.queryParams = queryParams;
|
308 | this.fragment = fragment;
|
309 | if (NG_DEV_MODE$b) {
|
310 | if (root.segments.length > 0) {
|
311 | throw new ɵRuntimeError(4015 , 'The root `UrlSegmentGroup` should not contain `segments`. ' +
|
312 | 'Instead, these segments belong in the `children` so they can be associated with a named outlet.');
|
313 | }
|
314 | }
|
315 | }
|
316 | get queryParamMap() {
|
317 | if (!this._queryParamMap) {
|
318 | this._queryParamMap = convertToParamMap(this.queryParams);
|
319 | }
|
320 | return this._queryParamMap;
|
321 | }
|
322 |
|
323 | toString() {
|
324 | return DEFAULT_SERIALIZER.serialize(this);
|
325 | }
|
326 | }
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 | class UrlSegmentGroup {
|
337 | constructor(
|
338 | /** The URL segments of this group. See `UrlSegment` for more information */
|
339 | segments,
|
340 | /** The list of children of this group */
|
341 | children) {
|
342 | this.segments = segments;
|
343 | this.children = children;
|
344 |
|
345 | this.parent = null;
|
346 | forEach(children, (v, k) => v.parent = this);
|
347 | }
|
348 |
|
349 | hasChildren() {
|
350 | return this.numberOfChildren > 0;
|
351 | }
|
352 |
|
353 | get numberOfChildren() {
|
354 | return Object.keys(this.children).length;
|
355 | }
|
356 |
|
357 | toString() {
|
358 | return serializePaths(this);
|
359 | }
|
360 | }
|
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 |
|
383 |
|
384 |
|
385 |
|
386 |
|
387 | class UrlSegment {
|
388 | constructor(
|
389 | /** The path part of a URL segment */
|
390 | path,
|
391 | /** The matrix parameters associated with a segment */
|
392 | parameters) {
|
393 | this.path = path;
|
394 | this.parameters = parameters;
|
395 | }
|
396 | get parameterMap() {
|
397 | if (!this._parameterMap) {
|
398 | this._parameterMap = convertToParamMap(this.parameters);
|
399 | }
|
400 | return this._parameterMap;
|
401 | }
|
402 |
|
403 | toString() {
|
404 | return serializePath(this);
|
405 | }
|
406 | }
|
407 | function equalSegments(as, bs) {
|
408 | return equalPath(as, bs) && as.every((a, i) => shallowEqual(a.parameters, bs[i].parameters));
|
409 | }
|
410 | function equalPath(as, bs) {
|
411 | if (as.length !== bs.length)
|
412 | return false;
|
413 | return as.every((a, i) => a.path === bs[i].path);
|
414 | }
|
415 | function mapChildrenIntoArray(segment, fn) {
|
416 | let res = [];
|
417 | forEach(segment.children, (child, childOutlet) => {
|
418 | if (childOutlet === PRIMARY_OUTLET) {
|
419 | res = res.concat(fn(child, childOutlet));
|
420 | }
|
421 | });
|
422 | forEach(segment.children, (child, childOutlet) => {
|
423 | if (childOutlet !== PRIMARY_OUTLET) {
|
424 | res = res.concat(fn(child, childOutlet));
|
425 | }
|
426 | });
|
427 | return res;
|
428 | }
|
429 |
|
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 |
|
440 |
|
441 | class UrlSerializer {
|
442 | }
|
443 | UrlSerializer.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: UrlSerializer, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
444 | UrlSerializer.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: UrlSerializer, providedIn: 'root', useFactory: () => new DefaultUrlSerializer() });
|
445 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: UrlSerializer, decorators: [{
|
446 | type: Injectable,
|
447 | args: [{ providedIn: 'root', useFactory: () => new DefaultUrlSerializer() }]
|
448 | }] });
|
449 |
|
450 |
|
451 |
|
452 |
|
453 |
|
454 |
|
455 |
|
456 |
|
457 |
|
458 |
|
459 |
|
460 |
|
461 |
|
462 |
|
463 |
|
464 |
|
465 |
|
466 |
|
467 | class DefaultUrlSerializer {
|
468 |
|
469 | parse(url) {
|
470 | const p = new UrlParser(url);
|
471 | return new UrlTree(p.parseRootSegment(), p.parseQueryParams(), p.parseFragment());
|
472 | }
|
473 |
|
474 | serialize(tree) {
|
475 | const segment = `/${serializeSegment(tree.root, true)}`;
|
476 | const query = serializeQueryParams(tree.queryParams);
|
477 | const fragment = typeof tree.fragment === `string` ? `#${encodeUriFragment(tree.fragment)}` : '';
|
478 | return `${segment}${query}${fragment}`;
|
479 | }
|
480 | }
|
481 | const DEFAULT_SERIALIZER = new DefaultUrlSerializer();
|
482 | function serializePaths(segment) {
|
483 | return segment.segments.map(p => serializePath(p)).join('/');
|
484 | }
|
485 | function serializeSegment(segment, root) {
|
486 | if (!segment.hasChildren()) {
|
487 | return serializePaths(segment);
|
488 | }
|
489 | if (root) {
|
490 | const primary = segment.children[PRIMARY_OUTLET] ?
|
491 | serializeSegment(segment.children[PRIMARY_OUTLET], false) :
|
492 | '';
|
493 | const children = [];
|
494 | forEach(segment.children, (v, k) => {
|
495 | if (k !== PRIMARY_OUTLET) {
|
496 | children.push(`${k}:${serializeSegment(v, false)}`);
|
497 | }
|
498 | });
|
499 | return children.length > 0 ? `${primary}(${children.join('//')})` : primary;
|
500 | }
|
501 | else {
|
502 | const children = mapChildrenIntoArray(segment, (v, k) => {
|
503 | if (k === PRIMARY_OUTLET) {
|
504 | return [serializeSegment(segment.children[PRIMARY_OUTLET], false)];
|
505 | }
|
506 | return [`${k}:${serializeSegment(v, false)}`];
|
507 | });
|
508 |
|
509 | if (Object.keys(segment.children).length === 1 && segment.children[PRIMARY_OUTLET] != null) {
|
510 | return `${serializePaths(segment)}/${children[0]}`;
|
511 | }
|
512 | return `${serializePaths(segment)}/(${children.join('//')})`;
|
513 | }
|
514 | }
|
515 |
|
516 |
|
517 |
|
518 |
|
519 |
|
520 |
|
521 | function encodeUriString(s) {
|
522 | return encodeURIComponent(s)
|
523 | .replace(/%40/g, '@')
|
524 | .replace(/%3A/gi, ':')
|
525 | .replace(/%24/g, '$')
|
526 | .replace(/%2C/gi, ',');
|
527 | }
|
528 |
|
529 |
|
530 |
|
531 |
|
532 |
|
533 |
|
534 | function encodeUriQuery(s) {
|
535 | return encodeUriString(s).replace(/%3B/gi, ';');
|
536 | }
|
537 |
|
538 |
|
539 |
|
540 |
|
541 |
|
542 |
|
543 | function encodeUriFragment(s) {
|
544 | return encodeURI(s);
|
545 | }
|
546 |
|
547 |
|
548 |
|
549 |
|
550 |
|
551 |
|
552 |
|
553 | function encodeUriSegment(s) {
|
554 | return encodeUriString(s).replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/%26/gi, '&');
|
555 | }
|
556 | function decode(s) {
|
557 | return decodeURIComponent(s);
|
558 | }
|
559 |
|
560 |
|
561 | function decodeQuery(s) {
|
562 | return decode(s.replace(/\+/g, '%20'));
|
563 | }
|
564 | function serializePath(path) {
|
565 | return `${encodeUriSegment(path.path)}${serializeMatrixParams(path.parameters)}`;
|
566 | }
|
567 | function serializeMatrixParams(params) {
|
568 | return Object.keys(params)
|
569 | .map(key => `;${encodeUriSegment(key)}=${encodeUriSegment(params[key])}`)
|
570 | .join('');
|
571 | }
|
572 | function serializeQueryParams(params) {
|
573 | const strParams = Object.keys(params)
|
574 | .map((name) => {
|
575 | const value = params[name];
|
576 | return Array.isArray(value) ?
|
577 | value.map(v => `${encodeUriQuery(name)}=${encodeUriQuery(v)}`).join('&') :
|
578 | `${encodeUriQuery(name)}=${encodeUriQuery(value)}`;
|
579 | })
|
580 | .filter(s => !!s);
|
581 | return strParams.length ? `?${strParams.join('&')}` : '';
|
582 | }
|
583 | const SEGMENT_RE = /^[^\/()?;=#]+/;
|
584 | function matchSegments(str) {
|
585 | const match = str.match(SEGMENT_RE);
|
586 | return match ? match[0] : '';
|
587 | }
|
588 | const QUERY_PARAM_RE = /^[^=?&#]+/;
|
589 |
|
590 | function matchQueryParams(str) {
|
591 | const match = str.match(QUERY_PARAM_RE);
|
592 | return match ? match[0] : '';
|
593 | }
|
594 | const QUERY_PARAM_VALUE_RE = /^[^&#]+/;
|
595 |
|
596 | function matchUrlQueryParamValue(str) {
|
597 | const match = str.match(QUERY_PARAM_VALUE_RE);
|
598 | return match ? match[0] : '';
|
599 | }
|
600 | class UrlParser {
|
601 | constructor(url) {
|
602 | this.url = url;
|
603 | this.remaining = url;
|
604 | }
|
605 | parseRootSegment() {
|
606 | this.consumeOptional('/');
|
607 | if (this.remaining === '' || this.peekStartsWith('?') || this.peekStartsWith('#')) {
|
608 | return new UrlSegmentGroup([], {});
|
609 | }
|
610 |
|
611 | return new UrlSegmentGroup([], this.parseChildren());
|
612 | }
|
613 | parseQueryParams() {
|
614 | const params = {};
|
615 | if (this.consumeOptional('?')) {
|
616 | do {
|
617 | this.parseQueryParam(params);
|
618 | } while (this.consumeOptional('&'));
|
619 | }
|
620 | return params;
|
621 | }
|
622 | parseFragment() {
|
623 | return this.consumeOptional('#') ? decodeURIComponent(this.remaining) : null;
|
624 | }
|
625 | parseChildren() {
|
626 | if (this.remaining === '') {
|
627 | return {};
|
628 | }
|
629 | this.consumeOptional('/');
|
630 | const segments = [];
|
631 | if (!this.peekStartsWith('(')) {
|
632 | segments.push(this.parseSegment());
|
633 | }
|
634 | while (this.peekStartsWith('/') && !this.peekStartsWith('//') && !this.peekStartsWith('/(')) {
|
635 | this.capture('/');
|
636 | segments.push(this.parseSegment());
|
637 | }
|
638 | let children = {};
|
639 | if (this.peekStartsWith('/(')) {
|
640 | this.capture('/');
|
641 | children = this.parseParens(true);
|
642 | }
|
643 | let res = {};
|
644 | if (this.peekStartsWith('(')) {
|
645 | res = this.parseParens(false);
|
646 | }
|
647 | if (segments.length > 0 || Object.keys(children).length > 0) {
|
648 | res[PRIMARY_OUTLET] = new UrlSegmentGroup(segments, children);
|
649 | }
|
650 | return res;
|
651 | }
|
652 |
|
653 |
|
654 | parseSegment() {
|
655 | const path = matchSegments(this.remaining);
|
656 | if (path === '' && this.peekStartsWith(';')) {
|
657 | throw new ɵRuntimeError(4009 , NG_DEV_MODE$b && `Empty path url segment cannot have parameters: '${this.remaining}'.`);
|
658 | }
|
659 | this.capture(path);
|
660 | return new UrlSegment(decode(path), this.parseMatrixParams());
|
661 | }
|
662 | parseMatrixParams() {
|
663 | const params = {};
|
664 | while (this.consumeOptional(';')) {
|
665 | this.parseParam(params);
|
666 | }
|
667 | return params;
|
668 | }
|
669 | parseParam(params) {
|
670 | const key = matchSegments(this.remaining);
|
671 | if (!key) {
|
672 | return;
|
673 | }
|
674 | this.capture(key);
|
675 | let value = '';
|
676 | if (this.consumeOptional('=')) {
|
677 | const valueMatch = matchSegments(this.remaining);
|
678 | if (valueMatch) {
|
679 | value = valueMatch;
|
680 | this.capture(value);
|
681 | }
|
682 | }
|
683 | params[decode(key)] = decode(value);
|
684 | }
|
685 |
|
686 | parseQueryParam(params) {
|
687 | const key = matchQueryParams(this.remaining);
|
688 | if (!key) {
|
689 | return;
|
690 | }
|
691 | this.capture(key);
|
692 | let value = '';
|
693 | if (this.consumeOptional('=')) {
|
694 | const valueMatch = matchUrlQueryParamValue(this.remaining);
|
695 | if (valueMatch) {
|
696 | value = valueMatch;
|
697 | this.capture(value);
|
698 | }
|
699 | }
|
700 | const decodedKey = decodeQuery(key);
|
701 | const decodedVal = decodeQuery(value);
|
702 | if (params.hasOwnProperty(decodedKey)) {
|
703 |
|
704 | let currentVal = params[decodedKey];
|
705 | if (!Array.isArray(currentVal)) {
|
706 | currentVal = [currentVal];
|
707 | params[decodedKey] = currentVal;
|
708 | }
|
709 | currentVal.push(decodedVal);
|
710 | }
|
711 | else {
|
712 |
|
713 | params[decodedKey] = decodedVal;
|
714 | }
|
715 | }
|
716 |
|
717 | parseParens(allowPrimary) {
|
718 | const segments = {};
|
719 | this.capture('(');
|
720 | while (!this.consumeOptional(')') && this.remaining.length > 0) {
|
721 | const path = matchSegments(this.remaining);
|
722 | const next = this.remaining[path.length];
|
723 |
|
724 |
|
725 | if (next !== '/' && next !== ')' && next !== ';') {
|
726 | throw new ɵRuntimeError(4010 , NG_DEV_MODE$b && `Cannot parse url '${this.url}'`);
|
727 | }
|
728 | let outletName = undefined;
|
729 | if (path.indexOf(':') > -1) {
|
730 | outletName = path.slice(0, path.indexOf(':'));
|
731 | this.capture(outletName);
|
732 | this.capture(':');
|
733 | }
|
734 | else if (allowPrimary) {
|
735 | outletName = PRIMARY_OUTLET;
|
736 | }
|
737 | const children = this.parseChildren();
|
738 | segments[outletName] = Object.keys(children).length === 1 ? children[PRIMARY_OUTLET] :
|
739 | new UrlSegmentGroup([], children);
|
740 | this.consumeOptional('//');
|
741 | }
|
742 | return segments;
|
743 | }
|
744 | peekStartsWith(str) {
|
745 | return this.remaining.startsWith(str);
|
746 | }
|
747 |
|
748 | consumeOptional(str) {
|
749 | if (this.peekStartsWith(str)) {
|
750 | this.remaining = this.remaining.substring(str.length);
|
751 | return true;
|
752 | }
|
753 | return false;
|
754 | }
|
755 | capture(str) {
|
756 | if (!this.consumeOptional(str)) {
|
757 | throw new ɵRuntimeError(4011 , NG_DEV_MODE$b && `Expected "${str}".`);
|
758 | }
|
759 | }
|
760 | }
|
761 | function createRoot(rootCandidate) {
|
762 | return rootCandidate.segments.length > 0 ?
|
763 | new UrlSegmentGroup([], { [PRIMARY_OUTLET]: rootCandidate }) :
|
764 | rootCandidate;
|
765 | }
|
766 |
|
767 |
|
768 |
|
769 |
|
770 |
|
771 | function squashSegmentGroup(segmentGroup) {
|
772 | const newChildren = {};
|
773 | for (const childOutlet of Object.keys(segmentGroup.children)) {
|
774 | const child = segmentGroup.children[childOutlet];
|
775 | const childCandidate = squashSegmentGroup(child);
|
776 |
|
777 | if (childCandidate.segments.length > 0 || childCandidate.hasChildren()) {
|
778 | newChildren[childOutlet] = childCandidate;
|
779 | }
|
780 | }
|
781 | const s = new UrlSegmentGroup(segmentGroup.segments, newChildren);
|
782 | return mergeTrivialChildren(s);
|
783 | }
|
784 |
|
785 |
|
786 |
|
787 |
|
788 |
|
789 |
|
790 |
|
791 |
|
792 | function mergeTrivialChildren(s) {
|
793 | if (s.numberOfChildren === 1 && s.children[PRIMARY_OUTLET]) {
|
794 | const c = s.children[PRIMARY_OUTLET];
|
795 | return new UrlSegmentGroup(s.segments.concat(c.segments), c.children);
|
796 | }
|
797 | return s;
|
798 | }
|
799 | function isUrlTree(v) {
|
800 | return v instanceof UrlTree;
|
801 | }
|
802 |
|
803 | const NG_DEV_MODE$a = typeof ngDevMode === 'undefined' || ngDevMode;
|
804 |
|
805 |
|
806 |
|
807 |
|
808 |
|
809 |
|
810 |
|
811 |
|
812 |
|
813 |
|
814 |
|
815 |
|
816 |
|
817 |
|
818 |
|
819 |
|
820 |
|
821 |
|
822 |
|
823 |
|
824 |
|
825 |
|
826 |
|
827 |
|
828 |
|
829 |
|
830 |
|
831 |
|
832 |
|
833 |
|
834 |
|
835 |
|
836 |
|
837 |
|
838 |
|
839 |
|
840 |
|
841 |
|
842 |
|
843 |
|
844 |
|
845 |
|
846 |
|
847 |
|
848 |
|
849 |
|
850 |
|
851 |
|
852 |
|
853 |
|
854 |
|
855 | function createUrlTreeFromSnapshot(relativeTo, commands, queryParams = null, fragment = null) {
|
856 | const relativeToUrlSegmentGroup = createSegmentGroupFromRoute(relativeTo);
|
857 | return createUrlTreeFromSegmentGroup(relativeToUrlSegmentGroup, commands, queryParams, fragment);
|
858 | }
|
859 | function createSegmentGroupFromRoute(route) {
|
860 | let targetGroup;
|
861 | function createSegmentGroupFromRouteRecursive(currentRoute) {
|
862 | const childOutlets = {};
|
863 | for (const childSnapshot of currentRoute.children) {
|
864 | const root = createSegmentGroupFromRouteRecursive(childSnapshot);
|
865 | childOutlets[childSnapshot.outlet] = root;
|
866 | }
|
867 | const segmentGroup = new UrlSegmentGroup(currentRoute.url, childOutlets);
|
868 | if (currentRoute === route) {
|
869 | targetGroup = segmentGroup;
|
870 | }
|
871 | return segmentGroup;
|
872 | }
|
873 | const rootCandidate = createSegmentGroupFromRouteRecursive(route.root);
|
874 | const rootSegmentGroup = createRoot(rootCandidate);
|
875 | return targetGroup ?? rootSegmentGroup;
|
876 | }
|
877 | function createUrlTreeFromSegmentGroup(relativeTo, commands, queryParams, fragment) {
|
878 | let root = relativeTo;
|
879 | while (root.parent) {
|
880 | root = root.parent;
|
881 | }
|
882 |
|
883 |
|
884 |
|
885 | if (commands.length === 0) {
|
886 | return tree(root, root, root, queryParams, fragment);
|
887 | }
|
888 | const nav = computeNavigation(commands);
|
889 | if (nav.toRoot()) {
|
890 | return tree(root, root, new UrlSegmentGroup([], {}), queryParams, fragment);
|
891 | }
|
892 | const position = findStartingPositionForTargetGroup(nav, root, relativeTo);
|
893 | const newSegmentGroup = position.processChildren ?
|
894 | updateSegmentGroupChildren(position.segmentGroup, position.index, nav.commands) :
|
895 | updateSegmentGroup(position.segmentGroup, position.index, nav.commands);
|
896 | return tree(root, position.segmentGroup, newSegmentGroup, queryParams, fragment);
|
897 | }
|
898 | function createUrlTree(route, urlTree, commands, queryParams, fragment) {
|
899 | if (commands.length === 0) {
|
900 | return tree(urlTree.root, urlTree.root, urlTree.root, queryParams, fragment);
|
901 | }
|
902 | const nav = computeNavigation(commands);
|
903 | if (nav.toRoot()) {
|
904 | return tree(urlTree.root, urlTree.root, new UrlSegmentGroup([], {}), queryParams, fragment);
|
905 | }
|
906 | function createTreeUsingPathIndex(lastPathIndex) {
|
907 | const startingPosition = findStartingPosition(nav, urlTree, route.snapshot?._urlSegment, lastPathIndex);
|
908 | const segmentGroup = startingPosition.processChildren ?
|
909 | updateSegmentGroupChildren(startingPosition.segmentGroup, startingPosition.index, nav.commands) :
|
910 | updateSegmentGroup(startingPosition.segmentGroup, startingPosition.index, nav.commands);
|
911 | return tree(urlTree.root, startingPosition.segmentGroup, segmentGroup, queryParams, fragment);
|
912 | }
|
913 |
|
914 |
|
915 |
|
916 |
|
917 | const result = createTreeUsingPathIndex(route.snapshot?._lastPathIndex);
|
918 | return result;
|
919 | }
|
920 | function isMatrixParams(command) {
|
921 | return typeof command === 'object' && command != null && !command.outlets && !command.segmentPath;
|
922 | }
|
923 |
|
924 |
|
925 |
|
926 |
|
927 | function isCommandWithOutlets(command) {
|
928 | return typeof command === 'object' && command != null && command.outlets;
|
929 | }
|
930 | function tree(oldRoot, oldSegmentGroup, newSegmentGroup, queryParams, fragment) {
|
931 | let qp = {};
|
932 | if (queryParams) {
|
933 | forEach(queryParams, (value, name) => {
|
934 | qp[name] = Array.isArray(value) ? value.map((v) => `${v}`) : `${value}`;
|
935 | });
|
936 | }
|
937 | let rootCandidate;
|
938 | if (oldRoot === oldSegmentGroup) {
|
939 | rootCandidate = newSegmentGroup;
|
940 | }
|
941 | else {
|
942 | rootCandidate = replaceSegment(oldRoot, oldSegmentGroup, newSegmentGroup);
|
943 | }
|
944 | const newRoot = createRoot(squashSegmentGroup(rootCandidate));
|
945 | return new UrlTree(newRoot, qp, fragment);
|
946 | }
|
947 |
|
948 |
|
949 |
|
950 |
|
951 |
|
952 |
|
953 |
|
954 | function replaceSegment(current, oldSegment, newSegment) {
|
955 | const children = {};
|
956 | forEach(current.children, (c, outletName) => {
|
957 | if (c === oldSegment) {
|
958 | children[outletName] = newSegment;
|
959 | }
|
960 | else {
|
961 | children[outletName] = replaceSegment(c, oldSegment, newSegment);
|
962 | }
|
963 | });
|
964 | return new UrlSegmentGroup(current.segments, children);
|
965 | }
|
966 | class Navigation {
|
967 | constructor(isAbsolute, numberOfDoubleDots, commands) {
|
968 | this.isAbsolute = isAbsolute;
|
969 | this.numberOfDoubleDots = numberOfDoubleDots;
|
970 | this.commands = commands;
|
971 | if (isAbsolute && commands.length > 0 && isMatrixParams(commands[0])) {
|
972 | throw new ɵRuntimeError(4003 , NG_DEV_MODE$a && 'Root segment cannot have matrix parameters');
|
973 | }
|
974 | const cmdWithOutlet = commands.find(isCommandWithOutlets);
|
975 | if (cmdWithOutlet && cmdWithOutlet !== last(commands)) {
|
976 | throw new ɵRuntimeError(4004 , NG_DEV_MODE$a && '{outlets:{}} has to be the last command');
|
977 | }
|
978 | }
|
979 | toRoot() {
|
980 | return this.isAbsolute && this.commands.length === 1 && this.commands[0] == '/';
|
981 | }
|
982 | }
|
983 |
|
984 | function computeNavigation(commands) {
|
985 | if ((typeof commands[0] === 'string') && commands.length === 1 && commands[0] === '/') {
|
986 | return new Navigation(true, 0, commands);
|
987 | }
|
988 | let numberOfDoubleDots = 0;
|
989 | let isAbsolute = false;
|
990 | const res = commands.reduce((res, cmd, cmdIdx) => {
|
991 | if (typeof cmd === 'object' && cmd != null) {
|
992 | if (cmd.outlets) {
|
993 | const outlets = {};
|
994 | forEach(cmd.outlets, (commands, name) => {
|
995 | outlets[name] = typeof commands === 'string' ? commands.split('/') : commands;
|
996 | });
|
997 | return [...res, { outlets }];
|
998 | }
|
999 | if (cmd.segmentPath) {
|
1000 | return [...res, cmd.segmentPath];
|
1001 | }
|
1002 | }
|
1003 | if (!(typeof cmd === 'string')) {
|
1004 | return [...res, cmd];
|
1005 | }
|
1006 | if (cmdIdx === 0) {
|
1007 | cmd.split('/').forEach((urlPart, partIndex) => {
|
1008 | if (partIndex == 0 && urlPart === '.') {
|
1009 |
|
1010 | }
|
1011 | else if (partIndex == 0 && urlPart === '') {
|
1012 | isAbsolute = true;
|
1013 | }
|
1014 | else if (urlPart === '..') {
|
1015 | numberOfDoubleDots++;
|
1016 | }
|
1017 | else if (urlPart != '') {
|
1018 | res.push(urlPart);
|
1019 | }
|
1020 | });
|
1021 | return res;
|
1022 | }
|
1023 | return [...res, cmd];
|
1024 | }, []);
|
1025 | return new Navigation(isAbsolute, numberOfDoubleDots, res);
|
1026 | }
|
1027 | class Position {
|
1028 | constructor(segmentGroup, processChildren, index) {
|
1029 | this.segmentGroup = segmentGroup;
|
1030 | this.processChildren = processChildren;
|
1031 | this.index = index;
|
1032 | }
|
1033 | }
|
1034 | function findStartingPositionForTargetGroup(nav, root, target) {
|
1035 | if (nav.isAbsolute) {
|
1036 | return new Position(root, true, 0);
|
1037 | }
|
1038 | if (!target) {
|
1039 |
|
1040 |
|
1041 |
|
1042 |
|
1043 | return new Position(root, false, NaN);
|
1044 | }
|
1045 | if (target.parent === null) {
|
1046 | return new Position(target, true, 0);
|
1047 | }
|
1048 | const modifier = isMatrixParams(nav.commands[0]) ? 0 : 1;
|
1049 | const index = target.segments.length - 1 + modifier;
|
1050 | return createPositionApplyingDoubleDots(target, index, nav.numberOfDoubleDots);
|
1051 | }
|
1052 | function findStartingPosition(nav, tree, segmentGroup, lastPathIndex) {
|
1053 | if (nav.isAbsolute) {
|
1054 | return new Position(tree.root, true, 0);
|
1055 | }
|
1056 | if (lastPathIndex === -1) {
|
1057 |
|
1058 |
|
1059 |
|
1060 | const processChildren = segmentGroup === tree.root;
|
1061 | return new Position(segmentGroup, processChildren, 0);
|
1062 | }
|
1063 | const modifier = isMatrixParams(nav.commands[0]) ? 0 : 1;
|
1064 | const index = lastPathIndex + modifier;
|
1065 | return createPositionApplyingDoubleDots(segmentGroup, index, nav.numberOfDoubleDots);
|
1066 | }
|
1067 | function createPositionApplyingDoubleDots(group, index, numberOfDoubleDots) {
|
1068 | let g = group;
|
1069 | let ci = index;
|
1070 | let dd = numberOfDoubleDots;
|
1071 | while (dd > ci) {
|
1072 | dd -= ci;
|
1073 | g = g.parent;
|
1074 | if (!g) {
|
1075 | throw new ɵRuntimeError(4005 , NG_DEV_MODE$a && 'Invalid number of \'../\'');
|
1076 | }
|
1077 | ci = g.segments.length;
|
1078 | }
|
1079 | return new Position(g, false, ci - dd);
|
1080 | }
|
1081 | function getOutlets(commands) {
|
1082 | if (isCommandWithOutlets(commands[0])) {
|
1083 | return commands[0].outlets;
|
1084 | }
|
1085 | return { [PRIMARY_OUTLET]: commands };
|
1086 | }
|
1087 | function updateSegmentGroup(segmentGroup, startIndex, commands) {
|
1088 | if (!segmentGroup) {
|
1089 | segmentGroup = new UrlSegmentGroup([], {});
|
1090 | }
|
1091 | if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) {
|
1092 | return updateSegmentGroupChildren(segmentGroup, startIndex, commands);
|
1093 | }
|
1094 | const m = prefixedWith(segmentGroup, startIndex, commands);
|
1095 | const slicedCommands = commands.slice(m.commandIndex);
|
1096 | if (m.match && m.pathIndex < segmentGroup.segments.length) {
|
1097 | const g = new UrlSegmentGroup(segmentGroup.segments.slice(0, m.pathIndex), {});
|
1098 | g.children[PRIMARY_OUTLET] =
|
1099 | new UrlSegmentGroup(segmentGroup.segments.slice(m.pathIndex), segmentGroup.children);
|
1100 | return updateSegmentGroupChildren(g, 0, slicedCommands);
|
1101 | }
|
1102 | else if (m.match && slicedCommands.length === 0) {
|
1103 | return new UrlSegmentGroup(segmentGroup.segments, {});
|
1104 | }
|
1105 | else if (m.match && !segmentGroup.hasChildren()) {
|
1106 | return createNewSegmentGroup(segmentGroup, startIndex, commands);
|
1107 | }
|
1108 | else if (m.match) {
|
1109 | return updateSegmentGroupChildren(segmentGroup, 0, slicedCommands);
|
1110 | }
|
1111 | else {
|
1112 | return createNewSegmentGroup(segmentGroup, startIndex, commands);
|
1113 | }
|
1114 | }
|
1115 | function updateSegmentGroupChildren(segmentGroup, startIndex, commands) {
|
1116 | if (commands.length === 0) {
|
1117 | return new UrlSegmentGroup(segmentGroup.segments, {});
|
1118 | }
|
1119 | else {
|
1120 | const outlets = getOutlets(commands);
|
1121 | const children = {};
|
1122 |
|
1123 |
|
1124 |
|
1125 |
|
1126 |
|
1127 |
|
1128 |
|
1129 |
|
1130 |
|
1131 |
|
1132 |
|
1133 |
|
1134 |
|
1135 |
|
1136 |
|
1137 |
|
1138 |
|
1139 |
|
1140 |
|
1141 |
|
1142 | if (!outlets[PRIMARY_OUTLET] && segmentGroup.children[PRIMARY_OUTLET] &&
|
1143 | segmentGroup.numberOfChildren === 1 &&
|
1144 | segmentGroup.children[PRIMARY_OUTLET].segments.length === 0) {
|
1145 | return updateSegmentGroupChildren(segmentGroup.children[PRIMARY_OUTLET], startIndex, commands);
|
1146 | }
|
1147 | forEach(outlets, (commands, outlet) => {
|
1148 | if (typeof commands === 'string') {
|
1149 | commands = [commands];
|
1150 | }
|
1151 | if (commands !== null) {
|
1152 | children[outlet] = updateSegmentGroup(segmentGroup.children[outlet], startIndex, commands);
|
1153 | }
|
1154 | });
|
1155 | forEach(segmentGroup.children, (child, childOutlet) => {
|
1156 | if (outlets[childOutlet] === undefined) {
|
1157 | children[childOutlet] = child;
|
1158 | }
|
1159 | });
|
1160 | return new UrlSegmentGroup(segmentGroup.segments, children);
|
1161 | }
|
1162 | }
|
1163 | function prefixedWith(segmentGroup, startIndex, commands) {
|
1164 | let currentCommandIndex = 0;
|
1165 | let currentPathIndex = startIndex;
|
1166 | const noMatch = { match: false, pathIndex: 0, commandIndex: 0 };
|
1167 | while (currentPathIndex < segmentGroup.segments.length) {
|
1168 | if (currentCommandIndex >= commands.length)
|
1169 | return noMatch;
|
1170 | const path = segmentGroup.segments[currentPathIndex];
|
1171 | const command = commands[currentCommandIndex];
|
1172 |
|
1173 |
|
1174 |
|
1175 | if (isCommandWithOutlets(command)) {
|
1176 | break;
|
1177 | }
|
1178 | const curr = `${command}`;
|
1179 | const next = currentCommandIndex < commands.length - 1 ? commands[currentCommandIndex + 1] : null;
|
1180 | if (currentPathIndex > 0 && curr === undefined)
|
1181 | break;
|
1182 | if (curr && next && (typeof next === 'object') && next.outlets === undefined) {
|
1183 | if (!compare(curr, next, path))
|
1184 | return noMatch;
|
1185 | currentCommandIndex += 2;
|
1186 | }
|
1187 | else {
|
1188 | if (!compare(curr, {}, path))
|
1189 | return noMatch;
|
1190 | currentCommandIndex++;
|
1191 | }
|
1192 | currentPathIndex++;
|
1193 | }
|
1194 | return { match: true, pathIndex: currentPathIndex, commandIndex: currentCommandIndex };
|
1195 | }
|
1196 | function createNewSegmentGroup(segmentGroup, startIndex, commands) {
|
1197 | const paths = segmentGroup.segments.slice(0, startIndex);
|
1198 | let i = 0;
|
1199 | while (i < commands.length) {
|
1200 | const command = commands[i];
|
1201 | if (isCommandWithOutlets(command)) {
|
1202 | const children = createNewSegmentChildren(command.outlets);
|
1203 | return new UrlSegmentGroup(paths, children);
|
1204 | }
|
1205 |
|
1206 | if (i === 0 && isMatrixParams(commands[0])) {
|
1207 | const p = segmentGroup.segments[startIndex];
|
1208 | paths.push(new UrlSegment(p.path, stringify(commands[0])));
|
1209 | i++;
|
1210 | continue;
|
1211 | }
|
1212 | const curr = isCommandWithOutlets(command) ? command.outlets[PRIMARY_OUTLET] : `${command}`;
|
1213 | const next = (i < commands.length - 1) ? commands[i + 1] : null;
|
1214 | if (curr && next && isMatrixParams(next)) {
|
1215 | paths.push(new UrlSegment(curr, stringify(next)));
|
1216 | i += 2;
|
1217 | }
|
1218 | else {
|
1219 | paths.push(new UrlSegment(curr, {}));
|
1220 | i++;
|
1221 | }
|
1222 | }
|
1223 | return new UrlSegmentGroup(paths, {});
|
1224 | }
|
1225 | function createNewSegmentChildren(outlets) {
|
1226 | const children = {};
|
1227 | forEach(outlets, (commands, outlet) => {
|
1228 | if (typeof commands === 'string') {
|
1229 | commands = [commands];
|
1230 | }
|
1231 | if (commands !== null) {
|
1232 | children[outlet] = createNewSegmentGroup(new UrlSegmentGroup([], {}), 0, commands);
|
1233 | }
|
1234 | });
|
1235 | return children;
|
1236 | }
|
1237 | function stringify(params) {
|
1238 | const res = {};
|
1239 | forEach(params, (v, k) => res[k] = `${v}`);
|
1240 | return res;
|
1241 | }
|
1242 | function compare(path, params, segment) {
|
1243 | return path == segment.path && shallowEqual(params, segment.parameters);
|
1244 | }
|
1245 |
|
1246 | const IMPERATIVE_NAVIGATION = 'imperative';
|
1247 |
|
1248 |
|
1249 |
|
1250 |
|
1251 |
|
1252 |
|
1253 |
|
1254 |
|
1255 |
|
1256 |
|
1257 |
|
1258 |
|
1259 |
|
1260 |
|
1261 |
|
1262 |
|
1263 |
|
1264 |
|
1265 |
|
1266 |
|
1267 |
|
1268 |
|
1269 |
|
1270 |
|
1271 | class RouterEvent {
|
1272 | constructor(
|
1273 | /** A unique ID that the router assigns to every router navigation. */
|
1274 | id,
|
1275 | /** The URL that is the destination for this navigation. */
|
1276 | url) {
|
1277 | this.id = id;
|
1278 | this.url = url;
|
1279 | }
|
1280 | }
|
1281 |
|
1282 |
|
1283 |
|
1284 |
|
1285 |
|
1286 | class NavigationStart extends RouterEvent {
|
1287 | constructor(
|
1288 | /** @docsNotRequired */
|
1289 | id,
|
1290 | /** @docsNotRequired */
|
1291 | url,
|
1292 | /** @docsNotRequired */
|
1293 | navigationTrigger = 'imperative',
|
1294 | /** @docsNotRequired */
|
1295 | restoredState = null) {
|
1296 | super(id, url);
|
1297 | this.type = 0 ;
|
1298 | this.navigationTrigger = navigationTrigger;
|
1299 | this.restoredState = restoredState;
|
1300 | }
|
1301 |
|
1302 | toString() {
|
1303 | return `NavigationStart(id: ${this.id}, url: '${this.url}')`;
|
1304 | }
|
1305 | }
|
1306 |
|
1307 |
|
1308 |
|
1309 |
|
1310 |
|
1311 |
|
1312 |
|
1313 |
|
1314 |
|
1315 | class NavigationEnd extends RouterEvent {
|
1316 | constructor(
|
1317 | /** @docsNotRequired */
|
1318 | id,
|
1319 | /** @docsNotRequired */
|
1320 | url,
|
1321 | /** @docsNotRequired */
|
1322 | urlAfterRedirects) {
|
1323 | super(id, url);
|
1324 | this.urlAfterRedirects = urlAfterRedirects;
|
1325 | this.type = 1 ;
|
1326 | }
|
1327 |
|
1328 | toString() {
|
1329 | return `NavigationEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}')`;
|
1330 | }
|
1331 | }
|
1332 |
|
1333 |
|
1334 |
|
1335 |
|
1336 |
|
1337 |
|
1338 |
|
1339 |
|
1340 |
|
1341 |
|
1342 |
|
1343 | class NavigationCancel extends RouterEvent {
|
1344 | constructor(
|
1345 | /** @docsNotRequired */
|
1346 | id,
|
1347 | /** @docsNotRequired */
|
1348 | url,
|
1349 | /**
|
1350 | * A description of why the navigation was cancelled. For debug purposes only. Use `code`
|
1351 | * instead for a stable cancellation reason that can be used in production.
|
1352 | */
|
1353 | reason,
|
1354 | /**
|
1355 | * A code to indicate why the navigation was canceled. This cancellation code is stable for
|
1356 | * the reason and can be relied on whereas the `reason` string could change and should not be
|
1357 | * used in production.
|
1358 | */
|
1359 | code) {
|
1360 | super(id, url);
|
1361 | this.reason = reason;
|
1362 | this.code = code;
|
1363 | this.type = 2 ;
|
1364 | }
|
1365 |
|
1366 | toString() {
|
1367 | return `NavigationCancel(id: ${this.id}, url: '${this.url}')`;
|
1368 | }
|
1369 | }
|
1370 |
|
1371 |
|
1372 |
|
1373 |
|
1374 |
|
1375 |
|
1376 |
|
1377 |
|
1378 | class NavigationSkipped extends RouterEvent {
|
1379 | constructor(
|
1380 | /** @docsNotRequired */
|
1381 | id,
|
1382 | /** @docsNotRequired */
|
1383 | url,
|
1384 | /**
|
1385 | * A description of why the navigation was skipped. For debug purposes only. Use `code`
|
1386 | * instead for a stable skipped reason that can be used in production.
|
1387 | */
|
1388 | reason,
|
1389 | /**
|
1390 | * A code to indicate why the navigation was skipped. This code is stable for
|
1391 | * the reason and can be relied on whereas the `reason` string could change and should not be
|
1392 | * used in production.
|
1393 | */
|
1394 | code) {
|
1395 | super(id, url);
|
1396 | this.reason = reason;
|
1397 | this.code = code;
|
1398 | this.type = 16 ;
|
1399 | }
|
1400 | }
|
1401 |
|
1402 |
|
1403 |
|
1404 |
|
1405 |
|
1406 |
|
1407 |
|
1408 |
|
1409 |
|
1410 | class NavigationError extends RouterEvent {
|
1411 | constructor(
|
1412 | /** @docsNotRequired */
|
1413 | id,
|
1414 | /** @docsNotRequired */
|
1415 | url,
|
1416 | /** @docsNotRequired */
|
1417 | error,
|
1418 | /**
|
1419 | * The target of the navigation when the error occurred.
|
1420 | *
|
1421 | * Note that this can be `undefined` because an error could have occurred before the
|
1422 | * `RouterStateSnapshot` was created for the navigation.
|
1423 | */
|
1424 | target) {
|
1425 | super(id, url);
|
1426 | this.error = error;
|
1427 | this.target = target;
|
1428 | this.type = 3 ;
|
1429 | }
|
1430 |
|
1431 | toString() {
|
1432 | return `NavigationError(id: ${this.id}, url: '${this.url}', error: ${this.error})`;
|
1433 | }
|
1434 | }
|
1435 |
|
1436 |
|
1437 |
|
1438 |
|
1439 |
|
1440 | class RoutesRecognized extends RouterEvent {
|
1441 | constructor(
|
1442 | /** @docsNotRequired */
|
1443 | id,
|
1444 | /** @docsNotRequired */
|
1445 | url,
|
1446 | /** @docsNotRequired */
|
1447 | urlAfterRedirects,
|
1448 | /** @docsNotRequired */
|
1449 | state) {
|
1450 | super(id, url);
|
1451 | this.urlAfterRedirects = urlAfterRedirects;
|
1452 | this.state = state;
|
1453 | this.type = 4 ;
|
1454 | }
|
1455 |
|
1456 | toString() {
|
1457 | return `RoutesRecognized(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
|
1458 | }
|
1459 | }
|
1460 |
|
1461 |
|
1462 |
|
1463 |
|
1464 |
|
1465 |
|
1466 |
|
1467 | class GuardsCheckStart extends RouterEvent {
|
1468 | constructor(
|
1469 | /** @docsNotRequired */
|
1470 | id,
|
1471 | /** @docsNotRequired */
|
1472 | url,
|
1473 | /** @docsNotRequired */
|
1474 | urlAfterRedirects,
|
1475 | /** @docsNotRequired */
|
1476 | state) {
|
1477 | super(id, url);
|
1478 | this.urlAfterRedirects = urlAfterRedirects;
|
1479 | this.state = state;
|
1480 | this.type = 7 ;
|
1481 | }
|
1482 | toString() {
|
1483 | return `GuardsCheckStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
|
1484 | }
|
1485 | }
|
1486 |
|
1487 |
|
1488 |
|
1489 |
|
1490 |
|
1491 |
|
1492 |
|
1493 | class GuardsCheckEnd extends RouterEvent {
|
1494 | constructor(
|
1495 | /** @docsNotRequired */
|
1496 | id,
|
1497 | /** @docsNotRequired */
|
1498 | url,
|
1499 | /** @docsNotRequired */
|
1500 | urlAfterRedirects,
|
1501 | /** @docsNotRequired */
|
1502 | state,
|
1503 | /** @docsNotRequired */
|
1504 | shouldActivate) {
|
1505 | super(id, url);
|
1506 | this.urlAfterRedirects = urlAfterRedirects;
|
1507 | this.state = state;
|
1508 | this.shouldActivate = shouldActivate;
|
1509 | this.type = 8 ;
|
1510 | }
|
1511 | toString() {
|
1512 | return `GuardsCheckEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state}, shouldActivate: ${this.shouldActivate})`;
|
1513 | }
|
1514 | }
|
1515 |
|
1516 |
|
1517 |
|
1518 |
|
1519 |
|
1520 |
|
1521 |
|
1522 |
|
1523 |
|
1524 |
|
1525 | class ResolveStart extends RouterEvent {
|
1526 | constructor(
|
1527 | /** @docsNotRequired */
|
1528 | id,
|
1529 | /** @docsNotRequired */
|
1530 | url,
|
1531 | /** @docsNotRequired */
|
1532 | urlAfterRedirects,
|
1533 | /** @docsNotRequired */
|
1534 | state) {
|
1535 | super(id, url);
|
1536 | this.urlAfterRedirects = urlAfterRedirects;
|
1537 | this.state = state;
|
1538 | this.type = 5 ;
|
1539 | }
|
1540 | toString() {
|
1541 | return `ResolveStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
|
1542 | }
|
1543 | }
|
1544 |
|
1545 |
|
1546 |
|
1547 |
|
1548 |
|
1549 |
|
1550 | class ResolveEnd extends RouterEvent {
|
1551 | constructor(
|
1552 | /** @docsNotRequired */
|
1553 | id,
|
1554 | /** @docsNotRequired */
|
1555 | url,
|
1556 | /** @docsNotRequired */
|
1557 | urlAfterRedirects,
|
1558 | /** @docsNotRequired */
|
1559 | state) {
|
1560 | super(id, url);
|
1561 | this.urlAfterRedirects = urlAfterRedirects;
|
1562 | this.state = state;
|
1563 | this.type = 6 ;
|
1564 | }
|
1565 | toString() {
|
1566 | return `ResolveEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
|
1567 | }
|
1568 | }
|
1569 |
|
1570 |
|
1571 |
|
1572 |
|
1573 |
|
1574 |
|
1575 |
|
1576 | class RouteConfigLoadStart {
|
1577 | constructor(
|
1578 | /** @docsNotRequired */
|
1579 | route) {
|
1580 | this.route = route;
|
1581 | this.type = 9 ;
|
1582 | }
|
1583 | toString() {
|
1584 | return `RouteConfigLoadStart(path: ${this.route.path})`;
|
1585 | }
|
1586 | }
|
1587 |
|
1588 |
|
1589 |
|
1590 |
|
1591 |
|
1592 |
|
1593 |
|
1594 | class RouteConfigLoadEnd {
|
1595 | constructor(
|
1596 | /** @docsNotRequired */
|
1597 | route) {
|
1598 | this.route = route;
|
1599 | this.type = 10 ;
|
1600 | }
|
1601 | toString() {
|
1602 | return `RouteConfigLoadEnd(path: ${this.route.path})`;
|
1603 | }
|
1604 | }
|
1605 |
|
1606 |
|
1607 |
|
1608 |
|
1609 |
|
1610 |
|
1611 |
|
1612 |
|
1613 | class ChildActivationStart {
|
1614 | constructor(
|
1615 | /** @docsNotRequired */
|
1616 | snapshot) {
|
1617 | this.snapshot = snapshot;
|
1618 | this.type = 11 ;
|
1619 | }
|
1620 | toString() {
|
1621 | const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
|
1622 | return `ChildActivationStart(path: '${path}')`;
|
1623 | }
|
1624 | }
|
1625 |
|
1626 |
|
1627 |
|
1628 |
|
1629 |
|
1630 |
|
1631 |
|
1632 | class ChildActivationEnd {
|
1633 | constructor(
|
1634 | /** @docsNotRequired */
|
1635 | snapshot) {
|
1636 | this.snapshot = snapshot;
|
1637 | this.type = 12 ;
|
1638 | }
|
1639 | toString() {
|
1640 | const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
|
1641 | return `ChildActivationEnd(path: '${path}')`;
|
1642 | }
|
1643 | }
|
1644 |
|
1645 |
|
1646 |
|
1647 |
|
1648 |
|
1649 |
|
1650 |
|
1651 |
|
1652 | class ActivationStart {
|
1653 | constructor(
|
1654 | /** @docsNotRequired */
|
1655 | snapshot) {
|
1656 | this.snapshot = snapshot;
|
1657 | this.type = 13 ;
|
1658 | }
|
1659 | toString() {
|
1660 | const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
|
1661 | return `ActivationStart(path: '${path}')`;
|
1662 | }
|
1663 | }
|
1664 |
|
1665 |
|
1666 |
|
1667 |
|
1668 |
|
1669 |
|
1670 |
|
1671 |
|
1672 | class ActivationEnd {
|
1673 | constructor(
|
1674 | /** @docsNotRequired */
|
1675 | snapshot) {
|
1676 | this.snapshot = snapshot;
|
1677 | this.type = 14 ;
|
1678 | }
|
1679 | toString() {
|
1680 | const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
|
1681 | return `ActivationEnd(path: '${path}')`;
|
1682 | }
|
1683 | }
|
1684 |
|
1685 |
|
1686 |
|
1687 |
|
1688 |
|
1689 | class Scroll {
|
1690 | constructor(
|
1691 | /** @docsNotRequired */
|
1692 | routerEvent,
|
1693 | /** @docsNotRequired */
|
1694 | position,
|
1695 | /** @docsNotRequired */
|
1696 | anchor) {
|
1697 | this.routerEvent = routerEvent;
|
1698 | this.position = position;
|
1699 | this.anchor = anchor;
|
1700 | this.type = 15 ;
|
1701 | }
|
1702 | toString() {
|
1703 | const pos = this.position ? `${this.position[0]}, ${this.position[1]}` : null;
|
1704 | return `Scroll(anchor: '${this.anchor}', position: '${pos}')`;
|
1705 | }
|
1706 | }
|
1707 | function stringifyEvent(routerEvent) {
|
1708 | if (!('type' in routerEvent)) {
|
1709 | return `Unknown Router Event: ${routerEvent.constructor.name}`;
|
1710 | }
|
1711 | switch (routerEvent.type) {
|
1712 | case 14 :
|
1713 | return `ActivationEnd(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
|
1714 | case 13 :
|
1715 | return `ActivationStart(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
|
1716 | case 12 :
|
1717 | return `ChildActivationEnd(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
|
1718 | case 11 :
|
1719 | return `ChildActivationStart(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
|
1720 | case 8 :
|
1721 | return `GuardsCheckEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state}, shouldActivate: ${routerEvent.shouldActivate})`;
|
1722 | case 7 :
|
1723 | return `GuardsCheckStart(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
|
1724 | case 2 :
|
1725 | return `NavigationCancel(id: ${routerEvent.id}, url: '${routerEvent.url}')`;
|
1726 | case 16 :
|
1727 | return `NavigationSkipped(id: ${routerEvent.id}, url: '${routerEvent.url}')`;
|
1728 | case 1 :
|
1729 | return `NavigationEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}')`;
|
1730 | case 3 :
|
1731 | return `NavigationError(id: ${routerEvent.id}, url: '${routerEvent.url}', error: ${routerEvent.error})`;
|
1732 | case 0 :
|
1733 | return `NavigationStart(id: ${routerEvent.id}, url: '${routerEvent.url}')`;
|
1734 | case 6 :
|
1735 | return `ResolveEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
|
1736 | case 5 :
|
1737 | return `ResolveStart(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
|
1738 | case 10 :
|
1739 | return `RouteConfigLoadEnd(path: ${routerEvent.route.path})`;
|
1740 | case 9 :
|
1741 | return `RouteConfigLoadStart(path: ${routerEvent.route.path})`;
|
1742 | case 4 :
|
1743 | return `RoutesRecognized(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
|
1744 | case 15 :
|
1745 | const pos = routerEvent.position ? `${routerEvent.position[0]}, ${routerEvent.position[1]}` : null;
|
1746 | return `Scroll(anchor: '${routerEvent.anchor}', position: '${pos}')`;
|
1747 | }
|
1748 | }
|
1749 |
|
1750 | const NG_DEV_MODE$9 = typeof ngDevMode === 'undefined' || ngDevMode;
|
1751 | class LegacyCreateUrlTree {
|
1752 | createUrlTree(relativeTo, currentState, currentUrlTree, commands, queryParams, fragment) {
|
1753 | const a = relativeTo || currentState.root;
|
1754 | const tree = createUrlTree(a, currentUrlTree, commands, queryParams, fragment);
|
1755 | if (NG_DEV_MODE$9) {
|
1756 | const treeFromSnapshotStrategy = new CreateUrlTreeUsingSnapshot().createUrlTree(relativeTo, currentState, currentUrlTree, commands, queryParams, fragment);
|
1757 | if (treeFromSnapshotStrategy.toString() !== tree.toString()) {
|
1758 | let warningString = `The navigation to ${tree.toString()} will instead go to ${treeFromSnapshotStrategy.toString()} in an upcoming version of Angular.`;
|
1759 | if (!!relativeTo) {
|
1760 | warningString += ' `relativeTo` might need to be removed from the `UrlCreationOptions`.';
|
1761 | }
|
1762 | tree._warnIfUsedForNavigation = warningString;
|
1763 | }
|
1764 | }
|
1765 | return tree;
|
1766 | }
|
1767 | }
|
1768 | LegacyCreateUrlTree.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: LegacyCreateUrlTree, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
1769 | LegacyCreateUrlTree.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: LegacyCreateUrlTree });
|
1770 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: LegacyCreateUrlTree, decorators: [{
|
1771 | type: Injectable
|
1772 | }] });
|
1773 | class CreateUrlTreeUsingSnapshot {
|
1774 | createUrlTree(relativeTo, currentState, currentUrlTree, commands, queryParams, fragment) {
|
1775 | let relativeToUrlSegmentGroup;
|
1776 | try {
|
1777 | const relativeToSnapshot = relativeTo ? relativeTo.snapshot : currentState.snapshot.root;
|
1778 | relativeToUrlSegmentGroup = createSegmentGroupFromRoute(relativeToSnapshot);
|
1779 | }
|
1780 | catch (e) {
|
1781 |
|
1782 |
|
1783 |
|
1784 |
|
1785 |
|
1786 | if (typeof commands[0] !== 'string' || !commands[0].startsWith('/')) {
|
1787 |
|
1788 |
|
1789 |
|
1790 |
|
1791 |
|
1792 |
|
1793 |
|
1794 | commands = [];
|
1795 | }
|
1796 | relativeToUrlSegmentGroup = currentUrlTree.root;
|
1797 | }
|
1798 | return createUrlTreeFromSegmentGroup(relativeToUrlSegmentGroup, commands, queryParams, fragment);
|
1799 | }
|
1800 | }
|
1801 | CreateUrlTreeUsingSnapshot.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: CreateUrlTreeUsingSnapshot, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
1802 | CreateUrlTreeUsingSnapshot.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: CreateUrlTreeUsingSnapshot });
|
1803 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: CreateUrlTreeUsingSnapshot, decorators: [{
|
1804 | type: Injectable
|
1805 | }] });
|
1806 | class CreateUrlTreeStrategy {
|
1807 | }
|
1808 | CreateUrlTreeStrategy.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: CreateUrlTreeStrategy, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
1809 | CreateUrlTreeStrategy.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: CreateUrlTreeStrategy, providedIn: 'root', useClass: LegacyCreateUrlTree });
|
1810 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: CreateUrlTreeStrategy, decorators: [{
|
1811 | type: Injectable,
|
1812 | args: [{ providedIn: 'root', useClass: LegacyCreateUrlTree }]
|
1813 | }] });
|
1814 |
|
1815 | class Tree {
|
1816 | constructor(root) {
|
1817 | this._root = root;
|
1818 | }
|
1819 | get root() {
|
1820 | return this._root.value;
|
1821 | }
|
1822 | |
1823 |
|
1824 |
|
1825 | parent(t) {
|
1826 | const p = this.pathFromRoot(t);
|
1827 | return p.length > 1 ? p[p.length - 2] : null;
|
1828 | }
|
1829 | |
1830 |
|
1831 |
|
1832 | children(t) {
|
1833 | const n = findNode(t, this._root);
|
1834 | return n ? n.children.map(t => t.value) : [];
|
1835 | }
|
1836 | |
1837 |
|
1838 |
|
1839 | firstChild(t) {
|
1840 | const n = findNode(t, this._root);
|
1841 | return n && n.children.length > 0 ? n.children[0].value : null;
|
1842 | }
|
1843 | |
1844 |
|
1845 |
|
1846 | siblings(t) {
|
1847 | const p = findPath(t, this._root);
|
1848 | if (p.length < 2)
|
1849 | return [];
|
1850 | const c = p[p.length - 2].children.map(c => c.value);
|
1851 | return c.filter(cc => cc !== t);
|
1852 | }
|
1853 | |
1854 |
|
1855 |
|
1856 | pathFromRoot(t) {
|
1857 | return findPath(t, this._root).map(s => s.value);
|
1858 | }
|
1859 | }
|
1860 |
|
1861 | function findNode(value, node) {
|
1862 | if (value === node.value)
|
1863 | return node;
|
1864 | for (const child of node.children) {
|
1865 | const node = findNode(value, child);
|
1866 | if (node)
|
1867 | return node;
|
1868 | }
|
1869 | return null;
|
1870 | }
|
1871 |
|
1872 | function findPath(value, node) {
|
1873 | if (value === node.value)
|
1874 | return [node];
|
1875 | for (const child of node.children) {
|
1876 | const path = findPath(value, child);
|
1877 | if (path.length) {
|
1878 | path.unshift(node);
|
1879 | return path;
|
1880 | }
|
1881 | }
|
1882 | return [];
|
1883 | }
|
1884 | class TreeNode {
|
1885 | constructor(value, children) {
|
1886 | this.value = value;
|
1887 | this.children = children;
|
1888 | }
|
1889 | toString() {
|
1890 | return `TreeNode(${this.value})`;
|
1891 | }
|
1892 | }
|
1893 |
|
1894 | function nodeChildrenAsMap(node) {
|
1895 | const map = {};
|
1896 | if (node) {
|
1897 | node.children.forEach(child => map[child.value.outlet] = child);
|
1898 | }
|
1899 | return map;
|
1900 | }
|
1901 |
|
1902 |
|
1903 |
|
1904 |
|
1905 |
|
1906 |
|
1907 |
|
1908 |
|
1909 |
|
1910 |
|
1911 |
|
1912 |
|
1913 |
|
1914 |
|
1915 |
|
1916 |
|
1917 |
|
1918 |
|
1919 |
|
1920 |
|
1921 |
|
1922 |
|
1923 |
|
1924 |
|
1925 |
|
1926 |
|
1927 |
|
1928 |
|
1929 |
|
1930 |
|
1931 |
|
1932 |
|
1933 | class RouterState extends Tree {
|
1934 |
|
1935 | constructor(root,
|
1936 | /** The current snapshot of the router state */
|
1937 | snapshot) {
|
1938 | super(root);
|
1939 | this.snapshot = snapshot;
|
1940 | setRouterState(this, root);
|
1941 | }
|
1942 | toString() {
|
1943 | return this.snapshot.toString();
|
1944 | }
|
1945 | }
|
1946 | function createEmptyState(urlTree, rootComponent) {
|
1947 | const snapshot = createEmptyStateSnapshot(urlTree, rootComponent);
|
1948 | const emptyUrl = new BehaviorSubject([new UrlSegment('', {})]);
|
1949 | const emptyParams = new BehaviorSubject({});
|
1950 | const emptyData = new BehaviorSubject({});
|
1951 | const emptyQueryParams = new BehaviorSubject({});
|
1952 | const fragment = new BehaviorSubject('');
|
1953 | const activated = new ActivatedRoute(emptyUrl, emptyParams, emptyQueryParams, fragment, emptyData, PRIMARY_OUTLET, rootComponent, snapshot.root);
|
1954 | activated.snapshot = snapshot.root;
|
1955 | return new RouterState(new TreeNode(activated, []), snapshot);
|
1956 | }
|
1957 | function createEmptyStateSnapshot(urlTree, rootComponent) {
|
1958 | const emptyParams = {};
|
1959 | const emptyData = {};
|
1960 | const emptyQueryParams = {};
|
1961 | const fragment = '';
|
1962 | const activated = new ActivatedRouteSnapshot([], emptyParams, emptyQueryParams, fragment, emptyData, PRIMARY_OUTLET, rootComponent, null, urlTree.root, -1, {});
|
1963 | return new RouterStateSnapshot('', new TreeNode(activated, []));
|
1964 | }
|
1965 |
|
1966 |
|
1967 |
|
1968 |
|
1969 |
|
1970 |
|
1971 |
|
1972 |
|
1973 |
|
1974 |
|
1975 |
|
1976 |
|
1977 |
|
1978 |
|
1979 |
|
1980 |
|
1981 |
|
1982 |
|
1983 |
|
1984 | class ActivatedRoute {
|
1985 |
|
1986 | constructor(
|
1987 | /** An observable of the URL segments matched by this route. */
|
1988 | url,
|
1989 | /** An observable of the matrix parameters scoped to this route. */
|
1990 | params,
|
1991 | /** An observable of the query parameters shared by all the routes. */
|
1992 | queryParams,
|
1993 | /** An observable of the URL fragment shared by all the routes. */
|
1994 | fragment,
|
1995 | /** An observable of the static and resolved data of this route. */
|
1996 | data,
|
1997 | /** The outlet name of the route, a constant. */
|
1998 | outlet,
|
1999 | /** The component of the route, a constant. */
|
2000 | component, futureSnapshot) {
|
2001 | this.url = url;
|
2002 | this.params = params;
|
2003 | this.queryParams = queryParams;
|
2004 | this.fragment = fragment;
|
2005 | this.data = data;
|
2006 | this.outlet = outlet;
|
2007 | this.component = component;
|
2008 |
|
2009 | this.title = this.data?.pipe(map((d) => d[RouteTitleKey])) ?? of(undefined);
|
2010 | this._futureSnapshot = futureSnapshot;
|
2011 | }
|
2012 |
|
2013 | get routeConfig() {
|
2014 | return this._futureSnapshot.routeConfig;
|
2015 | }
|
2016 |
|
2017 | get root() {
|
2018 | return this._routerState.root;
|
2019 | }
|
2020 |
|
2021 | get parent() {
|
2022 | return this._routerState.parent(this);
|
2023 | }
|
2024 |
|
2025 | get firstChild() {
|
2026 | return this._routerState.firstChild(this);
|
2027 | }
|
2028 |
|
2029 | get children() {
|
2030 | return this._routerState.children(this);
|
2031 | }
|
2032 |
|
2033 | get pathFromRoot() {
|
2034 | return this._routerState.pathFromRoot(this);
|
2035 | }
|
2036 | |
2037 |
|
2038 |
|
2039 |
|
2040 |
|
2041 | get paramMap() {
|
2042 | if (!this._paramMap) {
|
2043 | this._paramMap = this.params.pipe(map((p) => convertToParamMap(p)));
|
2044 | }
|
2045 | return this._paramMap;
|
2046 | }
|
2047 | |
2048 |
|
2049 |
|
2050 |
|
2051 | get queryParamMap() {
|
2052 | if (!this._queryParamMap) {
|
2053 | this._queryParamMap =
|
2054 | this.queryParams.pipe(map((p) => convertToParamMap(p)));
|
2055 | }
|
2056 | return this._queryParamMap;
|
2057 | }
|
2058 | toString() {
|
2059 | return this.snapshot ? this.snapshot.toString() : `Future(${this._futureSnapshot})`;
|
2060 | }
|
2061 | }
|
2062 |
|
2063 |
|
2064 |
|
2065 |
|
2066 |
|
2067 | function inheritedParamsDataResolve(route, paramsInheritanceStrategy = 'emptyOnly') {
|
2068 | const pathFromRoot = route.pathFromRoot;
|
2069 | let inheritingStartingFrom = 0;
|
2070 | if (paramsInheritanceStrategy !== 'always') {
|
2071 | inheritingStartingFrom = pathFromRoot.length - 1;
|
2072 | while (inheritingStartingFrom >= 1) {
|
2073 | const current = pathFromRoot[inheritingStartingFrom];
|
2074 | const parent = pathFromRoot[inheritingStartingFrom - 1];
|
2075 |
|
2076 | if (current.routeConfig && current.routeConfig.path === '') {
|
2077 | inheritingStartingFrom--;
|
2078 |
|
2079 | }
|
2080 | else if (!parent.component) {
|
2081 | inheritingStartingFrom--;
|
2082 | }
|
2083 | else {
|
2084 | break;
|
2085 | }
|
2086 | }
|
2087 | }
|
2088 | return flattenInherited(pathFromRoot.slice(inheritingStartingFrom));
|
2089 | }
|
2090 |
|
2091 | function flattenInherited(pathFromRoot) {
|
2092 | return pathFromRoot.reduce((res, curr) => {
|
2093 | const params = { ...res.params, ...curr.params };
|
2094 | const data = { ...res.data, ...curr.data };
|
2095 | const resolve = { ...curr.data, ...res.resolve, ...curr.routeConfig?.data, ...curr._resolvedData };
|
2096 | return { params, data, resolve };
|
2097 | }, { params: {}, data: {}, resolve: {} });
|
2098 | }
|
2099 |
|
2100 |
|
2101 |
|
2102 |
|
2103 |
|
2104 |
|
2105 |
|
2106 |
|
2107 |
|
2108 |
|
2109 |
|
2110 |
|
2111 |
|
2112 |
|
2113 |
|
2114 |
|
2115 |
|
2116 |
|
2117 |
|
2118 |
|
2119 |
|
2120 |
|
2121 |
|
2122 | class ActivatedRouteSnapshot {
|
2123 |
|
2124 | get title() {
|
2125 |
|
2126 |
|
2127 | return this.data?.[RouteTitleKey];
|
2128 | }
|
2129 |
|
2130 | constructor(
|
2131 | /** The URL segments matched by this route */
|
2132 | url,
|
2133 | /**
|
2134 | * The matrix parameters scoped to this route.
|
2135 | *
|
2136 | * You can compute all params (or data) in the router state or to get params outside
|
2137 | * of an activated component by traversing the `RouterState` tree as in the following
|
2138 | * example:
|
2139 | * ```
|
2140 | * collectRouteParams(router: Router) {
|
2141 | * let params = {};
|
2142 | * let stack: ActivatedRouteSnapshot[] = [router.routerState.snapshot.root];
|
2143 | * while (stack.length > 0) {
|
2144 | * const route = stack.pop()!;
|
2145 | * params = {...params, ...route.params};
|
2146 | * stack.push(...route.children);
|
2147 | * }
|
2148 | * return params;
|
2149 | * }
|
2150 | * ```
|
2151 | */
|
2152 | params,
|
2153 | /** The query parameters shared by all the routes */
|
2154 | queryParams,
|
2155 | /** The URL fragment shared by all the routes */
|
2156 | fragment,
|
2157 | /** The static and resolved data of this route */
|
2158 | data,
|
2159 | /** The outlet name of the route */
|
2160 | outlet,
|
2161 | /** The component of the route */
|
2162 | component, routeConfig, urlSegment, lastPathIndex, resolve) {
|
2163 | this.url = url;
|
2164 | this.params = params;
|
2165 | this.queryParams = queryParams;
|
2166 | this.fragment = fragment;
|
2167 | this.data = data;
|
2168 | this.outlet = outlet;
|
2169 | this.component = component;
|
2170 | this.routeConfig = routeConfig;
|
2171 | this._urlSegment = urlSegment;
|
2172 | this._lastPathIndex = lastPathIndex;
|
2173 | this._resolve = resolve;
|
2174 | }
|
2175 | /** The root of the router state */
|
2176 | get root() {
|
2177 | return this._routerState.root;
|
2178 | }
|
2179 | /** The parent of this route in the router state tree */
|
2180 | get parent() {
|
2181 | return this._routerState.parent(this);
|
2182 | }
|
2183 | /** The first child of this route in the router state tree */
|
2184 | get firstChild() {
|
2185 | return this._routerState.firstChild(this);
|
2186 | }
|
2187 | /** The children of this route in the router state tree */
|
2188 | get children() {
|
2189 | return this._routerState.children(this);
|
2190 | }
|
2191 | /** The path from the root of the router state tree to this route */
|
2192 | get pathFromRoot() {
|
2193 | return this._routerState.pathFromRoot(this);
|
2194 | }
|
2195 | get paramMap() {
|
2196 | if (!this._paramMap) {
|
2197 | this._paramMap = convertToParamMap(this.params);
|
2198 | }
|
2199 | return this._paramMap;
|
2200 | }
|
2201 | get queryParamMap() {
|
2202 | if (!this._queryParamMap) {
|
2203 | this._queryParamMap = convertToParamMap(this.queryParams);
|
2204 | }
|
2205 | return this._queryParamMap;
|
2206 | }
|
2207 | toString() {
|
2208 | const url = this.url.map(segment => segment.toString()).join('/');
|
2209 | const matched = this.routeConfig ? this.routeConfig.path : '';
|
2210 | return `Route(url:'${url}', path:'${matched}')`;
|
2211 | }
|
2212 | }
|
2213 | /**
|
2214 | * @description
|
2215 | *
|
2216 | * Represents the state of the router at a moment in time.
|
2217 | *
|
2218 | * This is a tree of activated route snapshots. Every node in this tree knows about
|
2219 | * the "consumed" URL segments, the extracted parameters, and the resolved data.
|
2220 | *
|
2221 | * The following example shows how a component is initialized with information
|
2222 | * from the snapshot of the root node's state at the time of creation.
|
2223 | *
|
2224 | * ```
|
2225 | * @Component({templateUrl:'template.html'})
|
2226 | * class MyComponent {
|
2227 | * constructor(router: Router) {
|
2228 | * const state: RouterState = router.routerState;
|
2229 | * const snapshot: RouterStateSnapshot = state.snapshot;
|
2230 | * const root: ActivatedRouteSnapshot = snapshot.root;
|
2231 | * const child = root.firstChild;
|
2232 | * const id: Observable<string> = child.params.map(p => p.id);
|
2233 | *
|
2234 | * }
|
2235 | * }
|
2236 | * ```
|
2237 | *
|
2238 | * @publicApi
|
2239 | */
|
2240 | class RouterStateSnapshot extends Tree {
|
2241 | /** @internal */
|
2242 | constructor(
|
2243 | /** The url from which this snapshot was created */
|
2244 | url, root) {
|
2245 | super(root);
|
2246 | this.url = url;
|
2247 | setRouterState(this, root);
|
2248 | }
|
2249 | toString() {
|
2250 | return serializeNode(this._root);
|
2251 | }
|
2252 | }
|
2253 | function setRouterState(state, node) {
|
2254 | node.value._routerState = state;
|
2255 | node.children.forEach(c => setRouterState(state, c));
|
2256 | }
|
2257 | function serializeNode(node) {
|
2258 | const c = node.children.length > 0 ? ` { ${node.children.map(serializeNode).join(', ')} } ` : '';
|
2259 | return `${node.value}${c}`;
|
2260 | }
|
2261 | /**
|
2262 | * The expectation is that the activate route is created with the right set of parameters.
|
2263 | * So we push new values into the observables only when they are not the initial values.
|
2264 | * And we detect that by checking if the snapshot field is set.
|
2265 | */
|
2266 | function advanceActivatedRoute(route) {
|
2267 | if (route.snapshot) {
|
2268 | const currentSnapshot = route.snapshot;
|
2269 | const nextSnapshot = route._futureSnapshot;
|
2270 | route.snapshot = nextSnapshot;
|
2271 | if (!shallowEqual(currentSnapshot.queryParams, nextSnapshot.queryParams)) {
|
2272 | route.queryParams.next(nextSnapshot.queryParams);
|
2273 | }
|
2274 | if (currentSnapshot.fragment !== nextSnapshot.fragment) {
|
2275 | route.fragment.next(nextSnapshot.fragment);
|
2276 | }
|
2277 | if (!shallowEqual(currentSnapshot.params, nextSnapshot.params)) {
|
2278 | route.params.next(nextSnapshot.params);
|
2279 | }
|
2280 | if (!shallowEqualArrays(currentSnapshot.url, nextSnapshot.url)) {
|
2281 | route.url.next(nextSnapshot.url);
|
2282 | }
|
2283 | if (!shallowEqual(currentSnapshot.data, nextSnapshot.data)) {
|
2284 | route.data.next(nextSnapshot.data);
|
2285 | }
|
2286 | }
|
2287 | else {
|
2288 | route.snapshot = route._futureSnapshot;
|
2289 | // this is for resolved data
|
2290 | route.data.next(route._futureSnapshot.data);
|
2291 | }
|
2292 | }
|
2293 | function equalParamsAndUrlSegments(a, b) {
|
2294 | const equalUrlParams = shallowEqual(a.params, b.params) && equalSegments(a.url, b.url);
|
2295 | const parentsMismatch = !a.parent !== !b.parent;
|
2296 | return equalUrlParams && !parentsMismatch &&
|
2297 | (!a.parent || equalParamsAndUrlSegments(a.parent, b.parent));
|
2298 | }
|
2299 |
|
2300 | function createRouterState(routeReuseStrategy, curr, prevState) {
|
2301 | const root = createNode(routeReuseStrategy, curr._root, prevState ? prevState._root : undefined);
|
2302 | return new RouterState(root, curr);
|
2303 | }
|
2304 | function createNode(routeReuseStrategy, curr, prevState) {
|
2305 | // reuse an activated route that is currently displayed on the screen
|
2306 | if (prevState && routeReuseStrategy.shouldReuseRoute(curr.value, prevState.value.snapshot)) {
|
2307 | const value = prevState.value;
|
2308 | value._futureSnapshot = curr.value;
|
2309 | const children = createOrReuseChildren(routeReuseStrategy, curr, prevState);
|
2310 | return new TreeNode(value, children);
|
2311 | }
|
2312 | else {
|
2313 | if (routeReuseStrategy.shouldAttach(curr.value)) {
|
2314 | // retrieve an activated route that is used to be displayed, but is not currently displayed
|
2315 | const detachedRouteHandle = routeReuseStrategy.retrieve(curr.value);
|
2316 | if (detachedRouteHandle !== null) {
|
2317 | const tree = detachedRouteHandle.route;
|
2318 | tree.value._futureSnapshot = curr.value;
|
2319 | tree.children = curr.children.map(c => createNode(routeReuseStrategy, c));
|
2320 | return tree;
|
2321 | }
|
2322 | }
|
2323 | const value = createActivatedRoute(curr.value);
|
2324 | const children = curr.children.map(c => createNode(routeReuseStrategy, c));
|
2325 | return new TreeNode(value, children);
|
2326 | }
|
2327 | }
|
2328 | function createOrReuseChildren(routeReuseStrategy, curr, prevState) {
|
2329 | return curr.children.map(child => {
|
2330 | for (const p of prevState.children) {
|
2331 | if (routeReuseStrategy.shouldReuseRoute(child.value, p.value.snapshot)) {
|
2332 | return createNode(routeReuseStrategy, child, p);
|
2333 | }
|
2334 | }
|
2335 | return createNode(routeReuseStrategy, child);
|
2336 | });
|
2337 | }
|
2338 | function createActivatedRoute(c) {
|
2339 | return new ActivatedRoute(new BehaviorSubject(c.url), new BehaviorSubject(c.params), new BehaviorSubject(c.queryParams), new BehaviorSubject(c.fragment), new BehaviorSubject(c.data), c.outlet, c.component, c);
|
2340 | }
|
2341 |
|
2342 | const NAVIGATION_CANCELING_ERROR = 'ngNavigationCancelingError';
|
2343 | function redirectingNavigationError(urlSerializer, redirect) {
|
2344 | const { redirectTo, navigationBehaviorOptions } = isUrlTree(redirect) ? { redirectTo: redirect, navigationBehaviorOptions: undefined } : redirect;
|
2345 | const error = navigationCancelingError(ngDevMode && `Redirecting to "${urlSerializer.serialize(redirectTo)}"`, 0 /* NavigationCancellationCode.Redirect */, redirect);
|
2346 | error.url = redirectTo;
|
2347 | error.navigationBehaviorOptions = navigationBehaviorOptions;
|
2348 | return error;
|
2349 | }
|
2350 | function navigationCancelingError(message, code, redirectUrl) {
|
2351 | const error = new Error('NavigationCancelingError: ' + (message || ''));
|
2352 | error[NAVIGATION_CANCELING_ERROR] = true;
|
2353 | error.cancellationCode = code;
|
2354 | if (redirectUrl) {
|
2355 | error.url = redirectUrl;
|
2356 | }
|
2357 | return error;
|
2358 | }
|
2359 | function isRedirectingNavigationCancelingError$1(error) {
|
2360 | return isNavigationCancelingError$1(error) && isUrlTree(error.url);
|
2361 | }
|
2362 | function isNavigationCancelingError$1(error) {
|
2363 | return error && error[NAVIGATION_CANCELING_ERROR];
|
2364 | }
|
2365 |
|
2366 | /**
|
2367 | * Store contextual information about a `RouterOutlet`
|
2368 | *
|
2369 | * @publicApi
|
2370 | */
|
2371 | class OutletContext {
|
2372 | constructor() {
|
2373 | this.outlet = null;
|
2374 | this.route = null;
|
2375 | /**
|
2376 | * @deprecated Passing a resolver to retrieve a component factory is not required and is
|
2377 | * deprecated since v14.
|
2378 | */
|
2379 | this.resolver = null;
|
2380 | this.injector = null;
|
2381 | this.children = new ChildrenOutletContexts();
|
2382 | this.attachRef = null;
|
2383 | }
|
2384 | }
|
2385 | /**
|
2386 | * Store contextual information about the children (= nested) `RouterOutlet`
|
2387 | *
|
2388 | * @publicApi
|
2389 | */
|
2390 | class ChildrenOutletContexts {
|
2391 | constructor() {
|
2392 | // contexts for child outlets, by name.
|
2393 | this.contexts = new Map();
|
2394 | }
|
2395 | /** Called when a `RouterOutlet` directive is instantiated */
|
2396 | onChildOutletCreated(childName, outlet) {
|
2397 | const context = this.getOrCreateContext(childName);
|
2398 | context.outlet = outlet;
|
2399 | this.contexts.set(childName, context);
|
2400 | }
|
2401 | /**
|
2402 | * Called when a `RouterOutlet` directive is destroyed.
|
2403 | * We need to keep the context as the outlet could be destroyed inside a NgIf and might be
|
2404 | * re-created later.
|
2405 | */
|
2406 | onChildOutletDestroyed(childName) {
|
2407 | const context = this.getContext(childName);
|
2408 | if (context) {
|
2409 | context.outlet = null;
|
2410 | context.attachRef = null;
|
2411 | }
|
2412 | }
|
2413 | /**
|
2414 | * Called when the corresponding route is deactivated during navigation.
|
2415 | * Because the component get destroyed, all children outlet are destroyed.
|
2416 | */
|
2417 | onOutletDeactivated() {
|
2418 | const contexts = this.contexts;
|
2419 | this.contexts = new Map();
|
2420 | return contexts;
|
2421 | }
|
2422 | onOutletReAttached(contexts) {
|
2423 | this.contexts = contexts;
|
2424 | }
|
2425 | getOrCreateContext(childName) {
|
2426 | let context = this.getContext(childName);
|
2427 | if (!context) {
|
2428 | context = new OutletContext();
|
2429 | this.contexts.set(childName, context);
|
2430 | }
|
2431 | return context;
|
2432 | }
|
2433 | getContext(childName) {
|
2434 | return this.contexts.get(childName) || null;
|
2435 | }
|
2436 | }
|
2437 | ChildrenOutletContexts.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: ChildrenOutletContexts, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
2438 | ChildrenOutletContexts.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: ChildrenOutletContexts, providedIn: 'root' });
|
2439 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: ChildrenOutletContexts, decorators: [{
|
2440 | type: Injectable,
|
2441 | args: [{ providedIn: 'root' }]
|
2442 | }] });
|
2443 |
|
2444 | const NG_DEV_MODE$8 = typeof ngDevMode === 'undefined' || ngDevMode;
|
2445 | /**
|
2446 | * @description
|
2447 | *
|
2448 | * Acts as a placeholder that Angular dynamically fills based on the current router state.
|
2449 | *
|
2450 | * Each outlet can have a unique name, determined by the optional `name` attribute.
|
2451 | * The name cannot be set or changed dynamically. If not set, default value is "primary".
|
2452 | *
|
2453 | * ```
|
2454 | * <router-outlet></router-outlet>
|
2455 | * <router-outlet name='left'></router-outlet>
|
2456 | * <router-outlet name='right'></router-outlet>
|
2457 | * ```
|
2458 | *
|
2459 | * Named outlets can be the targets of secondary routes.
|
2460 | * The `Route` object for a secondary route has an `outlet` property to identify the target outlet:
|
2461 | *
|
2462 | * `{path: <base-path>, component: <component>, outlet: <target_outlet_name>}`
|
2463 | *
|
2464 | * Using named outlets and secondary routes, you can target multiple outlets in
|
2465 | * the same `RouterLink` directive.
|
2466 | *
|
2467 | * The router keeps track of separate branches in a navigation tree for each named outlet and
|
2468 | * generates a representation of that tree in the URL.
|
2469 | * The URL for a secondary route uses the following syntax to specify both the primary and secondary
|
2470 | * routes at the same time:
|
2471 | *
|
2472 | * `http://base-path/primary-route-path(outlet-name:route-path)`
|
2473 | *
|
2474 | * A router outlet emits an activate event when a new component is instantiated,
|
2475 | * deactivate event when a component is destroyed.
|
2476 | * An attached event emits when the `RouteReuseStrategy` instructs the outlet to reattach the
|
2477 | * subtree, and the detached event emits when the `RouteReuseStrategy` instructs the outlet to
|
2478 | * detach the subtree.
|
2479 | *
|
2480 | * ```
|
2481 | * <router-outlet
|
2482 | * (activate)='onActivate($event)'
|
2483 | * (deactivate)='onDeactivate($event)'
|
2484 | * (attach)='onAttach($event)'
|
2485 | * (detach)='onDetach($event)'></router-outlet>
|
2486 | * ```
|
2487 | *
|
2488 | * @see [Routing tutorial](guide/router-tutorial-toh#named-outlets "Example of a named
|
2489 | * outlet and secondary route configuration").
|
2490 | * @see `RouterLink`
|
2491 | * @see `Route`
|
2492 | * @ngModule RouterModule
|
2493 | *
|
2494 | * @publicApi
|
2495 | */
|
2496 | class RouterOutlet {
|
2497 | constructor() {
|
2498 | this.activated = null;
|
2499 | this._activatedRoute = null;
|
2500 | /**
|
2501 | * The name of the outlet
|
2502 | *
|
2503 | * @see [named outlets](guide/router-tutorial-toh#displaying-multiple-routes-in-named-outlets)
|
2504 | */
|
2505 | this.name = PRIMARY_OUTLET;
|
2506 | this.activateEvents = new EventEmitter();
|
2507 | this.deactivateEvents = new EventEmitter();
|
2508 | /**
|
2509 | * Emits an attached component instance when the `RouteReuseStrategy` instructs to re-attach a
|
2510 | * previously detached subtree.
|
2511 | **/
|
2512 | this.attachEvents = new EventEmitter();
|
2513 | /**
|
2514 | * Emits a detached component instance when the `RouteReuseStrategy` instructs to detach the
|
2515 | * subtree.
|
2516 | */
|
2517 | this.detachEvents = new EventEmitter();
|
2518 | this.parentContexts = inject(ChildrenOutletContexts);
|
2519 | this.location = inject(ViewContainerRef);
|
2520 | this.changeDetector = inject(ChangeDetectorRef);
|
2521 | this.environmentInjector = inject(EnvironmentInjector);
|
2522 | }
|
2523 | /** @nodoc */
|
2524 | ngOnChanges(changes) {
|
2525 | if (changes['name']) {
|
2526 | const { firstChange, previousValue } = changes['name'];
|
2527 | if (firstChange) {
|
2528 | // The first change is handled by ngOnInit. Because ngOnChanges doesn't get called when no
|
2529 | // input is set at all, we need to centrally handle the first change there.
|
2530 | return;
|
2531 | }
|
2532 | // unregister with the old name
|
2533 | if (this.isTrackedInParentContexts(previousValue)) {
|
2534 | this.deactivate();
|
2535 | this.parentContexts.onChildOutletDestroyed(previousValue);
|
2536 | }
|
2537 | // register the new name
|
2538 | this.initializeOutletWithName();
|
2539 | }
|
2540 | }
|
2541 | /** @nodoc */
|
2542 | ngOnDestroy() {
|
2543 | // Ensure that the registered outlet is this one before removing it on the context.
|
2544 | if (this.isTrackedInParentContexts(this.name)) {
|
2545 | this.parentContexts.onChildOutletDestroyed(this.name);
|
2546 | }
|
2547 | }
|
2548 | isTrackedInParentContexts(outletName) {
|
2549 | return this.parentContexts.getContext(outletName)?.outlet === this;
|
2550 | }
|
2551 | /** @nodoc */
|
2552 | ngOnInit() {
|
2553 | this.initializeOutletWithName();
|
2554 | }
|
2555 | initializeOutletWithName() {
|
2556 | this.parentContexts.onChildOutletCreated(this.name, this);
|
2557 | if (this.activated) {
|
2558 | return;
|
2559 | }
|
2560 | // If the outlet was not instantiated at the time the route got activated we need to populate
|
2561 | // the outlet when it is initialized (ie inside a NgIf)
|
2562 | const context = this.parentContexts.getContext(this.name);
|
2563 | if (context?.route) {
|
2564 | if (context.attachRef) {
|
2565 | // `attachRef` is populated when there is an existing component to mount
|
2566 | this.attach(context.attachRef, context.route);
|
2567 | }
|
2568 | else {
|
2569 | // otherwise the component defined in the configuration is created
|
2570 | this.activateWith(context.route, context.injector);
|
2571 | }
|
2572 | }
|
2573 | }
|
2574 | get isActivated() {
|
2575 | return !!this.activated;
|
2576 | }
|
2577 | /**
|
2578 | * @returns The currently activated component instance.
|
2579 | * @throws An error if the outlet is not activated.
|
2580 | */
|
2581 | get component() {
|
2582 | if (!this.activated)
|
2583 | throw new ɵRuntimeError(4012 /* RuntimeErrorCode.OUTLET_NOT_ACTIVATED */, NG_DEV_MODE$8 && 'Outlet is not activated');
|
2584 | return this.activated.instance;
|
2585 | }
|
2586 | get activatedRoute() {
|
2587 | if (!this.activated)
|
2588 | throw new ɵRuntimeError(4012 /* RuntimeErrorCode.OUTLET_NOT_ACTIVATED */, NG_DEV_MODE$8 && 'Outlet is not activated');
|
2589 | return this._activatedRoute;
|
2590 | }
|
2591 | get activatedRouteData() {
|
2592 | if (this._activatedRoute) {
|
2593 | return this._activatedRoute.snapshot.data;
|
2594 | }
|
2595 | return {};
|
2596 | }
|
2597 | /**
|
2598 | * Called when the `RouteReuseStrategy` instructs to detach the subtree
|
2599 | */
|
2600 | detach() {
|
2601 | if (!this.activated)
|
2602 | throw new ɵRuntimeError(4012 /* RuntimeErrorCode.OUTLET_NOT_ACTIVATED */, NG_DEV_MODE$8 && 'Outlet is not activated');
|
2603 | this.location.detach();
|
2604 | const cmp = this.activated;
|
2605 | this.activated = null;
|
2606 | this._activatedRoute = null;
|
2607 | this.detachEvents.emit(cmp.instance);
|
2608 | return cmp;
|
2609 | }
|
2610 | /**
|
2611 | * Called when the `RouteReuseStrategy` instructs to re-attach a previously detached subtree
|
2612 | */
|
2613 | attach(ref, activatedRoute) {
|
2614 | this.activated = ref;
|
2615 | this._activatedRoute = activatedRoute;
|
2616 | this.location.insert(ref.hostView);
|
2617 | this.attachEvents.emit(ref.instance);
|
2618 | }
|
2619 | deactivate() {
|
2620 | if (this.activated) {
|
2621 | const c = this.component;
|
2622 | this.activated.destroy();
|
2623 | this.activated = null;
|
2624 | this._activatedRoute = null;
|
2625 | this.deactivateEvents.emit(c);
|
2626 | }
|
2627 | }
|
2628 | activateWith(activatedRoute, resolverOrInjector) {
|
2629 | if (this.isActivated) {
|
2630 | throw new ɵRuntimeError(4013 /* RuntimeErrorCode.OUTLET_ALREADY_ACTIVATED */, NG_DEV_MODE$8 && 'Cannot activate an already activated outlet');
|
2631 | }
|
2632 | this._activatedRoute = activatedRoute;
|
2633 | const location = this.location;
|
2634 | const snapshot = activatedRoute.snapshot;
|
2635 | const component = snapshot.component;
|
2636 | const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
|
2637 | const injector = new OutletInjector(activatedRoute, childContexts, location.injector);
|
2638 | if (resolverOrInjector && isComponentFactoryResolver(resolverOrInjector)) {
|
2639 | const factory = resolverOrInjector.resolveComponentFactory(component);
|
2640 | this.activated = location.createComponent(factory, location.length, injector);
|
2641 | }
|
2642 | else {
|
2643 | const environmentInjector = resolverOrInjector ?? this.environmentInjector;
|
2644 | this.activated = location.createComponent(component, { index: location.length, injector, environmentInjector });
|
2645 | }
|
2646 | // Calling `markForCheck` to make sure we will run the change detection when the
|
2647 | // `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
|
2648 | this.changeDetector.markForCheck();
|
2649 | this.activateEvents.emit(this.activated.instance);
|
2650 | }
|
2651 | }
|
2652 | RouterOutlet.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterOutlet, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
2653 | RouterOutlet.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.3", type: RouterOutlet, isStandalone: true, selector: "router-outlet", inputs: { name: "name" }, outputs: { activateEvents: "activate", deactivateEvents: "deactivate", attachEvents: "attach", detachEvents: "detach" }, exportAs: ["outlet"], usesOnChanges: true, ngImport: i0 });
|
2654 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterOutlet, decorators: [{
|
2655 | type: Directive,
|
2656 | args: [{
|
2657 | selector: 'router-outlet',
|
2658 | exportAs: 'outlet',
|
2659 | standalone: true,
|
2660 | }]
|
2661 | }], propDecorators: { name: [{
|
2662 | type: Input
|
2663 | }], activateEvents: [{
|
2664 | type: Output,
|
2665 | args: ['activate']
|
2666 | }], deactivateEvents: [{
|
2667 | type: Output,
|
2668 | args: ['deactivate']
|
2669 | }], attachEvents: [{
|
2670 | type: Output,
|
2671 | args: ['attach']
|
2672 | }], detachEvents: [{
|
2673 | type: Output,
|
2674 | args: ['detach']
|
2675 | }] } });
|
2676 | class OutletInjector {
|
2677 | constructor(route, childContexts, parent) {
|
2678 | this.route = route;
|
2679 | this.childContexts = childContexts;
|
2680 | this.parent = parent;
|
2681 | }
|
2682 | get(token, notFoundValue) {
|
2683 | if (token === ActivatedRoute) {
|
2684 | return this.route;
|
2685 | }
|
2686 | if (token === ChildrenOutletContexts) {
|
2687 | return this.childContexts;
|
2688 | }
|
2689 | return this.parent.get(token, notFoundValue);
|
2690 | }
|
2691 | }
|
2692 | function isComponentFactoryResolver(item) {
|
2693 | return !!item.resolveComponentFactory;
|
2694 | }
|
2695 |
|
2696 | /**
|
2697 | * This component is used internally within the router to be a placeholder when an empty
|
2698 | * router-outlet is needed. For example, with a config such as:
|
2699 | *
|
2700 | * `{path: 'parent', outlet: 'nav', children: [...]}`
|
2701 | *
|
2702 | * In order to render, there needs to be a component on this config, which will default
|
2703 | * to this `EmptyOutletComponent`.
|
2704 | */
|
2705 | class ɵEmptyOutletComponent {
|
2706 | }
|
2707 | ɵEmptyOutletComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: ɵEmptyOutletComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
2708 | ɵEmptyOutletComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.3", type: ɵEmptyOutletComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: `<router-outlet></router-outlet>`, isInline: true, dependencies: [{ kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }] });
|
2709 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: ɵEmptyOutletComponent, decorators: [{
|
2710 | type: Component,
|
2711 | args: [{
|
2712 | template: `<router-outlet></router-outlet>`,
|
2713 | imports: [RouterOutlet],
|
2714 | standalone: true,
|
2715 | }]
|
2716 | }] });
|
2717 |
|
2718 | /**
|
2719 | * Creates an `EnvironmentInjector` if the `Route` has providers and one does not already exist
|
2720 | * and returns the injector. Otherwise, if the `Route` does not have `providers`, returns the
|
2721 | * `currentInjector`.
|
2722 | *
|
2723 | * @param route The route that might have providers
|
2724 | * @param currentInjector The parent injector of the `Route`
|
2725 | */
|
2726 | function getOrCreateRouteInjectorIfNeeded(route, currentInjector) {
|
2727 | if (route.providers && !route._injector) {
|
2728 | route._injector =
|
2729 | createEnvironmentInjector(route.providers, currentInjector, `Route: ${route.path}`);
|
2730 | }
|
2731 | return route._injector ?? currentInjector;
|
2732 | }
|
2733 | function getLoadedRoutes(route) {
|
2734 | return route._loadedRoutes;
|
2735 | }
|
2736 | function getLoadedInjector(route) {
|
2737 | return route._loadedInjector;
|
2738 | }
|
2739 | function getLoadedComponent(route) {
|
2740 | return route._loadedComponent;
|
2741 | }
|
2742 | function getProvidersInjector(route) {
|
2743 | return route._injector;
|
2744 | }
|
2745 | function validateConfig(config, parentPath = '', requireStandaloneComponents = false) {
|
2746 | // forEach doesn't iterate undefined values
|
2747 | for (let i = 0; i < config.length; i++) {
|
2748 | const route = config[i];
|
2749 | const fullPath = getFullPath(parentPath, route);
|
2750 | validateNode(route, fullPath, requireStandaloneComponents);
|
2751 | }
|
2752 | }
|
2753 | function assertStandalone(fullPath, component) {
|
2754 | if (component && ɵisNgModule(component)) {
|
2755 | throw new ɵRuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}'. You are using 'loadComponent' with a module, ` +
|
2756 | `but it must be used with standalone components. Use 'loadChildren' instead.`);
|
2757 | }
|
2758 | else if (component && !isStandalone(component)) {
|
2759 | throw new ɵRuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}'. The component must be standalone.`);
|
2760 | }
|
2761 | }
|
2762 | function validateNode(route, fullPath, requireStandaloneComponents) {
|
2763 | if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
2764 | if (!route) {
|
2765 | throw new ɵRuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `
|
2766 | Invalid configuration of route '${fullPath}': Encountered undefined route.
|
2767 | The reason might be an extra comma.
|
2768 |
|
2769 | Example:
|
2770 | const routes: Routes = [
|
2771 | { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
|
2772 | { path: 'dashboard', component: DashboardComponent },, << two commas
|
2773 | { path: 'detail/:id', component: HeroDetailComponent }
|
2774 | ];
|
2775 | `);
|
2776 | }
|
2777 | if (Array.isArray(route)) {
|
2778 | throw new ɵRuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': Array cannot be specified`);
|
2779 | }
|
2780 | if (!route.redirectTo && !route.component && !route.loadComponent && !route.children &&
|
2781 | !route.loadChildren && (route.outlet && route.outlet !== PRIMARY_OUTLET)) {
|
2782 | throw new ɵRuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': a componentless route without children or loadChildren cannot have a named outlet set`);
|
2783 | }
|
2784 | if (route.redirectTo && route.children) {
|
2785 | throw new ɵRuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': redirectTo and children cannot be used together`);
|
2786 | }
|
2787 | if (route.redirectTo && route.loadChildren) {
|
2788 | throw new ɵRuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': redirectTo and loadChildren cannot be used together`);
|
2789 | }
|
2790 | if (route.children && route.loadChildren) {
|
2791 | throw new ɵRuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': children and loadChildren cannot be used together`);
|
2792 | }
|
2793 | if (route.redirectTo && (route.component || route.loadComponent)) {
|
2794 | throw new ɵRuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': redirectTo and component/loadComponent cannot be used together`);
|
2795 | }
|
2796 | if (route.component && route.loadComponent) {
|
2797 | throw new ɵRuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': component and loadComponent cannot be used together`);
|
2798 | }
|
2799 | if (route.redirectTo && route.canActivate) {
|
2800 | throw new ɵRuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': redirectTo and canActivate cannot be used together. Redirects happen before activation ` +
|
2801 | `so canActivate will never be executed.`);
|
2802 | }
|
2803 | if (route.path && route.matcher) {
|
2804 | throw new ɵRuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': path and matcher cannot be used together`);
|
2805 | }
|
2806 | if (route.redirectTo === void 0 && !route.component && !route.loadComponent &&
|
2807 | !route.children && !route.loadChildren) {
|
2808 | throw new ɵRuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}'. One of the following must be provided: component, loadComponent, redirectTo, children or loadChildren`);
|
2809 | }
|
2810 | if (route.path === void 0 && route.matcher === void 0) {
|
2811 | throw new ɵRuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': routes must have either a path or a matcher specified`);
|
2812 | }
|
2813 | if (typeof route.path === 'string' && route.path.charAt(0) === '/') {
|
2814 | throw new ɵRuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '${fullPath}': path cannot start with a slash`);
|
2815 | }
|
2816 | if (route.path === '' && route.redirectTo !== void 0 && route.pathMatch === void 0) {
|
2817 | const exp = `The default value of 'pathMatch' is 'prefix', but often the intent is to use 'full'.`;
|
2818 | throw new ɵRuntimeError(4014 /* RuntimeErrorCode.INVALID_ROUTE_CONFIG */, `Invalid configuration of route '{path: "${fullPath}", redirectTo: "${route.redirectTo}"}': please provide 'pathMatch'. ${exp}`);
|
2819 | }
|
2820 | if (requireStandaloneComponents) {
|
2821 | assertStandalone(fullPath, route.component);
|
2822 | }
|
2823 | }
|
2824 | if (route.children) {
|
2825 | validateConfig(route.children, fullPath, requireStandaloneComponents);
|
2826 | }
|
2827 | }
|
2828 | function getFullPath(parentPath, currentRoute) {
|
2829 | if (!currentRoute) {
|
2830 | return parentPath;
|
2831 | }
|
2832 | if (!parentPath && !currentRoute.path) {
|
2833 | return '';
|
2834 | }
|
2835 | else if (parentPath && !currentRoute.path) {
|
2836 | return `${parentPath}/`;
|
2837 | }
|
2838 | else if (!parentPath && currentRoute.path) {
|
2839 | return currentRoute.path;
|
2840 | }
|
2841 | else {
|
2842 | return `${parentPath}/${currentRoute.path}`;
|
2843 | }
|
2844 | }
|
2845 | /**
|
2846 | * Makes a copy of the config and adds any default required properties.
|
2847 | */
|
2848 | function standardizeConfig(r) {
|
2849 | const children = r.children && r.children.map(standardizeConfig);
|
2850 | const c = children ? { ...r, children } : { ...r };
|
2851 | if ((!c.component && !c.loadComponent) && (children || c.loadChildren) &&
|
2852 | (c.outlet && c.outlet !== PRIMARY_OUTLET)) {
|
2853 | c.component = ɵEmptyOutletComponent;
|
2854 | }
|
2855 | return c;
|
2856 | }
|
2857 | /** Returns the `route.outlet` or PRIMARY_OUTLET if none exists. */
|
2858 | function getOutlet(route) {
|
2859 | return route.outlet || PRIMARY_OUTLET;
|
2860 | }
|
2861 | /**
|
2862 | * Sorts the `routes` such that the ones with an outlet matching `outletName` come first.
|
2863 | * The order of the configs is otherwise preserved.
|
2864 | */
|
2865 | function sortByMatchingOutlets(routes, outletName) {
|
2866 | const sortedConfig = routes.filter(r => getOutlet(r) === outletName);
|
2867 | sortedConfig.push(...routes.filter(r => getOutlet(r) !== outletName));
|
2868 | return sortedConfig;
|
2869 | }
|
2870 | /**
|
2871 | * Gets the first injector in the snapshot's parent tree.
|
2872 | *
|
2873 | * If the `Route` has a static list of providers, the returned injector will be the one created from
|
2874 | * those. If it does not exist, the returned injector may come from the parents, which may be from a
|
2875 | * loaded config or their static providers.
|
2876 | *
|
2877 | * Returns `null` if there is neither this nor any parents have a stored injector.
|
2878 | *
|
2879 | * Generally used for retrieving the injector to use for getting tokens for guards/resolvers and
|
2880 | * also used for getting the correct injector to use for creating components.
|
2881 | */
|
2882 | function getClosestRouteInjector(snapshot) {
|
2883 | if (!snapshot)
|
2884 | return null;
|
2885 | // If the current route has its own injector, which is created from the static providers on the
|
2886 | // route itself, we should use that. Otherwise, we start at the parent since we do not want to
|
2887 | // include the lazy loaded injector from this route.
|
2888 | if (snapshot.routeConfig?._injector) {
|
2889 | return snapshot.routeConfig._injector;
|
2890 | }
|
2891 | for (let s = snapshot.parent; s; s = s.parent) {
|
2892 | const route = s.routeConfig;
|
2893 | // Note that the order here is important. `_loadedInjector` stored on the route with
|
2894 | // `loadChildren: () => NgModule` so it applies to child routes with priority. The `_injector`
|
2895 | // is created from the static providers on that parent route, so it applies to the children as
|
2896 | // well, but only if there is no lazy loaded NgModuleRef injector.
|
2897 | if (route?._loadedInjector)
|
2898 | return route._loadedInjector;
|
2899 | if (route?._injector)
|
2900 | return route._injector;
|
2901 | }
|
2902 | return null;
|
2903 | }
|
2904 |
|
2905 | const activateRoutes = (rootContexts, routeReuseStrategy, forwardEvent) => map(t => {
|
2906 | new ActivateRoutes(routeReuseStrategy, t.targetRouterState, t.currentRouterState, forwardEvent)
|
2907 | .activate(rootContexts);
|
2908 | return t;
|
2909 | });
|
2910 | class ActivateRoutes {
|
2911 | constructor(routeReuseStrategy, futureState, currState, forwardEvent) {
|
2912 | this.routeReuseStrategy = routeReuseStrategy;
|
2913 | this.futureState = futureState;
|
2914 | this.currState = currState;
|
2915 | this.forwardEvent = forwardEvent;
|
2916 | }
|
2917 | activate(parentContexts) {
|
2918 | const futureRoot = this.futureState._root;
|
2919 | const currRoot = this.currState ? this.currState._root : null;
|
2920 | this.deactivateChildRoutes(futureRoot, currRoot, parentContexts);
|
2921 | advanceActivatedRoute(this.futureState.root);
|
2922 | this.activateChildRoutes(futureRoot, currRoot, parentContexts);
|
2923 | }
|
2924 | // De-activate the child route that are not re-used for the future state
|
2925 | deactivateChildRoutes(futureNode, currNode, contexts) {
|
2926 | const children = nodeChildrenAsMap(currNode);
|
2927 | // Recurse on the routes active in the future state to de-activate deeper children
|
2928 | futureNode.children.forEach(futureChild => {
|
2929 | const childOutletName = futureChild.value.outlet;
|
2930 | this.deactivateRoutes(futureChild, children[childOutletName], contexts);
|
2931 | delete children[childOutletName];
|
2932 | });
|
2933 | // De-activate the routes that will not be re-used
|
2934 | forEach(children, (v, childName) => {
|
2935 | this.deactivateRouteAndItsChildren(v, contexts);
|
2936 | });
|
2937 | }
|
2938 | deactivateRoutes(futureNode, currNode, parentContext) {
|
2939 | const future = futureNode.value;
|
2940 | const curr = currNode ? currNode.value : null;
|
2941 | if (future === curr) {
|
2942 | // Reusing the node, check to see if the children need to be de-activated
|
2943 | if (future.component) {
|
2944 | // If we have a normal route, we need to go through an outlet.
|
2945 | const context = parentContext.getContext(future.outlet);
|
2946 | if (context) {
|
2947 | this.deactivateChildRoutes(futureNode, currNode, context.children);
|
2948 | }
|
2949 | }
|
2950 | else {
|
2951 | // if we have a componentless route, we recurse but keep the same outlet map.
|
2952 | this.deactivateChildRoutes(futureNode, currNode, parentContext);
|
2953 | }
|
2954 | }
|
2955 | else {
|
2956 | if (curr) {
|
2957 | // Deactivate the current route which will not be re-used
|
2958 | this.deactivateRouteAndItsChildren(currNode, parentContext);
|
2959 | }
|
2960 | }
|
2961 | }
|
2962 | deactivateRouteAndItsChildren(route, parentContexts) {
|
2963 | // If there is no component, the Route is never attached to an outlet (because there is no
|
2964 | // component to attach).
|
2965 | if (route.value.component && this.routeReuseStrategy.shouldDetach(route.value.snapshot)) {
|
2966 | this.detachAndStoreRouteSubtree(route, parentContexts);
|
2967 | }
|
2968 | else {
|
2969 | this.deactivateRouteAndOutlet(route, parentContexts);
|
2970 | }
|
2971 | }
|
2972 | detachAndStoreRouteSubtree(route, parentContexts) {
|
2973 | const context = parentContexts.getContext(route.value.outlet);
|
2974 | const contexts = context && route.value.component ? context.children : parentContexts;
|
2975 | const children = nodeChildrenAsMap(route);
|
2976 | for (const childOutlet of Object.keys(children)) {
|
2977 | this.deactivateRouteAndItsChildren(children[childOutlet], contexts);
|
2978 | }
|
2979 | if (context && context.outlet) {
|
2980 | const componentRef = context.outlet.detach();
|
2981 | const contexts = context.children.onOutletDeactivated();
|
2982 | this.routeReuseStrategy.store(route.value.snapshot, { componentRef, route, contexts });
|
2983 | }
|
2984 | }
|
2985 | deactivateRouteAndOutlet(route, parentContexts) {
|
2986 | const context = parentContexts.getContext(route.value.outlet);
|
2987 | // The context could be `null` if we are on a componentless route but there may still be
|
2988 | // children that need deactivating.
|
2989 | const contexts = context && route.value.component ? context.children : parentContexts;
|
2990 | const children = nodeChildrenAsMap(route);
|
2991 | for (const childOutlet of Object.keys(children)) {
|
2992 | this.deactivateRouteAndItsChildren(children[childOutlet], contexts);
|
2993 | }
|
2994 | if (context && context.outlet) {
|
2995 | // Destroy the component
|
2996 | context.outlet.deactivate();
|
2997 | // Destroy the contexts for all the outlets that were in the component
|
2998 | context.children.onOutletDeactivated();
|
2999 | // Clear the information about the attached component on the context but keep the reference to
|
3000 | // the outlet.
|
3001 | context.attachRef = null;
|
3002 | context.resolver = null;
|
3003 | context.route = null;
|
3004 | }
|
3005 | }
|
3006 | activateChildRoutes(futureNode, currNode, contexts) {
|
3007 | const children = nodeChildrenAsMap(currNode);
|
3008 | futureNode.children.forEach(c => {
|
3009 | this.activateRoutes(c, children[c.value.outlet], contexts);
|
3010 | this.forwardEvent(new ActivationEnd(c.value.snapshot));
|
3011 | });
|
3012 | if (futureNode.children.length) {
|
3013 | this.forwardEvent(new ChildActivationEnd(futureNode.value.snapshot));
|
3014 | }
|
3015 | }
|
3016 | activateRoutes(futureNode, currNode, parentContexts) {
|
3017 | const future = futureNode.value;
|
3018 | const curr = currNode ? currNode.value : null;
|
3019 | advanceActivatedRoute(future);
|
3020 | // reusing the node
|
3021 | if (future === curr) {
|
3022 | if (future.component) {
|
3023 | // If we have a normal route, we need to go through an outlet.
|
3024 | const context = parentContexts.getOrCreateContext(future.outlet);
|
3025 | this.activateChildRoutes(futureNode, currNode, context.children);
|
3026 | }
|
3027 | else {
|
3028 | // if we have a componentless route, we recurse but keep the same outlet map.
|
3029 | this.activateChildRoutes(futureNode, currNode, parentContexts);
|
3030 | }
|
3031 | }
|
3032 | else {
|
3033 | if (future.component) {
|
3034 | // if we have a normal route, we need to place the component into the outlet and recurse.
|
3035 | const context = parentContexts.getOrCreateContext(future.outlet);
|
3036 | if (this.routeReuseStrategy.shouldAttach(future.snapshot)) {
|
3037 | const stored = this.routeReuseStrategy.retrieve(future.snapshot);
|
3038 | this.routeReuseStrategy.store(future.snapshot, null);
|
3039 | context.children.onOutletReAttached(stored.contexts);
|
3040 | context.attachRef = stored.componentRef;
|
3041 | context.route = stored.route.value;
|
3042 | if (context.outlet) {
|
3043 | // Attach right away when the outlet has already been instantiated
|
3044 | // Otherwise attach from `RouterOutlet.ngOnInit` when it is instantiated
|
3045 | context.outlet.attach(stored.componentRef, stored.route.value);
|
3046 | }
|
3047 | advanceActivatedRoute(stored.route.value);
|
3048 | this.activateChildRoutes(futureNode, null, context.children);
|
3049 | }
|
3050 | else {
|
3051 | const injector = getClosestRouteInjector(future.snapshot);
|
3052 | const cmpFactoryResolver = injector?.get(ComponentFactoryResolver) ?? null;
|
3053 | context.attachRef = null;
|
3054 | context.route = future;
|
3055 | context.resolver = cmpFactoryResolver;
|
3056 | context.injector = injector;
|
3057 | if (context.outlet) {
|
3058 | // Activate the outlet when it has already been instantiated
|
3059 | // Otherwise it will get activated from its `ngOnInit` when instantiated
|
3060 | context.outlet.activateWith(future, context.injector);
|
3061 | }
|
3062 | this.activateChildRoutes(futureNode, null, context.children);
|
3063 | }
|
3064 | }
|
3065 | else {
|
3066 | // if we have a componentless route, we recurse but keep the same outlet map.
|
3067 | this.activateChildRoutes(futureNode, null, parentContexts);
|
3068 | }
|
3069 | }
|
3070 | }
|
3071 | }
|
3072 |
|
3073 | class CanActivate {
|
3074 | constructor(path) {
|
3075 | this.path = path;
|
3076 | this.route = this.path[this.path.length - 1];
|
3077 | }
|
3078 | }
|
3079 | class CanDeactivate {
|
3080 | constructor(component, route) {
|
3081 | this.component = component;
|
3082 | this.route = route;
|
3083 | }
|
3084 | }
|
3085 | function getAllRouteGuards(future, curr, parentContexts) {
|
3086 | const futureRoot = future._root;
|
3087 | const currRoot = curr ? curr._root : null;
|
3088 | return getChildRouteGuards(futureRoot, currRoot, parentContexts, [futureRoot.value]);
|
3089 | }
|
3090 | function getCanActivateChild(p) {
|
3091 | const canActivateChild = p.routeConfig ? p.routeConfig.canActivateChild : null;
|
3092 | if (!canActivateChild || canActivateChild.length === 0)
|
3093 | return null;
|
3094 | return { node: p, guards: canActivateChild };
|
3095 | }
|
3096 | function getTokenOrFunctionIdentity(tokenOrFunction, injector) {
|
3097 | const NOT_FOUND = Symbol();
|
3098 | const result = injector.get(tokenOrFunction, NOT_FOUND);
|
3099 | if (result === NOT_FOUND) {
|
3100 | if (typeof tokenOrFunction === 'function' && !ɵisInjectable(tokenOrFunction)) {
|
3101 | // We think the token is just a function so return it as-is
|
3102 | return tokenOrFunction;
|
3103 | }
|
3104 | else {
|
3105 | // This will throw the not found error
|
3106 | return injector.get(tokenOrFunction);
|
3107 | }
|
3108 | }
|
3109 | return result;
|
3110 | }
|
3111 | function getChildRouteGuards(futureNode, currNode, contexts, futurePath, checks = {
|
3112 | canDeactivateChecks: [],
|
3113 | canActivateChecks: []
|
3114 | }) {
|
3115 | const prevChildren = nodeChildrenAsMap(currNode);
|
3116 | // Process the children of the future route
|
3117 | futureNode.children.forEach(c => {
|
3118 | getRouteGuards(c, prevChildren[c.value.outlet], contexts, futurePath.concat([c.value]), checks);
|
3119 | delete prevChildren[c.value.outlet];
|
3120 | });
|
3121 | // Process any children left from the current route (not active for the future route)
|
3122 | forEach(prevChildren, (v, k) => deactivateRouteAndItsChildren(v, contexts.getContext(k), checks));
|
3123 | return checks;
|
3124 | }
|
3125 | function getRouteGuards(futureNode, currNode, parentContexts, futurePath, checks = {
|
3126 | canDeactivateChecks: [],
|
3127 | canActivateChecks: []
|
3128 | }) {
|
3129 | const future = futureNode.value;
|
3130 | const curr = currNode ? currNode.value : null;
|
3131 | const context = parentContexts ? parentContexts.getContext(futureNode.value.outlet) : null;
|
3132 | // reusing the node
|
3133 | if (curr && future.routeConfig === curr.routeConfig) {
|
3134 | const shouldRun = shouldRunGuardsAndResolvers(curr, future, future.routeConfig.runGuardsAndResolvers);
|
3135 | if (shouldRun) {
|
3136 | checks.canActivateChecks.push(new CanActivate(futurePath));
|
3137 | }
|
3138 | else {
|
3139 | // we need to set the data
|
3140 | future.data = curr.data;
|
3141 | future._resolvedData = curr._resolvedData;
|
3142 | }
|
3143 | // If we have a component, we need to go through an outlet.
|
3144 | if (future.component) {
|
3145 | getChildRouteGuards(futureNode, currNode, context ? context.children : null, futurePath, checks);
|
3146 | // if we have a componentless route, we recurse but keep the same outlet map.
|
3147 | }
|
3148 | else {
|
3149 | getChildRouteGuards(futureNode, currNode, parentContexts, futurePath, checks);
|
3150 | }
|
3151 | if (shouldRun && context && context.outlet && context.outlet.isActivated) {
|
3152 | checks.canDeactivateChecks.push(new CanDeactivate(context.outlet.component, curr));
|
3153 | }
|
3154 | }
|
3155 | else {
|
3156 | if (curr) {
|
3157 | deactivateRouteAndItsChildren(currNode, context, checks);
|
3158 | }
|
3159 | checks.canActivateChecks.push(new CanActivate(futurePath));
|
3160 | // If we have a component, we need to go through an outlet.
|
3161 | if (future.component) {
|
3162 | getChildRouteGuards(futureNode, null, context ? context.children : null, futurePath, checks);
|
3163 | // if we have a componentless route, we recurse but keep the same outlet map.
|
3164 | }
|
3165 | else {
|
3166 | getChildRouteGuards(futureNode, null, parentContexts, futurePath, checks);
|
3167 | }
|
3168 | }
|
3169 | return checks;
|
3170 | }
|
3171 | function shouldRunGuardsAndResolvers(curr, future, mode) {
|
3172 | if (typeof mode === 'function') {
|
3173 | return mode(curr, future);
|
3174 | }
|
3175 | switch (mode) {
|
3176 | case 'pathParamsChange':
|
3177 | return !equalPath(curr.url, future.url);
|
3178 | case 'pathParamsOrQueryParamsChange':
|
3179 | return !equalPath(curr.url, future.url) ||
|
3180 | !shallowEqual(curr.queryParams, future.queryParams);
|
3181 | case 'always':
|
3182 | return true;
|
3183 | case 'paramsOrQueryParamsChange':
|
3184 | return !equalParamsAndUrlSegments(curr, future) ||
|
3185 | !shallowEqual(curr.queryParams, future.queryParams);
|
3186 | case 'paramsChange':
|
3187 | default:
|
3188 | return !equalParamsAndUrlSegments(curr, future);
|
3189 | }
|
3190 | }
|
3191 | function deactivateRouteAndItsChildren(route, context, checks) {
|
3192 | const children = nodeChildrenAsMap(route);
|
3193 | const r = route.value;
|
3194 | forEach(children, (node, childName) => {
|
3195 | if (!r.component) {
|
3196 | deactivateRouteAndItsChildren(node, context, checks);
|
3197 | }
|
3198 | else if (context) {
|
3199 | deactivateRouteAndItsChildren(node, context.children.getContext(childName), checks);
|
3200 | }
|
3201 | else {
|
3202 | deactivateRouteAndItsChildren(node, null, checks);
|
3203 | }
|
3204 | });
|
3205 | if (!r.component) {
|
3206 | checks.canDeactivateChecks.push(new CanDeactivate(null, r));
|
3207 | }
|
3208 | else if (context && context.outlet && context.outlet.isActivated) {
|
3209 | checks.canDeactivateChecks.push(new CanDeactivate(context.outlet.component, r));
|
3210 | }
|
3211 | else {
|
3212 | checks.canDeactivateChecks.push(new CanDeactivate(null, r));
|
3213 | }
|
3214 | }
|
3215 |
|
3216 | /**
|
3217 | * Simple function check, but generic so type inference will flow. Example:
|
3218 | *
|
3219 | * function product(a: number, b: number) {
|
3220 | * return a * b;
|
3221 | * }
|
3222 | *
|
3223 | * if (isFunction<product>(fn)) {
|
3224 | * return fn(1, 2);
|
3225 | * } else {
|
3226 | * throw "Must provide the `product` function";
|
3227 | * }
|
3228 | */
|
3229 | function isFunction(v) {
|
3230 | return typeof v === 'function';
|
3231 | }
|
3232 | function isBoolean(v) {
|
3233 | return typeof v === 'boolean';
|
3234 | }
|
3235 | function isCanLoad(guard) {
|
3236 | return guard && isFunction(guard.canLoad);
|
3237 | }
|
3238 | function isCanActivate(guard) {
|
3239 | return guard && isFunction(guard.canActivate);
|
3240 | }
|
3241 | function isCanActivateChild(guard) {
|
3242 | return guard && isFunction(guard.canActivateChild);
|
3243 | }
|
3244 | function isCanDeactivate(guard) {
|
3245 | return guard && isFunction(guard.canDeactivate);
|
3246 | }
|
3247 | function isCanMatch(guard) {
|
3248 | return guard && isFunction(guard.canMatch);
|
3249 | }
|
3250 | function isRedirectingNavigationCancelingError(error) {
|
3251 | return isNavigationCancelingError(error) && isUrlTree(error.url);
|
3252 | }
|
3253 | function isNavigationCancelingError(error) {
|
3254 | return error && error[NAVIGATION_CANCELING_ERROR];
|
3255 | }
|
3256 | function isEmptyError(e) {
|
3257 | return e instanceof EmptyError || e?.name === 'EmptyError';
|
3258 | }
|
3259 |
|
3260 | const INITIAL_VALUE = Symbol('INITIAL_VALUE');
|
3261 | function prioritizedGuardValue() {
|
3262 | return switchMap(obs => {
|
3263 | return combineLatest(obs.map(o => o.pipe(take(1), startWith(INITIAL_VALUE))))
|
3264 | .pipe(map((results) => {
|
3265 | for (const result of results) {
|
3266 | if (result === true) {
|
3267 | // If result is true, check the next one
|
3268 | continue;
|
3269 | }
|
3270 | else if (result === INITIAL_VALUE) {
|
3271 | // If guard has not finished, we need to stop processing.
|
3272 | return INITIAL_VALUE;
|
3273 | }
|
3274 | else if (result === false || result instanceof UrlTree) {
|
3275 | // Result finished and was not true. Return the result.
|
3276 | // Note that we only allow false/UrlTree. Other values are considered invalid and
|
3277 | // ignored.
|
3278 | return result;
|
3279 | }
|
3280 | }
|
3281 | // Everything resolved to true. Return true.
|
3282 | return true;
|
3283 | }), filter((item) => item !== INITIAL_VALUE), take(1));
|
3284 | });
|
3285 | }
|
3286 |
|
3287 | function checkGuards(injector, forwardEvent) {
|
3288 | return mergeMap(t => {
|
3289 | const { targetSnapshot, currentSnapshot, guards: { canActivateChecks, canDeactivateChecks } } = t;
|
3290 | if (canDeactivateChecks.length === 0 && canActivateChecks.length === 0) {
|
3291 | return of({ ...t, guardsResult: true });
|
3292 | }
|
3293 | return runCanDeactivateChecks(canDeactivateChecks, targetSnapshot, currentSnapshot, injector)
|
3294 | .pipe(mergeMap(canDeactivate => {
|
3295 | return canDeactivate && isBoolean(canDeactivate) ?
|
3296 | runCanActivateChecks(targetSnapshot, canActivateChecks, injector, forwardEvent) :
|
3297 | of(canDeactivate);
|
3298 | }), map(guardsResult => ({ ...t, guardsResult })));
|
3299 | });
|
3300 | }
|
3301 | function runCanDeactivateChecks(checks, futureRSS, currRSS, injector) {
|
3302 | return from(checks).pipe(mergeMap(check => runCanDeactivate(check.component, check.route, currRSS, futureRSS, injector)), first(result => {
|
3303 | return result !== true;
|
3304 | }, true));
|
3305 | }
|
3306 | function runCanActivateChecks(futureSnapshot, checks, injector, forwardEvent) {
|
3307 | return from(checks).pipe(concatMap((check) => {
|
3308 | return concat(fireChildActivationStart(check.route.parent, forwardEvent), fireActivationStart(check.route, forwardEvent), runCanActivateChild(futureSnapshot, check.path, injector), runCanActivate(futureSnapshot, check.route, injector));
|
3309 | }), first(result => {
|
3310 | return result !== true;
|
3311 | }, true));
|
3312 | }
|
3313 | /**
|
3314 | * This should fire off `ActivationStart` events for each route being activated at this
|
3315 | * level.
|
3316 | * In other words, if you're activating `a` and `b` below, `path` will contain the
|
3317 | * `ActivatedRouteSnapshot`s for both and we will fire `ActivationStart` for both. Always
|
3318 | * return
|
3319 | * `true` so checks continue to run.
|
3320 | */
|
3321 | function fireActivationStart(snapshot, forwardEvent) {
|
3322 | if (snapshot !== null && forwardEvent) {
|
3323 | forwardEvent(new ActivationStart(snapshot));
|
3324 | }
|
3325 | return of(true);
|
3326 | }
|
3327 | /**
|
3328 | * This should fire off `ChildActivationStart` events for each route being activated at this
|
3329 | * level.
|
3330 | * In other words, if you're activating `a` and `b` below, `path` will contain the
|
3331 | * `ActivatedRouteSnapshot`s for both and we will fire `ChildActivationStart` for both. Always
|
3332 | * return
|
3333 | * `true` so checks continue to run.
|
3334 | */
|
3335 | function fireChildActivationStart(snapshot, forwardEvent) {
|
3336 | if (snapshot !== null && forwardEvent) {
|
3337 | forwardEvent(new ChildActivationStart(snapshot));
|
3338 | }
|
3339 | return of(true);
|
3340 | }
|
3341 | function runCanActivate(futureRSS, futureARS, injector) {
|
3342 | const canActivate = futureARS.routeConfig ? futureARS.routeConfig.canActivate : null;
|
3343 | if (!canActivate || canActivate.length === 0)
|
3344 | return of(true);
|
3345 | const canActivateObservables = canActivate.map((canActivate) => {
|
3346 | return defer(() => {
|
3347 | const closestInjector = getClosestRouteInjector(futureARS) ?? injector;
|
3348 | const guard = getTokenOrFunctionIdentity(canActivate, closestInjector);
|
3349 | const guardVal = isCanActivate(guard) ?
|
3350 | guard.canActivate(futureARS, futureRSS) :
|
3351 | closestInjector.runInContext(() => guard(futureARS, futureRSS));
|
3352 | return wrapIntoObservable(guardVal).pipe(first());
|
3353 | });
|
3354 | });
|
3355 | return of(canActivateObservables).pipe(prioritizedGuardValue());
|
3356 | }
|
3357 | function runCanActivateChild(futureRSS, path, injector) {
|
3358 | const futureARS = path[path.length - 1];
|
3359 | const canActivateChildGuards = path.slice(0, path.length - 1)
|
3360 | .reverse()
|
3361 | .map(p => getCanActivateChild(p))
|
3362 | .filter(_ => _ !== null);
|
3363 | const canActivateChildGuardsMapped = canActivateChildGuards.map((d) => {
|
3364 | return defer(() => {
|
3365 | const guardsMapped = d.guards.map((canActivateChild) => {
|
3366 | const closestInjector = getClosestRouteInjector(d.node) ?? injector;
|
3367 | const guard = getTokenOrFunctionIdentity(canActivateChild, closestInjector);
|
3368 | const guardVal = isCanActivateChild(guard) ?
|
3369 | guard.canActivateChild(futureARS, futureRSS) :
|
3370 | closestInjector.runInContext(() => guard(futureARS, futureRSS));
|
3371 | return wrapIntoObservable(guardVal).pipe(first());
|
3372 | });
|
3373 | return of(guardsMapped).pipe(prioritizedGuardValue());
|
3374 | });
|
3375 | });
|
3376 | return of(canActivateChildGuardsMapped).pipe(prioritizedGuardValue());
|
3377 | }
|
3378 | function runCanDeactivate(component, currARS, currRSS, futureRSS, injector) {
|
3379 | const canDeactivate = currARS && currARS.routeConfig ? currARS.routeConfig.canDeactivate : null;
|
3380 | if (!canDeactivate || canDeactivate.length === 0)
|
3381 | return of(true);
|
3382 | const canDeactivateObservables = canDeactivate.map((c) => {
|
3383 | const closestInjector = getClosestRouteInjector(currARS) ?? injector;
|
3384 | const guard = getTokenOrFunctionIdentity(c, closestInjector);
|
3385 | const guardVal = isCanDeactivate(guard) ?
|
3386 | guard.canDeactivate(component, currARS, currRSS, futureRSS) :
|
3387 | closestInjector.runInContext(() => guard(component, currARS, currRSS, futureRSS));
|
3388 | return wrapIntoObservable(guardVal).pipe(first());
|
3389 | });
|
3390 | return of(canDeactivateObservables).pipe(prioritizedGuardValue());
|
3391 | }
|
3392 | function runCanLoadGuards(injector, route, segments, urlSerializer) {
|
3393 | const canLoad = route.canLoad;
|
3394 | if (canLoad === undefined || canLoad.length === 0) {
|
3395 | return of(true);
|
3396 | }
|
3397 | const canLoadObservables = canLoad.map((injectionToken) => {
|
3398 | const guard = getTokenOrFunctionIdentity(injectionToken, injector);
|
3399 | const guardVal = isCanLoad(guard) ?
|
3400 | guard.canLoad(route, segments) :
|
3401 | injector.runInContext(() => guard(route, segments));
|
3402 | return wrapIntoObservable(guardVal);
|
3403 | });
|
3404 | return of(canLoadObservables)
|
3405 | .pipe(prioritizedGuardValue(), redirectIfUrlTree(urlSerializer));
|
3406 | }
|
3407 | function redirectIfUrlTree(urlSerializer) {
|
3408 | return pipe(tap((result) => {
|
3409 | if (!isUrlTree(result))
|
3410 | return;
|
3411 | throw redirectingNavigationError(urlSerializer, result);
|
3412 | }), map(result => result === true));
|
3413 | }
|
3414 | function runCanMatchGuards(injector, route, segments, urlSerializer) {
|
3415 | const canMatch = route.canMatch;
|
3416 | if (!canMatch || canMatch.length === 0)
|
3417 | return of(true);
|
3418 | const canMatchObservables = canMatch.map(injectionToken => {
|
3419 | const guard = getTokenOrFunctionIdentity(injectionToken, injector);
|
3420 | const guardVal = isCanMatch(guard) ?
|
3421 | guard.canMatch(route, segments) :
|
3422 | injector.runInContext(() => guard(route, segments));
|
3423 | return wrapIntoObservable(guardVal);
|
3424 | });
|
3425 | return of(canMatchObservables)
|
3426 | .pipe(prioritizedGuardValue(), redirectIfUrlTree(urlSerializer));
|
3427 | }
|
3428 |
|
3429 | const noMatch$1 = {
|
3430 | matched: false,
|
3431 | consumedSegments: [],
|
3432 | remainingSegments: [],
|
3433 | parameters: {},
|
3434 | positionalParamSegments: {}
|
3435 | };
|
3436 | function matchWithChecks(segmentGroup, route, segments, injector, urlSerializer) {
|
3437 | const result = match(segmentGroup, route, segments);
|
3438 | if (!result.matched) {
|
3439 | return of(result);
|
3440 | }
|
3441 | // Only create the Route's `EnvironmentInjector` if it matches the attempted
|
3442 | // navigation
|
3443 | injector = getOrCreateRouteInjectorIfNeeded(route, injector);
|
3444 | return runCanMatchGuards(injector, route, segments, urlSerializer)
|
3445 | .pipe(map((v) => v === true ? result : { ...noMatch$1 }));
|
3446 | }
|
3447 | function match(segmentGroup, route, segments) {
|
3448 | if (route.path === '') {
|
3449 | if (route.pathMatch === 'full' && (segmentGroup.hasChildren() || segments.length > 0)) {
|
3450 | return { ...noMatch$1 };
|
3451 | }
|
3452 | return {
|
3453 | matched: true,
|
3454 | consumedSegments: [],
|
3455 | remainingSegments: segments,
|
3456 | parameters: {},
|
3457 | positionalParamSegments: {}
|
3458 | };
|
3459 | }
|
3460 | const matcher = route.matcher || defaultUrlMatcher;
|
3461 | const res = matcher(segments, segmentGroup, route);
|
3462 | if (!res)
|
3463 | return { ...noMatch$1 };
|
3464 | const posParams = {};
|
3465 | forEach(res.posParams, (v, k) => {
|
3466 | posParams[k] = v.path;
|
3467 | });
|
3468 | const parameters = res.consumed.length > 0 ?
|
3469 | { ...posParams, ...res.consumed[res.consumed.length - 1].parameters } :
|
3470 | posParams;
|
3471 | return {
|
3472 | matched: true,
|
3473 | consumedSegments: res.consumed,
|
3474 | remainingSegments: segments.slice(res.consumed.length),
|
3475 | // TODO(atscott): investigate combining parameters and positionalParamSegments
|
3476 | parameters,
|
3477 | positionalParamSegments: res.posParams ?? {}
|
3478 | };
|
3479 | }
|
3480 | function split(segmentGroup, consumedSegments, slicedSegments, config) {
|
3481 | if (slicedSegments.length > 0 &&
|
3482 | containsEmptyPathMatchesWithNamedOutlets(segmentGroup, slicedSegments, config)) {
|
3483 | const s = new UrlSegmentGroup(consumedSegments, createChildrenForEmptyPaths(segmentGroup, consumedSegments, config, new UrlSegmentGroup(slicedSegments, segmentGroup.children)));
|
3484 | s._sourceSegment = segmentGroup;
|
3485 | s._segmentIndexShift = consumedSegments.length;
|
3486 | return { segmentGroup: s, slicedSegments: [] };
|
3487 | }
|
3488 | if (slicedSegments.length === 0 &&
|
3489 | containsEmptyPathMatches(segmentGroup, slicedSegments, config)) {
|
3490 | const s = new UrlSegmentGroup(segmentGroup.segments, addEmptyPathsToChildrenIfNeeded(segmentGroup, consumedSegments, slicedSegments, config, segmentGroup.children));
|
3491 | s._sourceSegment = segmentGroup;
|
3492 | s._segmentIndexShift = consumedSegments.length;
|
3493 | return { segmentGroup: s, slicedSegments };
|
3494 | }
|
3495 | const s = new UrlSegmentGroup(segmentGroup.segments, segmentGroup.children);
|
3496 | s._sourceSegment = segmentGroup;
|
3497 | s._segmentIndexShift = consumedSegments.length;
|
3498 | return { segmentGroup: s, slicedSegments };
|
3499 | }
|
3500 | function addEmptyPathsToChildrenIfNeeded(segmentGroup, consumedSegments, slicedSegments, routes, children) {
|
3501 | const res = {};
|
3502 | for (const r of routes) {
|
3503 | if (emptyPathMatch(segmentGroup, slicedSegments, r) && !children[getOutlet(r)]) {
|
3504 | const s = new UrlSegmentGroup([], {});
|
3505 | s._sourceSegment = segmentGroup;
|
3506 | s._segmentIndexShift = consumedSegments.length;
|
3507 | res[getOutlet(r)] = s;
|
3508 | }
|
3509 | }
|
3510 | return { ...children, ...res };
|
3511 | }
|
3512 | function createChildrenForEmptyPaths(segmentGroup, consumedSegments, routes, primarySegment) {
|
3513 | const res = {};
|
3514 | res[PRIMARY_OUTLET] = primarySegment;
|
3515 | primarySegment._sourceSegment = segmentGroup;
|
3516 | primarySegment._segmentIndexShift = consumedSegments.length;
|
3517 | for (const r of routes) {
|
3518 | if (r.path === '' && getOutlet(r) !== PRIMARY_OUTLET) {
|
3519 | const s = new UrlSegmentGroup([], {});
|
3520 | s._sourceSegment = segmentGroup;
|
3521 | s._segmentIndexShift = consumedSegments.length;
|
3522 | res[getOutlet(r)] = s;
|
3523 | }
|
3524 | }
|
3525 | return res;
|
3526 | }
|
3527 | function containsEmptyPathMatchesWithNamedOutlets(segmentGroup, slicedSegments, routes) {
|
3528 | return routes.some(r => emptyPathMatch(segmentGroup, slicedSegments, r) && getOutlet(r) !== PRIMARY_OUTLET);
|
3529 | }
|
3530 | function containsEmptyPathMatches(segmentGroup, slicedSegments, routes) {
|
3531 | return routes.some(r => emptyPathMatch(segmentGroup, slicedSegments, r));
|
3532 | }
|
3533 | function emptyPathMatch(segmentGroup, slicedSegments, r) {
|
3534 | if ((segmentGroup.hasChildren() || slicedSegments.length > 0) && r.pathMatch === 'full') {
|
3535 | return false;
|
3536 | }
|
3537 | return r.path === '';
|
3538 | }
|
3539 | /**
|
3540 | * Determines if `route` is a path match for the `rawSegment`, `segments`, and `outlet` without
|
3541 | * verifying that its children are a full match for the remainder of the `rawSegment` children as
|
3542 | * well.
|
3543 | */
|
3544 | function isImmediateMatch(route, rawSegment, segments, outlet) {
|
3545 | // We allow matches to empty paths when the outlets differ so we can match a url like `/(b:b)` to
|
3546 | // a config like
|
3547 | // * `{path: '', children: [{path: 'b', outlet: 'b'}]}`
|
3548 | // or even
|
3549 | // * `{path: '', outlet: 'a', children: [{path: 'b', outlet: 'b'}]`
|
3550 | //
|
3551 | // The exception here is when the segment outlet is for the primary outlet. This would
|
3552 | // result in a match inside the named outlet because all children there are written as primary
|
3553 | // outlets. So we need to prevent child named outlet matches in a url like `/b` in a config like
|
3554 | // * `{path: '', outlet: 'x' children: [{path: 'b'}]}`
|
3555 | // This should only match if the url is `/(x:b)`.
|
3556 | if (getOutlet(route) !== outlet &&
|
3557 | (outlet === PRIMARY_OUTLET || !emptyPathMatch(rawSegment, segments, route))) {
|
3558 | return false;
|
3559 | }
|
3560 | if (route.path === '**') {
|
3561 | return true;
|
3562 | }
|
3563 | return match(rawSegment, route, segments).matched;
|
3564 | }
|
3565 | function noLeftoversInUrl(segmentGroup, segments, outlet) {
|
3566 | return segments.length === 0 && !segmentGroup.children[outlet];
|
3567 | }
|
3568 |
|
3569 | const NG_DEV_MODE$7 = typeof ngDevMode === 'undefined' || ngDevMode;
|
3570 | class NoMatch$1 {
|
3571 | constructor(segmentGroup) {
|
3572 | this.segmentGroup = segmentGroup || null;
|
3573 | }
|
3574 | }
|
3575 | class AbsoluteRedirect {
|
3576 | constructor(urlTree) {
|
3577 | this.urlTree = urlTree;
|
3578 | }
|
3579 | }
|
3580 | function noMatch(segmentGroup) {
|
3581 | return throwError(new NoMatch$1(segmentGroup));
|
3582 | }
|
3583 | function absoluteRedirect(newTree) {
|
3584 | return throwError(new AbsoluteRedirect(newTree));
|
3585 | }
|
3586 | function namedOutletsRedirect(redirectTo) {
|
3587 | return throwError(new ɵRuntimeError(4000 /* RuntimeErrorCode.NAMED_OUTLET_REDIRECT */, NG_DEV_MODE$7 &&
|
3588 | `Only absolute redirects can have named outlets. redirectTo: '${redirectTo}'`));
|
3589 | }
|
3590 | function canLoadFails(route) {
|
3591 | return throwError(navigationCancelingError(NG_DEV_MODE$7 &&
|
3592 | `Cannot load children because the guard of the route "path: '${route.path}'" returned false`, 3 /* NavigationCancellationCode.GuardRejected */));
|
3593 | }
|
3594 | /**
|
3595 | * Returns the `UrlTree` with the redirection applied.
|
3596 | *
|
3597 | * Lazy modules are loaded along the way.
|
3598 | */
|
3599 | function applyRedirects$1(injector, configLoader, urlSerializer, urlTree, config) {
|
3600 | return new ApplyRedirects(injector, configLoader, urlSerializer, urlTree, config).apply();
|
3601 | }
|
3602 | class ApplyRedirects {
|
3603 | constructor(injector, configLoader, urlSerializer, urlTree, config) {
|
3604 | this.injector = injector;
|
3605 | this.configLoader = configLoader;
|
3606 | this.urlSerializer = urlSerializer;
|
3607 | this.urlTree = urlTree;
|
3608 | this.config = config;
|
3609 | this.allowRedirects = true;
|
3610 | }
|
3611 | apply() {
|
3612 | const splitGroup = split(this.urlTree.root, [], [], this.config).segmentGroup;
|
3613 | // TODO(atscott): creating a new segment removes the _sourceSegment _segmentIndexShift, which is
|
3614 | // only necessary to prevent failures in tests which assert exact object matches. The `split` is
|
3615 | // now shared between `applyRedirects` and `recognize` but only the `recognize` step needs these
|
3616 | // properties. Before the implementations were merged, the `applyRedirects` would not assign
|
3617 | // them. We should be able to remove this logic as a "breaking change" but should do some more
|
3618 | // investigation into the failures first.
|
3619 | const rootSegmentGroup = new UrlSegmentGroup(splitGroup.segments, splitGroup.children);
|
3620 | const expanded$ = this.expandSegmentGroup(this.injector, this.config, rootSegmentGroup, PRIMARY_OUTLET);
|
3621 | const urlTrees$ = expanded$.pipe(map((rootSegmentGroup) => {
|
3622 | return this.createUrlTree(squashSegmentGroup(rootSegmentGroup), this.urlTree.queryParams, this.urlTree.fragment);
|
3623 | }));
|
3624 | return urlTrees$.pipe(catchError((e) => {
|
3625 | if (e instanceof AbsoluteRedirect) {
|
3626 | // After an absolute redirect we do not apply any more redirects!
|
3627 | // If this implementation changes, update the documentation note in `redirectTo`.
|
3628 | this.allowRedirects = false;
|
3629 | // we need to run matching, so we can fetch all lazy-loaded modules
|
3630 | return this.match(e.urlTree);
|
3631 | }
|
3632 | if (e instanceof NoMatch$1) {
|
3633 | throw this.noMatchError(e);
|
3634 | }
|
3635 | throw e;
|
3636 | }));
|
3637 | }
|
3638 | match(tree) {
|
3639 | const expanded$ = this.expandSegmentGroup(this.injector, this.config, tree.root, PRIMARY_OUTLET);
|
3640 | const mapped$ = expanded$.pipe(map((rootSegmentGroup) => {
|
3641 | return this.createUrlTree(squashSegmentGroup(rootSegmentGroup), tree.queryParams, tree.fragment);
|
3642 | }));
|
3643 | return mapped$.pipe(catchError((e) => {
|
3644 | if (e instanceof NoMatch$1) {
|
3645 | throw this.noMatchError(e);
|
3646 | }
|
3647 | throw e;
|
3648 | }));
|
3649 | }
|
3650 | noMatchError(e) {
|
3651 | return new ɵRuntimeError(4002 /* RuntimeErrorCode.NO_MATCH */, NG_DEV_MODE$7 && `Cannot match any routes. URL Segment: '${e.segmentGroup}'`);
|
3652 | }
|
3653 | createUrlTree(rootCandidate, queryParams, fragment) {
|
3654 | const root = createRoot(rootCandidate);
|
3655 | return new UrlTree(root, queryParams, fragment);
|
3656 | }
|
3657 | expandSegmentGroup(injector, routes, segmentGroup, outlet) {
|
3658 | if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) {
|
3659 | return this.expandChildren(injector, routes, segmentGroup)
|
3660 | .pipe(map((children) => new UrlSegmentGroup([], children)));
|
3661 | }
|
3662 | return this.expandSegment(injector, segmentGroup, routes, segmentGroup.segments, outlet, true);
|
3663 | }
|
3664 | // Recursively expand segment groups for all the child outlets
|
3665 | expandChildren(injector, routes, segmentGroup) {
|
3666 | // Expand outlets one at a time, starting with the primary outlet. We need to do it this way
|
3667 | // because an absolute redirect from the primary outlet takes precedence.
|
3668 | const childOutlets = [];
|
3669 | for (const child of Object.keys(segmentGroup.children)) {
|
3670 | if (child === 'primary') {
|
3671 | childOutlets.unshift(child);
|
3672 | }
|
3673 | else {
|
3674 | childOutlets.push(child);
|
3675 | }
|
3676 | }
|
3677 | return from(childOutlets)
|
3678 | .pipe(concatMap(childOutlet => {
|
3679 | const child = segmentGroup.children[childOutlet];
|
3680 | // Sort the routes so routes with outlets that match the segment appear
|
3681 | // first, followed by routes for other outlets, which might match if they have an
|
3682 | // empty path.
|
3683 | const sortedRoutes = sortByMatchingOutlets(routes, childOutlet);
|
3684 | return this.expandSegmentGroup(injector, sortedRoutes, child, childOutlet)
|
3685 | .pipe(map(s => ({ segment: s, outlet: childOutlet })));
|
3686 | }), scan((children, expandedChild) => {
|
3687 | children[expandedChild.outlet] = expandedChild.segment;
|
3688 | return children;
|
3689 | }, {}), last$1());
|
3690 | }
|
3691 | expandSegment(injector, segmentGroup, routes, segments, outlet, allowRedirects) {
|
3692 | return from(routes).pipe(concatMap(r => {
|
3693 | const expanded$ = this.expandSegmentAgainstRoute(injector, segmentGroup, routes, r, segments, outlet, allowRedirects);
|
3694 | return expanded$.pipe(catchError((e) => {
|
3695 | if (e instanceof NoMatch$1) {
|
3696 | return of(null);
|
3697 | }
|
3698 | throw e;
|
3699 | }));
|
3700 | }), first((s) => !!s), catchError((e, _) => {
|
3701 | if (isEmptyError(e)) {
|
3702 | if (noLeftoversInUrl(segmentGroup, segments, outlet)) {
|
3703 | return of(new UrlSegmentGroup([], {}));
|
3704 | }
|
3705 | return noMatch(segmentGroup);
|
3706 | }
|
3707 | throw e;
|
3708 | }));
|
3709 | }
|
3710 | expandSegmentAgainstRoute(injector, segmentGroup, routes, route, paths, outlet, allowRedirects) {
|
3711 | if (!isImmediateMatch(route, segmentGroup, paths, outlet)) {
|
3712 | return noMatch(segmentGroup);
|
3713 | }
|
3714 | if (route.redirectTo === undefined) {
|
3715 | return this.matchSegmentAgainstRoute(injector, segmentGroup, route, paths, outlet);
|
3716 | }
|
3717 | if (allowRedirects && this.allowRedirects) {
|
3718 | return this.expandSegmentAgainstRouteUsingRedirect(injector, segmentGroup, routes, route, paths, outlet);
|
3719 | }
|
3720 | return noMatch(segmentGroup);
|
3721 | }
|
3722 | expandSegmentAgainstRouteUsingRedirect(injector, segmentGroup, routes, route, segments, outlet) {
|
3723 | if (route.path === '**') {
|
3724 | return this.expandWildCardWithParamsAgainstRouteUsingRedirect(injector, routes, route, outlet);
|
3725 | }
|
3726 | return this.expandRegularSegmentAgainstRouteUsingRedirect(injector, segmentGroup, routes, route, segments, outlet);
|
3727 | }
|
3728 | expandWildCardWithParamsAgainstRouteUsingRedirect(injector, routes, route, outlet) {
|
3729 | const newTree = this.applyRedirectCommands([], route.redirectTo, {});
|
3730 | if (route.redirectTo.startsWith('/')) {
|
3731 | return absoluteRedirect(newTree);
|
3732 | }
|
3733 | return this.lineralizeSegments(route, newTree).pipe(mergeMap((newSegments) => {
|
3734 | const group = new UrlSegmentGroup(newSegments, {});
|
3735 | return this.expandSegment(injector, group, routes, newSegments, outlet, false);
|
3736 | }));
|
3737 | }
|
3738 | expandRegularSegmentAgainstRouteUsingRedirect(injector, segmentGroup, routes, route, segments, outlet) {
|
3739 | const { matched, consumedSegments, remainingSegments, positionalParamSegments } = match(segmentGroup, route, segments);
|
3740 | if (!matched)
|
3741 | return noMatch(segmentGroup);
|
3742 | const newTree = this.applyRedirectCommands(consumedSegments, route.redirectTo, positionalParamSegments);
|
3743 | if (route.redirectTo.startsWith('/')) {
|
3744 | return absoluteRedirect(newTree);
|
3745 | }
|
3746 | return this.lineralizeSegments(route, newTree).pipe(mergeMap((newSegments) => {
|
3747 | return this.expandSegment(injector, segmentGroup, routes, newSegments.concat(remainingSegments), outlet, false);
|
3748 | }));
|
3749 | }
|
3750 | matchSegmentAgainstRoute(injector, rawSegmentGroup, route, segments, outlet) {
|
3751 | if (route.path === '**') {
|
3752 | // Only create the Route's `EnvironmentInjector` if it matches the attempted navigation
|
3753 | injector = getOrCreateRouteInjectorIfNeeded(route, injector);
|
3754 | if (route.loadChildren) {
|
3755 | const loaded$ = route._loadedRoutes ?
|
3756 | of({ routes: route._loadedRoutes, injector: route._loadedInjector }) :
|
3757 | this.configLoader.loadChildren(injector, route);
|
3758 | return loaded$.pipe(map((cfg) => {
|
3759 | route._loadedRoutes = cfg.routes;
|
3760 | route._loadedInjector = cfg.injector;
|
3761 | return new UrlSegmentGroup(segments, {});
|
3762 | }));
|
3763 | }
|
3764 | return of(new UrlSegmentGroup(segments, {}));
|
3765 | }
|
3766 | return matchWithChecks(rawSegmentGroup, route, segments, injector, this.urlSerializer)
|
3767 | .pipe(switchMap(({ matched, consumedSegments, remainingSegments }) => {
|
3768 | if (!matched)
|
3769 | return noMatch(rawSegmentGroup);
|
3770 | // If the route has an injector created from providers, we should start using that.
|
3771 | injector = route._injector ?? injector;
|
3772 | const childConfig$ = this.getChildConfig(injector, route, segments);
|
3773 | return childConfig$.pipe(mergeMap((routerConfig) => {
|
3774 | const childInjector = routerConfig.injector ?? injector;
|
3775 | const childConfig = routerConfig.routes;
|
3776 | const { segmentGroup: splitSegmentGroup, slicedSegments } = split(rawSegmentGroup, consumedSegments, remainingSegments, childConfig);
|
3777 | // See comment on the other call to `split` about why this is necessary.
|
3778 | const segmentGroup = new UrlSegmentGroup(splitSegmentGroup.segments, splitSegmentGroup.children);
|
3779 | if (slicedSegments.length === 0 && segmentGroup.hasChildren()) {
|
3780 | const expanded$ = this.expandChildren(childInjector, childConfig, segmentGroup);
|
3781 | return expanded$.pipe(map((children) => new UrlSegmentGroup(consumedSegments, children)));
|
3782 | }
|
3783 | if (childConfig.length === 0 && slicedSegments.length === 0) {
|
3784 | return of(new UrlSegmentGroup(consumedSegments, {}));
|
3785 | }
|
3786 | const matchedOnOutlet = getOutlet(route) === outlet;
|
3787 | const expanded$ = this.expandSegment(childInjector, segmentGroup, childConfig, slicedSegments, matchedOnOutlet ? PRIMARY_OUTLET : outlet, true);
|
3788 | return expanded$.pipe(map((cs) => new UrlSegmentGroup(consumedSegments.concat(cs.segments), cs.children)));
|
3789 | }));
|
3790 | }));
|
3791 | }
|
3792 | getChildConfig(injector, route, segments) {
|
3793 | if (route.children) {
|
3794 | // The children belong to the same module
|
3795 | return of({ routes: route.children, injector });
|
3796 | }
|
3797 | if (route.loadChildren) {
|
3798 | // lazy children belong to the loaded module
|
3799 | if (route._loadedRoutes !== undefined) {
|
3800 | return of({ routes: route._loadedRoutes, injector: route._loadedInjector });
|
3801 | }
|
3802 | return runCanLoadGuards(injector, route, segments, this.urlSerializer)
|
3803 | .pipe(mergeMap((shouldLoadResult) => {
|
3804 | if (shouldLoadResult) {
|
3805 | return this.configLoader.loadChildren(injector, route)
|
3806 | .pipe(tap((cfg) => {
|
3807 | route._loadedRoutes = cfg.routes;
|
3808 | route._loadedInjector = cfg.injector;
|
3809 | }));
|
3810 | }
|
3811 | return canLoadFails(route);
|
3812 | }));
|
3813 | }
|
3814 | return of({ routes: [], injector });
|
3815 | }
|
3816 | lineralizeSegments(route, urlTree) {
|
3817 | let res = [];
|
3818 | let c = urlTree.root;
|
3819 | while (true) {
|
3820 | res = res.concat(c.segments);
|
3821 | if (c.numberOfChildren === 0) {
|
3822 | return of(res);
|
3823 | }
|
3824 | if (c.numberOfChildren > 1 || !c.children[PRIMARY_OUTLET]) {
|
3825 | return namedOutletsRedirect(route.redirectTo);
|
3826 | }
|
3827 | c = c.children[PRIMARY_OUTLET];
|
3828 | }
|
3829 | }
|
3830 | applyRedirectCommands(segments, redirectTo, posParams) {
|
3831 | return this.applyRedirectCreateUrlTree(redirectTo, this.urlSerializer.parse(redirectTo), segments, posParams);
|
3832 | }
|
3833 | applyRedirectCreateUrlTree(redirectTo, urlTree, segments, posParams) {
|
3834 | const newRoot = this.createSegmentGroup(redirectTo, urlTree.root, segments, posParams);
|
3835 | return new UrlTree(newRoot, this.createQueryParams(urlTree.queryParams, this.urlTree.queryParams), urlTree.fragment);
|
3836 | }
|
3837 | createQueryParams(redirectToParams, actualParams) {
|
3838 | const res = {};
|
3839 | forEach(redirectToParams, (v, k) => {
|
3840 | const copySourceValue = typeof v === 'string' && v.startsWith(':');
|
3841 | if (copySourceValue) {
|
3842 | const sourceName = v.substring(1);
|
3843 | res[k] = actualParams[sourceName];
|
3844 | }
|
3845 | else {
|
3846 | res[k] = v;
|
3847 | }
|
3848 | });
|
3849 | return res;
|
3850 | }
|
3851 | createSegmentGroup(redirectTo, group, segments, posParams) {
|
3852 | const updatedSegments = this.createSegments(redirectTo, group.segments, segments, posParams);
|
3853 | let children = {};
|
3854 | forEach(group.children, (child, name) => {
|
3855 | children[name] = this.createSegmentGroup(redirectTo, child, segments, posParams);
|
3856 | });
|
3857 | return new UrlSegmentGroup(updatedSegments, children);
|
3858 | }
|
3859 | createSegments(redirectTo, redirectToSegments, actualSegments, posParams) {
|
3860 | return redirectToSegments.map(s => s.path.startsWith(':') ? this.findPosParam(redirectTo, s, posParams) :
|
3861 | this.findOrReturn(s, actualSegments));
|
3862 | }
|
3863 | findPosParam(redirectTo, redirectToUrlSegment, posParams) {
|
3864 | const pos = posParams[redirectToUrlSegment.path.substring(1)];
|
3865 | if (!pos)
|
3866 | throw new ɵRuntimeError(4001 /* RuntimeErrorCode.MISSING_REDIRECT */, NG_DEV_MODE$7 &&
|
3867 | `Cannot redirect to '${redirectTo}'. Cannot find '${redirectToUrlSegment.path}'.`);
|
3868 | return pos;
|
3869 | }
|
3870 | findOrReturn(redirectToUrlSegment, actualSegments) {
|
3871 | let idx = 0;
|
3872 | for (const s of actualSegments) {
|
3873 | if (s.path === redirectToUrlSegment.path) {
|
3874 | actualSegments.splice(idx);
|
3875 | return s;
|
3876 | }
|
3877 | idx++;
|
3878 | }
|
3879 | return redirectToUrlSegment;
|
3880 | }
|
3881 | }
|
3882 |
|
3883 | function applyRedirects(environmentInjector, configLoader, urlSerializer, config) {
|
3884 | return switchMap(t => applyRedirects$1(environmentInjector, configLoader, urlSerializer, t.extractedUrl, config)
|
3885 | .pipe(map(urlAfterRedirects => ({ ...t, urlAfterRedirects }))));
|
3886 | }
|
3887 |
|
3888 | const NG_DEV_MODE$6 = typeof ngDevMode === 'undefined' || !!ngDevMode;
|
3889 | class NoMatch {
|
3890 | }
|
3891 | function newObservableError(e) {
|
3892 | // TODO(atscott): This pattern is used throughout the router code and can be `throwError` instead.
|
3893 | return new Observable((obs) => obs.error(e));
|
3894 | }
|
3895 | function recognize$1(injector, rootComponentType, config, urlTree, url, urlSerializer, paramsInheritanceStrategy = 'emptyOnly') {
|
3896 | return new Recognizer(injector, rootComponentType, config, urlTree, url, paramsInheritanceStrategy, urlSerializer)
|
3897 | .recognize()
|
3898 | .pipe(switchMap(result => {
|
3899 | if (result === null) {
|
3900 | return newObservableError(new NoMatch());
|
3901 | }
|
3902 | else {
|
3903 | return of(result);
|
3904 | }
|
3905 | }));
|
3906 | }
|
3907 | class Recognizer {
|
3908 | constructor(injector, rootComponentType, config, urlTree, url, paramsInheritanceStrategy, urlSerializer) {
|
3909 | this.injector = injector;
|
3910 | this.rootComponentType = rootComponentType;
|
3911 | this.config = config;
|
3912 | this.urlTree = urlTree;
|
3913 | this.url = url;
|
3914 | this.paramsInheritanceStrategy = paramsInheritanceStrategy;
|
3915 | this.urlSerializer = urlSerializer;
|
3916 | }
|
3917 | recognize() {
|
3918 | const rootSegmentGroup = split(this.urlTree.root, [], [], this.config.filter(c => c.redirectTo === undefined))
|
3919 | .segmentGroup;
|
3920 | return this.processSegmentGroup(this.injector, this.config, rootSegmentGroup, PRIMARY_OUTLET)
|
3921 | .pipe(map(children => {
|
3922 | if (children === null) {
|
3923 | return null;
|
3924 | }
|
3925 | // Use Object.freeze to prevent readers of the Router state from modifying it outside of a
|
3926 | // navigation, resulting in the router being out of sync with the browser.
|
3927 | const root = new ActivatedRouteSnapshot([], Object.freeze({}), Object.freeze({ ...this.urlTree.queryParams }), this.urlTree.fragment, {}, PRIMARY_OUTLET, this.rootComponentType, null, this.urlTree.root, -1, {});
|
3928 | const rootNode = new TreeNode(root, children);
|
3929 | const routeState = new RouterStateSnapshot(this.url, rootNode);
|
3930 | this.inheritParamsAndData(routeState._root);
|
3931 | return routeState;
|
3932 | }));
|
3933 | }
|
3934 | inheritParamsAndData(routeNode) {
|
3935 | const route = routeNode.value;
|
3936 | const i = inheritedParamsDataResolve(route, this.paramsInheritanceStrategy);
|
3937 | route.params = Object.freeze(i.params);
|
3938 | route.data = Object.freeze(i.data);
|
3939 | routeNode.children.forEach(n => this.inheritParamsAndData(n));
|
3940 | }
|
3941 | processSegmentGroup(injector, config, segmentGroup, outlet) {
|
3942 | if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) {
|
3943 | return this.processChildren(injector, config, segmentGroup);
|
3944 | }
|
3945 | return this.processSegment(injector, config, segmentGroup, segmentGroup.segments, outlet);
|
3946 | }
|
3947 | /**
|
3948 | * Matches every child outlet in the `segmentGroup` to a `Route` in the config. Returns `null` if
|
3949 | * we cannot find a match for _any_ of the children.
|
3950 | *
|
3951 | * @param config - The `Routes` to match against
|
3952 | * @param segmentGroup - The `UrlSegmentGroup` whose children need to be matched against the
|
3953 | * config.
|
3954 | */
|
3955 | processChildren(injector, config, segmentGroup) {
|
3956 | return from(Object.keys(segmentGroup.children))
|
3957 | .pipe(concatMap(childOutlet => {
|
3958 | const child = segmentGroup.children[childOutlet];
|
3959 | // Sort the config so that routes with outlets that match the one being activated
|
3960 | // appear first, followed by routes for other outlets, which might match if they have
|
3961 | // an empty path.
|
3962 | const sortedConfig = sortByMatchingOutlets(config, childOutlet);
|
3963 | return this.processSegmentGroup(injector, sortedConfig, child, childOutlet);
|
3964 | }), scan((children, outletChildren) => {
|
3965 | if (!children || !outletChildren)
|
3966 | return null;
|
3967 | children.push(...outletChildren);
|
3968 | return children;
|
3969 | }), takeWhile(children => children !== null), defaultIfEmpty(null), last$1(), map(children => {
|
3970 | if (children === null)
|
3971 | return null;
|
3972 | // Because we may have matched two outlets to the same empty path segment, we can have
|
3973 | // multiple activated results for the same outlet. We should merge the children of
|
3974 | // these results so the final return value is only one `TreeNode` per outlet.
|
3975 | const mergedChildren = mergeEmptyPathMatches(children);
|
3976 | if (NG_DEV_MODE$6) {
|
3977 | // This should really never happen - we are only taking the first match for each
|
3978 | // outlet and merge the empty path matches.
|
3979 | checkOutletNameUniqueness(mergedChildren);
|
3980 | }
|
3981 | sortActivatedRouteSnapshots(mergedChildren);
|
3982 | return mergedChildren;
|
3983 | }));
|
3984 | }
|
3985 | processSegment(injector, routes, segmentGroup, segments, outlet) {
|
3986 | return from(routes).pipe(concatMap(r => {
|
3987 | return this.processSegmentAgainstRoute(r._injector ?? injector, r, segmentGroup, segments, outlet);
|
3988 | }), first((x) => !!x), catchError(e => {
|
3989 | if (isEmptyError(e)) {
|
3990 | if (noLeftoversInUrl(segmentGroup, segments, outlet)) {
|
3991 | return of([]);
|
3992 | }
|
3993 | return of(null);
|
3994 | }
|
3995 | throw e;
|
3996 | }));
|
3997 | }
|
3998 | processSegmentAgainstRoute(injector, route, rawSegment, segments, outlet) {
|
3999 | if (route.redirectTo || !isImmediateMatch(route, rawSegment, segments, outlet))
|
4000 | return of(null);
|
4001 | let matchResult;
|
4002 | if (route.path === '**') {
|
4003 | const params = segments.length > 0 ? last(segments).parameters : {};
|
4004 | const pathIndexShift = getPathIndexShift(rawSegment) + segments.length;
|
4005 | const snapshot = new ActivatedRouteSnapshot(segments, params, Object.freeze({ ...this.urlTree.queryParams }), this.urlTree.fragment, getData(route), getOutlet(route), route.component ?? route._loadedComponent ?? null, route, getSourceSegmentGroup(rawSegment), pathIndexShift, getResolve(route));
|
4006 | matchResult = of({
|
4007 | snapshot,
|
4008 | consumedSegments: [],
|
4009 | remainingSegments: [],
|
4010 | });
|
4011 | }
|
4012 | else {
|
4013 | matchResult =
|
4014 | matchWithChecks(rawSegment, route, segments, injector, this.urlSerializer)
|
4015 | .pipe(map(({ matched, consumedSegments, remainingSegments, parameters }) => {
|
4016 | if (!matched) {
|
4017 | return null;
|
4018 | }
|
4019 | const pathIndexShift = getPathIndexShift(rawSegment) + consumedSegments.length;
|
4020 | const snapshot = new ActivatedRouteSnapshot(consumedSegments, parameters, Object.freeze({ ...this.urlTree.queryParams }), this.urlTree.fragment, getData(route), getOutlet(route), route.component ?? route._loadedComponent ?? null, route, getSourceSegmentGroup(rawSegment), pathIndexShift, getResolve(route));
|
4021 | return { snapshot, consumedSegments, remainingSegments };
|
4022 | }));
|
4023 | }
|
4024 | return matchResult.pipe(switchMap((result) => {
|
4025 | if (result === null) {
|
4026 | return of(null);
|
4027 | }
|
4028 | const { snapshot, consumedSegments, remainingSegments } = result;
|
4029 | // If the route has an injector created from providers, we should start using that.
|
4030 | injector = route._injector ?? injector;
|
4031 | const childInjector = route._loadedInjector ?? injector;
|
4032 | const childConfig = getChildConfig(route);
|
4033 | const { segmentGroup, slicedSegments } = split(rawSegment, consumedSegments, remainingSegments,
|
4034 | // Filter out routes with redirectTo because we are trying to create activated route
|
4035 | // snapshots and don't handle redirects here. That should have been done in
|
4036 | // `applyRedirects`.
|
4037 | childConfig.filter(c => c.redirectTo === undefined));
|
4038 | if (slicedSegments.length === 0 && segmentGroup.hasChildren()) {
|
4039 | return this.processChildren(childInjector, childConfig, segmentGroup).pipe(map(children => {
|
4040 | if (children === null) {
|
4041 | return null;
|
4042 | }
|
4043 | return [new TreeNode(snapshot, children)];
|
4044 | }));
|
4045 | }
|
4046 | if (childConfig.length === 0 && slicedSegments.length === 0) {
|
4047 | return of([new TreeNode(snapshot, [])]);
|
4048 | }
|
4049 | const matchedOnOutlet = getOutlet(route) === outlet;
|
4050 | // If we matched a config due to empty path match on a different outlet, we need to
|
4051 | // continue passing the current outlet for the segment rather than switch to PRIMARY.
|
4052 | // Note that we switch to primary when we have a match because outlet configs look like
|
4053 | // this: {path: 'a', outlet: 'a', children: [
|
4054 | // {path: 'b', component: B},
|
4055 | // {path: 'c', component: C},
|
4056 | // ]}
|
4057 | // Notice that the children of the named outlet are configured with the primary outlet
|
4058 | return this
|
4059 | .processSegment(childInjector, childConfig, segmentGroup, slicedSegments, matchedOnOutlet ? PRIMARY_OUTLET : outlet)
|
4060 | .pipe(map(children => {
|
4061 | if (children === null) {
|
4062 | return null;
|
4063 | }
|
4064 | return [new TreeNode(snapshot, children)];
|
4065 | }));
|
4066 | }));
|
4067 | }
|
4068 | }
|
4069 | function sortActivatedRouteSnapshots(nodes) {
|
4070 | nodes.sort((a, b) => {
|
4071 | if (a.value.outlet === PRIMARY_OUTLET)
|
4072 | return -1;
|
4073 | if (b.value.outlet === PRIMARY_OUTLET)
|
4074 | return 1;
|
4075 | return a.value.outlet.localeCompare(b.value.outlet);
|
4076 | });
|
4077 | }
|
4078 | function getChildConfig(route) {
|
4079 | if (route.children) {
|
4080 | return route.children;
|
4081 | }
|
4082 | if (route.loadChildren) {
|
4083 | return route._loadedRoutes;
|
4084 | }
|
4085 | return [];
|
4086 | }
|
4087 | function hasEmptyPathConfig(node) {
|
4088 | const config = node.value.routeConfig;
|
4089 | return config && config.path === '' && config.redirectTo === undefined;
|
4090 | }
|
4091 | /**
|
4092 | * Finds `TreeNode`s with matching empty path route configs and merges them into `TreeNode` with
|
4093 | * the children from each duplicate. This is necessary because different outlets can match a
|
4094 | * single empty path route config and the results need to then be merged.
|
4095 | */
|
4096 | function mergeEmptyPathMatches(nodes) {
|
4097 | const result = [];
|
4098 | // The set of nodes which contain children that were merged from two duplicate empty path nodes.
|
4099 | const mergedNodes = new Set();
|
4100 | for (const node of nodes) {
|
4101 | if (!hasEmptyPathConfig(node)) {
|
4102 | result.push(node);
|
4103 | continue;
|
4104 | }
|
4105 | const duplicateEmptyPathNode = result.find(resultNode => node.value.routeConfig === resultNode.value.routeConfig);
|
4106 | if (duplicateEmptyPathNode !== undefined) {
|
4107 | duplicateEmptyPathNode.children.push(...node.children);
|
4108 | mergedNodes.add(duplicateEmptyPathNode);
|
4109 | }
|
4110 | else {
|
4111 | result.push(node);
|
4112 | }
|
4113 | }
|
4114 | // For each node which has children from multiple sources, we need to recompute a new `TreeNode`
|
4115 | // by also merging those children. This is necessary when there are multiple empty path configs
|
4116 | // in a row. Put another way: whenever we combine children of two nodes, we need to also check
|
4117 | // if any of those children can be combined into a single node as well.
|
4118 | for (const mergedNode of mergedNodes) {
|
4119 | const mergedChildren = mergeEmptyPathMatches(mergedNode.children);
|
4120 | result.push(new TreeNode(mergedNode.value, mergedChildren));
|
4121 | }
|
4122 | return result.filter(n => !mergedNodes.has(n));
|
4123 | }
|
4124 | function checkOutletNameUniqueness(nodes) {
|
4125 | const names = {};
|
4126 | nodes.forEach(n => {
|
4127 | const routeWithSameOutletName = names[n.value.outlet];
|
4128 | if (routeWithSameOutletName) {
|
4129 | const p = routeWithSameOutletName.url.map(s => s.toString()).join('/');
|
4130 | const c = n.value.url.map(s => s.toString()).join('/');
|
4131 | throw new ɵRuntimeError(4006 /* RuntimeErrorCode.TWO_SEGMENTS_WITH_SAME_OUTLET */, NG_DEV_MODE$6 && `Two segments cannot have the same outlet name: '${p}' and '${c}'.`);
|
4132 | }
|
4133 | names[n.value.outlet] = n.value;
|
4134 | });
|
4135 | }
|
4136 | function getSourceSegmentGroup(segmentGroup) {
|
4137 | let s = segmentGroup;
|
4138 | while (s._sourceSegment) {
|
4139 | s = s._sourceSegment;
|
4140 | }
|
4141 | return s;
|
4142 | }
|
4143 | function getPathIndexShift(segmentGroup) {
|
4144 | let s = segmentGroup;
|
4145 | let res = s._segmentIndexShift ?? 0;
|
4146 | while (s._sourceSegment) {
|
4147 | s = s._sourceSegment;
|
4148 | res += s._segmentIndexShift ?? 0;
|
4149 | }
|
4150 | return res - 1;
|
4151 | }
|
4152 | function getCorrectedPathIndexShift(segmentGroup) {
|
4153 | let s = segmentGroup;
|
4154 | let res = s._segmentIndexShiftCorrected ?? s._segmentIndexShift ?? 0;
|
4155 | while (s._sourceSegment) {
|
4156 | s = s._sourceSegment;
|
4157 | res += s._segmentIndexShiftCorrected ?? s._segmentIndexShift ?? 0;
|
4158 | }
|
4159 | return res - 1;
|
4160 | }
|
4161 | function getData(route) {
|
4162 | return route.data || {};
|
4163 | }
|
4164 | function getResolve(route) {
|
4165 | return route.resolve || {};
|
4166 | }
|
4167 |
|
4168 | function recognize(injector, rootComponentType, config, serializer, paramsInheritanceStrategy) {
|
4169 | return mergeMap(t => recognize$1(injector, rootComponentType, config, t.urlAfterRedirects, serializer.serialize(t.urlAfterRedirects), serializer, paramsInheritanceStrategy)
|
4170 | .pipe(map(targetSnapshot => ({ ...t, targetSnapshot }))));
|
4171 | }
|
4172 |
|
4173 | function resolveData(paramsInheritanceStrategy, injector) {
|
4174 | return mergeMap(t => {
|
4175 | const { targetSnapshot, guards: { canActivateChecks } } = t;
|
4176 | if (!canActivateChecks.length) {
|
4177 | return of(t);
|
4178 | }
|
4179 | let canActivateChecksResolved = 0;
|
4180 | return from(canActivateChecks)
|
4181 | .pipe(concatMap(check => runResolve(check.route, targetSnapshot, paramsInheritanceStrategy, injector)), tap(() => canActivateChecksResolved++), takeLast(1), mergeMap(_ => canActivateChecksResolved === canActivateChecks.length ? of(t) : EMPTY));
|
4182 | });
|
4183 | }
|
4184 | function runResolve(futureARS, futureRSS, paramsInheritanceStrategy, injector) {
|
4185 | const config = futureARS.routeConfig;
|
4186 | const resolve = futureARS._resolve;
|
4187 | if (config?.title !== undefined && !hasStaticTitle(config)) {
|
4188 | resolve[RouteTitleKey] = config.title;
|
4189 | }
|
4190 | return resolveNode(resolve, futureARS, futureRSS, injector).pipe(map((resolvedData) => {
|
4191 | futureARS._resolvedData = resolvedData;
|
4192 | futureARS.data = inheritedParamsDataResolve(futureARS, paramsInheritanceStrategy).resolve;
|
4193 | if (config && hasStaticTitle(config)) {
|
4194 | futureARS.data[RouteTitleKey] = config.title;
|
4195 | }
|
4196 | return null;
|
4197 | }));
|
4198 | }
|
4199 | function resolveNode(resolve, futureARS, futureRSS, injector) {
|
4200 | const keys = getDataKeys(resolve);
|
4201 | if (keys.length === 0) {
|
4202 | return of({});
|
4203 | }
|
4204 | const data = {};
|
4205 | return from(keys).pipe(mergeMap(key => getResolver(resolve[key], futureARS, futureRSS, injector)
|
4206 | .pipe(first(), tap((value) => {
|
4207 | data[key] = value;
|
4208 | }))), takeLast(1), mapTo(data), catchError((e) => isEmptyError(e) ? EMPTY : throwError(e)));
|
4209 | }
|
4210 | function getDataKeys(obj) {
|
4211 | return [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)];
|
4212 | }
|
4213 | function getResolver(injectionToken, futureARS, futureRSS, injector) {
|
4214 | const closestInjector = getClosestRouteInjector(futureARS) ?? injector;
|
4215 | const resolver = getTokenOrFunctionIdentity(injectionToken, closestInjector);
|
4216 | const resolverValue = resolver.resolve ?
|
4217 | resolver.resolve(futureARS, futureRSS) :
|
4218 | closestInjector.runInContext(() => resolver(futureARS, futureRSS));
|
4219 | return wrapIntoObservable(resolverValue);
|
4220 | }
|
4221 | function hasStaticTitle(config) {
|
4222 | return typeof config.title === 'string' || config.title === null;
|
4223 | }
|
4224 |
|
4225 | /**
|
4226 | * Perform a side effect through a switchMap for every emission on the source Observable,
|
4227 | * but return an Observable that is identical to the source. It's essentially the same as
|
4228 | * the `tap` operator, but if the side effectful `next` function returns an ObservableInput,
|
4229 | * it will wait before continuing with the original value.
|
4230 | */
|
4231 | function switchTap(next) {
|
4232 | return switchMap(v => {
|
4233 | const nextResult = next(v);
|
4234 | if (nextResult) {
|
4235 | return from(nextResult).pipe(map(() => v));
|
4236 | }
|
4237 | return of(v);
|
4238 | });
|
4239 | }
|
4240 |
|
4241 | // This file exists to support the legacy `loadChildren: string` behavior being patched back into
|
4242 | // Angular.
|
4243 | function deprecatedLoadChildrenString(injector, loadChildren) {
|
4244 | return null;
|
4245 | }
|
4246 |
|
4247 | const NG_DEV_MODE$5 = typeof ngDevMode === 'undefined' || !!ngDevMode;
|
4248 | /**
|
4249 | * The [DI token](guide/glossary/#di-token) for a router configuration.
|
4250 | *
|
4251 | * `ROUTES` is a low level API for router configuration via dependency injection.
|
4252 | *
|
4253 | * We recommend that in almost all cases to use higher level APIs such as `RouterModule.forRoot()`,
|
4254 | * `provideRouter`, or `Router.resetConfig()`.
|
4255 | *
|
4256 | * @publicApi
|
4257 | */
|
4258 | const ROUTES = new InjectionToken('ROUTES');
|
4259 | class RouterConfigLoader {
|
4260 | constructor(injector, compiler) {
|
4261 | this.injector = injector;
|
4262 | this.compiler = compiler;
|
4263 | this.componentLoaders = new WeakMap();
|
4264 | this.childrenLoaders = new WeakMap();
|
4265 | }
|
4266 | loadComponent(route) {
|
4267 | if (this.componentLoaders.get(route)) {
|
4268 | return this.componentLoaders.get(route);
|
4269 | }
|
4270 | else if (route._loadedComponent) {
|
4271 | return of(route._loadedComponent);
|
4272 | }
|
4273 | if (this.onLoadStartListener) {
|
4274 | this.onLoadStartListener(route);
|
4275 | }
|
4276 | const loadRunner = wrapIntoObservable(route.loadComponent())
|
4277 | .pipe(map(maybeUnwrapDefaultExport), tap(component => {
|
4278 | if (this.onLoadEndListener) {
|
4279 | this.onLoadEndListener(route);
|
4280 | }
|
4281 | NG_DEV_MODE$5 && assertStandalone(route.path ?? '', component);
|
4282 | route._loadedComponent = component;
|
4283 | }), finalize(() => {
|
4284 | this.componentLoaders.delete(route);
|
4285 | }));
|
4286 | // Use custom ConnectableObservable as share in runners pipe increasing the bundle size too much
|
4287 | const loader = new ConnectableObservable(loadRunner, () => new Subject()).pipe(refCount());
|
4288 | this.componentLoaders.set(route, loader);
|
4289 | return loader;
|
4290 | }
|
4291 | loadChildren(parentInjector, route) {
|
4292 | if (this.childrenLoaders.get(route)) {
|
4293 | return this.childrenLoaders.get(route);
|
4294 | }
|
4295 | else if (route._loadedRoutes) {
|
4296 | return of({ routes: route._loadedRoutes, injector: route._loadedInjector });
|
4297 | }
|
4298 | if (this.onLoadStartListener) {
|
4299 | this.onLoadStartListener(route);
|
4300 | }
|
4301 | const moduleFactoryOrRoutes$ = this.loadModuleFactoryOrRoutes(route.loadChildren);
|
4302 | const loadRunner = moduleFactoryOrRoutes$.pipe(map((factoryOrRoutes) => {
|
4303 | if (this.onLoadEndListener) {
|
4304 | this.onLoadEndListener(route);
|
4305 | }
|
4306 | // This injector comes from the `NgModuleRef` when lazy loading an `NgModule`. There is no
|
4307 | // injector associated with lazy loading a `Route` array.
|
4308 | let injector;
|
4309 | let rawRoutes;
|
4310 | let requireStandaloneComponents = false;
|
4311 | if (Array.isArray(factoryOrRoutes)) {
|
4312 | rawRoutes = factoryOrRoutes;
|
4313 | requireStandaloneComponents = true;
|
4314 | }
|
4315 | else {
|
4316 | injector = factoryOrRoutes.create(parentInjector).injector;
|
4317 | // When loading a module that doesn't provide `RouterModule.forChild()` preloader
|
4318 | // will get stuck in an infinite loop. The child module's Injector will look to
|
4319 | // its parent `Injector` when it doesn't find any ROUTES so it will return routes
|
4320 | // for it's parent module instead.
|
4321 | rawRoutes = flatten(injector.get(ROUTES, [], InjectFlags.Self | InjectFlags.Optional));
|
4322 | }
|
4323 | const routes = rawRoutes.map(standardizeConfig);
|
4324 | NG_DEV_MODE$5 && validateConfig(routes, route.path, requireStandaloneComponents);
|
4325 | return { routes, injector };
|
4326 | }), finalize(() => {
|
4327 | this.childrenLoaders.delete(route);
|
4328 | }));
|
4329 | // Use custom ConnectableObservable as share in runners pipe increasing the bundle size too much
|
4330 | const loader = new ConnectableObservable(loadRunner, () => new Subject())
|
4331 | .pipe(refCount());
|
4332 | this.childrenLoaders.set(route, loader);
|
4333 | return loader;
|
4334 | }
|
4335 | loadModuleFactoryOrRoutes(loadChildren) {
|
4336 | const deprecatedResult = deprecatedLoadChildrenString(this.injector, loadChildren);
|
4337 | if (deprecatedResult) {
|
4338 | return deprecatedResult;
|
4339 | }
|
4340 | return wrapIntoObservable(loadChildren())
|
4341 | .pipe(map(maybeUnwrapDefaultExport), mergeMap((t) => {
|
4342 | if (t instanceof NgModuleFactory || Array.isArray(t)) {
|
4343 | return of(t);
|
4344 | }
|
4345 | else {
|
4346 | return from(this.compiler.compileModuleAsync(t));
|
4347 | }
|
4348 | }));
|
4349 | }
|
4350 | }
|
4351 | RouterConfigLoader.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterConfigLoader, deps: [{ token: i0.Injector }, { token: i0.Compiler }], target: i0.ɵɵFactoryTarget.Injectable });
|
4352 | RouterConfigLoader.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterConfigLoader, providedIn: 'root' });
|
4353 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterConfigLoader, decorators: [{
|
4354 | type: Injectable,
|
4355 | args: [{ providedIn: 'root' }]
|
4356 | }], ctorParameters: function () { return [{ type: i0.Injector }, { type: i0.Compiler }]; } });
|
4357 | function isWrappedDefaultExport(value) {
|
4358 | // We use `in` here with a string key `'default'`, because we expect `DefaultExport` objects to be
|
4359 | // dynamically imported ES modules with a spec-mandated `default` key. Thus we don't expect that
|
4360 | // `default` will be a renamed property.
|
4361 | return value && typeof value === 'object' && 'default' in value;
|
4362 | }
|
4363 | function maybeUnwrapDefaultExport(input) {
|
4364 | // As per `isWrappedDefaultExport`, the `default` key here is generated by the browser and not
|
4365 | // subject to property renaming, so we reference it with bracket access.
|
4366 | return isWrappedDefaultExport(input) ? input['default'] : input;
|
4367 | }
|
4368 |
|
4369 | const NG_DEV_MODE$4 = typeof ngDevMode === 'undefined' || !!ngDevMode;
|
4370 | class NavigationTransitions {
|
4371 | get hasRequestedNavigation() {
|
4372 | return this.navigationId !== 0;
|
4373 | }
|
4374 | constructor() {
|
4375 | this.currentNavigation = null;
|
4376 | this.lastSuccessfulNavigation = null;
|
4377 | this.events = new Subject();
|
4378 | this.configLoader = inject(RouterConfigLoader);
|
4379 | this.environmentInjector = inject(EnvironmentInjector);
|
4380 | this.urlSerializer = inject(UrlSerializer);
|
4381 | this.rootContexts = inject(ChildrenOutletContexts);
|
4382 | this.navigationId = 0;
|
4383 | /**
|
4384 | * Hook that enables you to pause navigation after the preactivation phase.
|
4385 | * Used by `RouterModule`.
|
4386 | *
|
4387 | * @internal
|
4388 | */
|
4389 | this.afterPreactivation = () => of(void 0);
|
4390 | /** @internal */
|
4391 | this.rootComponentType = null;
|
4392 | const onLoadStart = (r) => this.events.next(new RouteConfigLoadStart(r));
|
4393 | const onLoadEnd = (r) => this.events.next(new RouteConfigLoadEnd(r));
|
4394 | this.configLoader.onLoadEndListener = onLoadEnd;
|
4395 | this.configLoader.onLoadStartListener = onLoadStart;
|
4396 | }
|
4397 | complete() {
|
4398 | this.transitions?.complete();
|
4399 | }
|
4400 | handleNavigationRequest(request) {
|
4401 | const id = ++this.navigationId;
|
4402 | this.transitions?.next({ ...this.transitions.value, ...request, id });
|
4403 | }
|
4404 | setupNavigations(router) {
|
4405 | this.transitions = new BehaviorSubject({
|
4406 | id: 0,
|
4407 | targetPageId: 0,
|
4408 | currentUrlTree: router.currentUrlTree,
|
4409 | currentRawUrl: router.currentUrlTree,
|
4410 | extractedUrl: router.urlHandlingStrategy.extract(router.currentUrlTree),
|
4411 | urlAfterRedirects: router.urlHandlingStrategy.extract(router.currentUrlTree),
|
4412 | rawUrl: router.currentUrlTree,
|
4413 | extras: {},
|
4414 | resolve: null,
|
4415 | reject: null,
|
4416 | promise: Promise.resolve(true),
|
4417 | source: IMPERATIVE_NAVIGATION,
|
4418 | restoredState: null,
|
4419 | currentSnapshot: router.routerState.snapshot,
|
4420 | targetSnapshot: null,
|
4421 | currentRouterState: router.routerState,
|
4422 | targetRouterState: null,
|
4423 | guards: { canActivateChecks: [], canDeactivateChecks: [] },
|
4424 | guardsResult: null,
|
4425 | });
|
4426 | return this.transitions.pipe(filter(t => t.id !== 0),
|
4427 | // Extract URL
|
4428 | map(t => ({ ...t, extractedUrl: router.urlHandlingStrategy.extract(t.rawUrl) })),
|
4429 | // Using switchMap so we cancel executing navigations when a new one comes in
|
4430 | switchMap(overallTransitionState => {
|
4431 | let completed = false;
|
4432 | let errored = false;
|
4433 | return of(overallTransitionState)
|
4434 | .pipe(
|
4435 | // Store the Navigation object
|
4436 | tap(t => {
|
4437 | this.currentNavigation = {
|
4438 | id: t.id,
|
4439 | initialUrl: t.rawUrl,
|
4440 | extractedUrl: t.extractedUrl,
|
4441 | trigger: t.source,
|
4442 | extras: t.extras,
|
4443 | previousNavigation: !this.lastSuccessfulNavigation ? null : {
|
4444 | ...this.lastSuccessfulNavigation,
|
4445 | previousNavigation: null,
|
4446 | },
|
4447 | };
|
4448 | }), switchMap(t => {
|
4449 | const browserUrlTree = router.browserUrlTree.toString();
|
4450 | const urlTransition = !router.navigated ||
|
4451 | t.extractedUrl.toString() !== browserUrlTree ||
|
4452 | // Navigations which succeed or ones which fail and are cleaned up
|
4453 | // correctly should result in `browserUrlTree` and `currentUrlTree`
|
4454 | // matching. If this is not the case, assume something went wrong and
|
4455 | // try processing the URL again.
|
4456 | browserUrlTree !== router.currentUrlTree.toString();
|
4457 | const onSameUrlNavigation = t.extras.onSameUrlNavigation ?? router.onSameUrlNavigation;
|
4458 | if (!urlTransition && onSameUrlNavigation !== 'reload') {
|
4459 | const reason = NG_DEV_MODE$4 ?
|
4460 | `Navigation to ${t.rawUrl} was ignored because it is the same as the current Router URL.` :
|
4461 | '';
|
4462 | this.events.next(new NavigationSkipped(t.id, router.serializeUrl(overallTransitionState.rawUrl), reason, 0 /* NavigationSkippedCode.IgnoredSameUrlNavigation */));
|
4463 | router.rawUrlTree = t.rawUrl;
|
4464 | t.resolve(null);
|
4465 | return EMPTY;
|
4466 | }
|
4467 | if (router.urlHandlingStrategy.shouldProcessUrl(t.rawUrl)) {
|
4468 | // If the source of the navigation is from a browser event, the URL is
|
4469 | // already updated. We already need to sync the internal state.
|
4470 | if (isBrowserTriggeredNavigation(t.source)) {
|
4471 | router.browserUrlTree = t.extractedUrl;
|
4472 | }
|
4473 | return of(t).pipe(
|
4474 | // Fire NavigationStart event
|
4475 | switchMap(t => {
|
4476 | const transition = this.transitions?.getValue();
|
4477 | this.events.next(new NavigationStart(t.id, this.urlSerializer.serialize(t.extractedUrl), t.source, t.restoredState));
|
4478 | if (transition !== this.transitions?.getValue()) {
|
4479 | return EMPTY;
|
4480 | }
|
4481 | // This delay is required to match old behavior that forced
|
4482 | // navigation to always be async
|
4483 | return Promise.resolve(t);
|
4484 | }),
|
4485 | // ApplyRedirects
|
4486 | applyRedirects(this.environmentInjector, this.configLoader, this.urlSerializer, router.config),
|
4487 | // Update the currentNavigation
|
4488 | // `urlAfterRedirects` is guaranteed to be set after this point
|
4489 | tap(t => {
|
4490 | this.currentNavigation = {
|
4491 | ...this.currentNavigation,
|
4492 | finalUrl: t.urlAfterRedirects
|
4493 | };
|
4494 | overallTransitionState.urlAfterRedirects = t.urlAfterRedirects;
|
4495 | }),
|
4496 | // Recognize
|
4497 | recognize(this.environmentInjector, this.rootComponentType, router.config, this.urlSerializer, router.paramsInheritanceStrategy),
|
4498 | // Update URL if in `eager` update mode
|
4499 | tap(t => {
|
4500 | overallTransitionState.targetSnapshot = t.targetSnapshot;
|
4501 | if (router.urlUpdateStrategy === 'eager') {
|
4502 | if (!t.extras.skipLocationChange) {
|
4503 | const rawUrl = router.urlHandlingStrategy.merge(t.urlAfterRedirects, t.rawUrl);
|
4504 | router.setBrowserUrl(rawUrl, t);
|
4505 | }
|
4506 | router.browserUrlTree = t.urlAfterRedirects;
|
4507 | }
|
4508 | // Fire RoutesRecognized
|
4509 | const routesRecognized = new RoutesRecognized(t.id, this.urlSerializer.serialize(t.extractedUrl), this.urlSerializer.serialize(t.urlAfterRedirects), t.targetSnapshot);
|
4510 | this.events.next(routesRecognized);
|
4511 | }));
|
4512 | }
|
4513 | else if (urlTransition &&
|
4514 | router.urlHandlingStrategy.shouldProcessUrl(router.rawUrlTree)) {
|
4515 | /* When the current URL shouldn't be processed, but the previous one
|
4516 | * was, we handle this "error condition" by navigating to the
|
4517 | * previously successful URL, but leaving the URL intact.*/
|
4518 | const { id, extractedUrl, source, restoredState, extras } = t;
|
4519 | const navStart = new NavigationStart(id, this.urlSerializer.serialize(extractedUrl), source, restoredState);
|
4520 | this.events.next(navStart);
|
4521 | const targetSnapshot = createEmptyState(extractedUrl, this.rootComponentType).snapshot;
|
4522 | overallTransitionState = {
|
4523 | ...t,
|
4524 | targetSnapshot,
|
4525 | urlAfterRedirects: extractedUrl,
|
4526 | extras: { ...extras, skipLocationChange: false, replaceUrl: false },
|
4527 | };
|
4528 | return of(overallTransitionState);
|
4529 | }
|
4530 | else {
|
4531 | /* When neither the current or previous URL can be processed, do
|
4532 | * nothing other than update router's internal reference to the
|
4533 | * current "settled" URL. This way the next navigation will be coming
|
4534 | * from the current URL in the browser.
|
4535 | */
|
4536 | const reason = NG_DEV_MODE$4 ?
|
4537 | `Navigation was ignored because the UrlHandlingStrategy` +
|
4538 | ` indicated neither the current URL ${router.rawUrlTree} nor target URL ${t.rawUrl} should be processed.` :
|
4539 | '';
|
4540 | this.events.next(new NavigationSkipped(t.id, router.serializeUrl(overallTransitionState.extractedUrl), reason, 1 /* NavigationSkippedCode.IgnoredByUrlHandlingStrategy */));
|
4541 | router.rawUrlTree = t.rawUrl;
|
4542 | t.resolve(null);
|
4543 | return EMPTY;
|
4544 | }
|
4545 | }),
|
4546 | // --- GUARDS ---
|
4547 | tap(t => {
|
4548 | const guardsStart = new GuardsCheckStart(t.id, this.urlSerializer.serialize(t.extractedUrl), this.urlSerializer.serialize(t.urlAfterRedirects), t.targetSnapshot);
|
4549 | this.events.next(guardsStart);
|
4550 | }), map(t => {
|
4551 | overallTransitionState = {
|
4552 | ...t,
|
4553 | guards: getAllRouteGuards(t.targetSnapshot, t.currentSnapshot, this.rootContexts)
|
4554 | };
|
4555 | return overallTransitionState;
|
4556 | }), checkGuards(this.environmentInjector, (evt) => this.events.next(evt)), tap(t => {
|
4557 | overallTransitionState.guardsResult = t.guardsResult;
|
4558 | if (isUrlTree(t.guardsResult)) {
|
4559 | throw redirectingNavigationError(this.urlSerializer, t.guardsResult);
|
4560 | }
|
4561 | const guardsEnd = new GuardsCheckEnd(t.id, this.urlSerializer.serialize(t.extractedUrl), this.urlSerializer.serialize(t.urlAfterRedirects), t.targetSnapshot, !!t.guardsResult);
|
4562 | this.events.next(guardsEnd);
|
4563 | }), filter(t => {
|
4564 | if (!t.guardsResult) {
|
4565 | router.restoreHistory(t);
|
4566 | this.cancelNavigationTransition(t, '', 3 /* NavigationCancellationCode.GuardRejected */);
|
4567 | return false;
|
4568 | }
|
4569 | return true;
|
4570 | }),
|
4571 | // --- RESOLVE ---
|
4572 | switchTap(t => {
|
4573 | if (t.guards.canActivateChecks.length) {
|
4574 | return of(t).pipe(tap(t => {
|
4575 | const resolveStart = new ResolveStart(t.id, this.urlSerializer.serialize(t.extractedUrl), this.urlSerializer.serialize(t.urlAfterRedirects), t.targetSnapshot);
|
4576 | this.events.next(resolveStart);
|
4577 | }), switchMap(t => {
|
4578 | let dataResolved = false;
|
4579 | return of(t).pipe(resolveData(router.paramsInheritanceStrategy, this.environmentInjector), tap({
|
4580 | next: () => dataResolved = true,
|
4581 | complete: () => {
|
4582 | if (!dataResolved) {
|
4583 | router.restoreHistory(t);
|
4584 | this.cancelNavigationTransition(t, NG_DEV_MODE$4 ?
|
4585 | `At least one route resolver didn't emit any value.` :
|
4586 | '', 2 /* NavigationCancellationCode.NoDataFromResolver */);
|
4587 | }
|
4588 | }
|
4589 | }));
|
4590 | }), tap(t => {
|
4591 | const resolveEnd = new ResolveEnd(t.id, this.urlSerializer.serialize(t.extractedUrl), this.urlSerializer.serialize(t.urlAfterRedirects), t.targetSnapshot);
|
4592 | this.events.next(resolveEnd);
|
4593 | }));
|
4594 | }
|
4595 | return undefined;
|
4596 | }),
|
4597 | // --- LOAD COMPONENTS ---
|
4598 | switchTap((t) => {
|
4599 | const loadComponents = (route) => {
|
4600 | const loaders = [];
|
4601 | if (route.routeConfig?.loadComponent &&
|
4602 | !route.routeConfig._loadedComponent) {
|
4603 | loaders.push(this.configLoader.loadComponent(route.routeConfig)
|
4604 | .pipe(tap(loadedComponent => {
|
4605 | route.component = loadedComponent;
|
4606 | }), map(() => void 0)));
|
4607 | }
|
4608 | for (const child of route.children) {
|
4609 | loaders.push(...loadComponents(child));
|
4610 | }
|
4611 | return loaders;
|
4612 | };
|
4613 | return combineLatest(loadComponents(t.targetSnapshot.root))
|
4614 | .pipe(defaultIfEmpty(), take(1));
|
4615 | }), switchTap(() => this.afterPreactivation()), map((t) => {
|
4616 | const targetRouterState = createRouterState(router.routeReuseStrategy, t.targetSnapshot, t.currentRouterState);
|
4617 | overallTransitionState = { ...t, targetRouterState };
|
4618 | return (overallTransitionState);
|
4619 | }),
|
4620 | /* Once here, we are about to activate synchronously. The assumption is
|
4621 | this will succeed, and user code may read from the Router service.
|
4622 | Therefore before activation, we need to update router properties storing
|
4623 | the current URL and the RouterState, as well as updated the browser URL.
|
4624 | All this should happen *before* activating. */
|
4625 | tap((t) => {
|
4626 | router.currentUrlTree = t.urlAfterRedirects;
|
4627 | router.rawUrlTree =
|
4628 | router.urlHandlingStrategy.merge(t.urlAfterRedirects, t.rawUrl);
|
4629 | router.routerState =
|
4630 | t.targetRouterState;
|
4631 | if (router.urlUpdateStrategy === 'deferred') {
|
4632 | if (!t.extras.skipLocationChange) {
|
4633 | router.setBrowserUrl(router.rawUrlTree, t);
|
4634 | }
|
4635 | router.browserUrlTree = t.urlAfterRedirects;
|
4636 | }
|
4637 | }), activateRoutes(this.rootContexts, router.routeReuseStrategy, (evt) => this.events.next(evt)), tap({
|
4638 | next: (t) => {
|
4639 | completed = true;
|
4640 | this.lastSuccessfulNavigation = this.currentNavigation;
|
4641 | router.navigated = true;
|
4642 | this.events.next(new NavigationEnd(t.id, this.urlSerializer.serialize(t.extractedUrl), this.urlSerializer.serialize(router.currentUrlTree)));
|
4643 | router.titleStrategy?.updateTitle(t.targetRouterState.snapshot);
|
4644 | t.resolve(true);
|
4645 | },
|
4646 | complete: () => {
|
4647 | completed = true;
|
4648 | }
|
4649 | }), finalize(() => {
|
4650 | /* When the navigation stream finishes either through error or success,
|
4651 | * we set the `completed` or `errored` flag. However, there are some
|
4652 | * situations where we could get here without either of those being set.
|
4653 | * For instance, a redirect during NavigationStart. Therefore, this is a
|
4654 | * catch-all to make sure the NavigationCancel event is fired when a
|
4655 | * navigation gets cancelled but not caught by other means. */
|
4656 | if (!completed && !errored) {
|
4657 | const cancelationReason = NG_DEV_MODE$4 ?
|
4658 | `Navigation ID ${overallTransitionState
|
4659 | .id} is not equal to the current navigation id ${this.navigationId}` :
|
4660 | '';
|
4661 | this.cancelNavigationTransition(overallTransitionState, cancelationReason, 1 /* NavigationCancellationCode.SupersededByNewNavigation */);
|
4662 | }
|
4663 | // Only clear current navigation if it is still set to the one that
|
4664 | // finalized.
|
4665 | if (this.currentNavigation?.id === overallTransitionState.id) {
|
4666 | this.currentNavigation = null;
|
4667 | }
|
4668 | }), catchError((e) => {
|
4669 | errored = true;
|
4670 | /* This error type is issued during Redirect, and is handled as a
|
4671 | * cancellation rather than an error. */
|
4672 | if (isNavigationCancelingError$1(e)) {
|
4673 | if (!isRedirectingNavigationCancelingError$1(e)) {
|
4674 | // Set property only if we're not redirecting. If we landed on a page
|
4675 | // and redirect to `/` route, the new navigation is going to see the
|
4676 | // `/` isn't a change from the default currentUrlTree and won't
|
4677 | // navigate. This is only applicable with initial navigation, so
|
4678 | // setting `navigated` only when not redirecting resolves this
|
4679 | // scenario.
|
4680 | router.navigated = true;
|
4681 | router.restoreHistory(overallTransitionState, true);
|
4682 | }
|
4683 | const navCancel = new NavigationCancel(overallTransitionState.id, this.urlSerializer.serialize(overallTransitionState.extractedUrl), e.message, e.cancellationCode);
|
4684 | this.events.next(navCancel);
|
4685 | // When redirecting, we need to delay resolving the navigation
|
4686 | // promise and push it to the redirect navigation
|
4687 | if (!isRedirectingNavigationCancelingError$1(e)) {
|
4688 | overallTransitionState.resolve(false);
|
4689 | }
|
4690 | else {
|
4691 | const mergedTree = router.urlHandlingStrategy.merge(e.url, router.rawUrlTree);
|
4692 | const extras = {
|
4693 | skipLocationChange: overallTransitionState.extras.skipLocationChange,
|
4694 | // The URL is already updated at this point if we have 'eager' URL
|
4695 | // updates or if the navigation was triggered by the browser (back
|
4696 | // button, URL bar, etc). We want to replace that item in history
|
4697 | // if the navigation is rejected.
|
4698 | replaceUrl: router.urlUpdateStrategy === 'eager' ||
|
4699 | isBrowserTriggeredNavigation(overallTransitionState.source)
|
4700 | };
|
4701 | router.scheduleNavigation(mergedTree, IMPERATIVE_NAVIGATION, null, extras, {
|
4702 | resolve: overallTransitionState.resolve,
|
4703 | reject: overallTransitionState.reject,
|
4704 | promise: overallTransitionState.promise
|
4705 | });
|
4706 | }
|
4707 | /* All other errors should reset to the router's internal URL reference
|
4708 | * to the pre-error state. */
|
4709 | }
|
4710 | else {
|
4711 | router.restoreHistory(overallTransitionState, true);
|
4712 | const navError = new NavigationError(overallTransitionState.id, this.urlSerializer.serialize(overallTransitionState.extractedUrl), e, overallTransitionState.targetSnapshot ?? undefined);
|
4713 | this.events.next(navError);
|
4714 | try {
|
4715 | overallTransitionState.resolve(router.errorHandler(e));
|
4716 | }
|
4717 | catch (ee) {
|
4718 | overallTransitionState.reject(ee);
|
4719 | }
|
4720 | }
|
4721 | return EMPTY;
|
4722 | }));
|
4723 | // casting because `pipe` returns observable({}) when called with 8+ arguments
|
4724 | }));
|
4725 | }
|
4726 | cancelNavigationTransition(t, reason, code) {
|
4727 | const navCancel = new NavigationCancel(t.id, this.urlSerializer.serialize(t.extractedUrl), reason, code);
|
4728 | this.events.next(navCancel);
|
4729 | t.resolve(false);
|
4730 | }
|
4731 | }
|
4732 | NavigationTransitions.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: NavigationTransitions, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
4733 | NavigationTransitions.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: NavigationTransitions, providedIn: 'root' });
|
4734 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: NavigationTransitions, decorators: [{
|
4735 | type: Injectable,
|
4736 | args: [{ providedIn: 'root' }]
|
4737 | }], ctorParameters: function () { return []; } });
|
4738 | function isBrowserTriggeredNavigation(source) {
|
4739 | return source !== IMPERATIVE_NAVIGATION;
|
4740 | }
|
4741 |
|
4742 | /**
|
4743 | * Provides a strategy for setting the page title after a router navigation.
|
4744 | *
|
4745 | * The built-in implementation traverses the router state snapshot and finds the deepest primary
|
4746 | * outlet with `title` property. Given the `Routes` below, navigating to
|
4747 | * `/base/child(popup:aux)` would result in the document title being set to "child".
|
4748 | * ```
|
4749 | * [
|
4750 | * {path: 'base', title: 'base', children: [
|
4751 | * {path: 'child', title: 'child'},
|
4752 | * ],
|
4753 | * {path: 'aux', outlet: 'popup', title: 'popupTitle'}
|
4754 | * ]
|
4755 | * ```
|
4756 | *
|
4757 | * This class can be used as a base class for custom title strategies. That is, you can create your
|
4758 | * own class that extends the `TitleStrategy`. Note that in the above example, the `title`
|
4759 | * from the named outlet is never used. However, a custom strategy might be implemented to
|
4760 | * incorporate titles in named outlets.
|
4761 | *
|
4762 | * @publicApi
|
4763 | * @see [Page title guide](guide/router#setting-the-page-title)
|
4764 | */
|
4765 | class TitleStrategy {
|
4766 | /**
|
4767 | * @returns The `title` of the deepest primary route.
|
4768 | */
|
4769 | buildTitle(snapshot) {
|
4770 | let pageTitle;
|
4771 | let route = snapshot.root;
|
4772 | while (route !== undefined) {
|
4773 | pageTitle = this.getResolvedTitleForRoute(route) ?? pageTitle;
|
4774 | route = route.children.find(child => child.outlet === PRIMARY_OUTLET);
|
4775 | }
|
4776 | return pageTitle;
|
4777 | }
|
4778 | /**
|
4779 | * Given an `ActivatedRouteSnapshot`, returns the final value of the
|
4780 | * `Route.title` property, which can either be a static string or a resolved value.
|
4781 | */
|
4782 | getResolvedTitleForRoute(snapshot) {
|
4783 | return snapshot.data[RouteTitleKey];
|
4784 | }
|
4785 | }
|
4786 | TitleStrategy.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: TitleStrategy, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
4787 | TitleStrategy.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: TitleStrategy, providedIn: 'root', useFactory: () => inject(DefaultTitleStrategy) });
|
4788 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: TitleStrategy, decorators: [{
|
4789 | type: Injectable,
|
4790 | args: [{ providedIn: 'root', useFactory: () => inject(DefaultTitleStrategy) }]
|
4791 | }] });
|
4792 | /**
|
4793 | * The default `TitleStrategy` used by the router that updates the title using the `Title` service.
|
4794 | */
|
4795 | class DefaultTitleStrategy extends TitleStrategy {
|
4796 | constructor(title) {
|
4797 | super();
|
4798 | this.title = title;
|
4799 | }
|
4800 | /**
|
4801 | * Sets the title of the browser to the given value.
|
4802 | *
|
4803 | * @param title The `pageTitle` from the deepest primary route.
|
4804 | */
|
4805 | updateTitle(snapshot) {
|
4806 | const title = this.buildTitle(snapshot);
|
4807 | if (title !== undefined) {
|
4808 | this.title.setTitle(title);
|
4809 | }
|
4810 | }
|
4811 | }
|
4812 | DefaultTitleStrategy.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultTitleStrategy, deps: [{ token: i1.Title }], target: i0.ɵɵFactoryTarget.Injectable });
|
4813 | DefaultTitleStrategy.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultTitleStrategy, providedIn: 'root' });
|
4814 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultTitleStrategy, decorators: [{
|
4815 | type: Injectable,
|
4816 | args: [{ providedIn: 'root' }]
|
4817 | }], ctorParameters: function () { return [{ type: i1.Title }]; } });
|
4818 |
|
4819 | /**
|
4820 | * @description
|
4821 | *
|
4822 | * Provides a way to customize when activated routes get reused.
|
4823 | *
|
4824 | * @publicApi
|
4825 | */
|
4826 | class RouteReuseStrategy {
|
4827 | }
|
4828 | RouteReuseStrategy.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouteReuseStrategy, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
4829 | RouteReuseStrategy.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouteReuseStrategy, providedIn: 'root', useFactory: () => inject(DefaultRouteReuseStrategy) });
|
4830 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouteReuseStrategy, decorators: [{
|
4831 | type: Injectable,
|
4832 | args: [{ providedIn: 'root', useFactory: () => inject(DefaultRouteReuseStrategy) }]
|
4833 | }] });
|
4834 | /**
|
4835 | * @description
|
4836 | *
|
4837 | * This base route reuse strategy only reuses routes when the matched router configs are
|
4838 | * identical. This prevents components from being destroyed and recreated
|
4839 | * when just the route parameters, query parameters or fragment change
|
4840 | * (that is, the existing component is _reused_).
|
4841 | *
|
4842 | * This strategy does not store any routes for later reuse.
|
4843 | *
|
4844 | * Angular uses this strategy by default.
|
4845 | *
|
4846 | *
|
4847 | * It can be used as a base class for custom route reuse strategies, i.e. you can create your own
|
4848 | * class that extends the `BaseRouteReuseStrategy` one.
|
4849 | * @publicApi
|
4850 | */
|
4851 | class BaseRouteReuseStrategy {
|
4852 | /**
|
4853 | * Whether the given route should detach for later reuse.
|
4854 | * Always returns false for `BaseRouteReuseStrategy`.
|
4855 | * */
|
4856 | shouldDetach(route) {
|
4857 | return false;
|
4858 | }
|
4859 | /**
|
4860 | * A no-op; the route is never stored since this strategy never detaches routes for later re-use.
|
4861 | */
|
4862 | store(route, detachedTree) { }
|
4863 | /** Returns `false`, meaning the route (and its subtree) is never reattached */
|
4864 | shouldAttach(route) {
|
4865 | return false;
|
4866 | }
|
4867 | /** Returns `null` because this strategy does not store routes for later re-use. */
|
4868 | retrieve(route) {
|
4869 | return null;
|
4870 | }
|
4871 | /**
|
4872 | * Determines if a route should be reused.
|
4873 | * This strategy returns `true` when the future route config and current route config are
|
4874 | * identical.
|
4875 | */
|
4876 | shouldReuseRoute(future, curr) {
|
4877 | return future.routeConfig === curr.routeConfig;
|
4878 | }
|
4879 | }
|
4880 | class DefaultRouteReuseStrategy extends BaseRouteReuseStrategy {
|
4881 | }
|
4882 | DefaultRouteReuseStrategy.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultRouteReuseStrategy, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
4883 | DefaultRouteReuseStrategy.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultRouteReuseStrategy, providedIn: 'root' });
|
4884 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultRouteReuseStrategy, decorators: [{
|
4885 | type: Injectable,
|
4886 | args: [{ providedIn: 'root' }]
|
4887 | }] });
|
4888 |
|
4889 | const NG_DEV_MODE$3 = typeof ngDevMode === 'undefined' || !!ngDevMode;
|
4890 | /**
|
4891 | * A [DI token](guide/glossary/#di-token) for the router service.
|
4892 | *
|
4893 | * @publicApi
|
4894 | */
|
4895 | const ROUTER_CONFIGURATION = new InjectionToken(NG_DEV_MODE$3 ? 'router config' : '', {
|
4896 | providedIn: 'root',
|
4897 | factory: () => ({}),
|
4898 | });
|
4899 |
|
4900 | /**
|
4901 | * @description
|
4902 | *
|
4903 | * Provides a way to migrate AngularJS applications to Angular.
|
4904 | *
|
4905 | * @publicApi
|
4906 | */
|
4907 | class UrlHandlingStrategy {
|
4908 | }
|
4909 | UrlHandlingStrategy.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: UrlHandlingStrategy, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
4910 | UrlHandlingStrategy.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: UrlHandlingStrategy, providedIn: 'root', useFactory: () => inject(DefaultUrlHandlingStrategy) });
|
4911 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: UrlHandlingStrategy, decorators: [{
|
4912 | type: Injectable,
|
4913 | args: [{ providedIn: 'root', useFactory: () => inject(DefaultUrlHandlingStrategy) }]
|
4914 | }] });
|
4915 | /**
|
4916 | * @publicApi
|
4917 | */
|
4918 | class DefaultUrlHandlingStrategy {
|
4919 | shouldProcessUrl(url) {
|
4920 | return true;
|
4921 | }
|
4922 | extract(url) {
|
4923 | return url;
|
4924 | }
|
4925 | merge(newUrlPart, wholeUrl) {
|
4926 | return newUrlPart;
|
4927 | }
|
4928 | }
|
4929 | DefaultUrlHandlingStrategy.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultUrlHandlingStrategy, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
4930 | DefaultUrlHandlingStrategy.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultUrlHandlingStrategy, providedIn: 'root' });
|
4931 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultUrlHandlingStrategy, decorators: [{
|
4932 | type: Injectable,
|
4933 | args: [{ providedIn: 'root' }]
|
4934 | }] });
|
4935 |
|
4936 | const NG_DEV_MODE$2 = typeof ngDevMode === 'undefined' || !!ngDevMode;
|
4937 | function defaultErrorHandler(error) {
|
4938 | throw error;
|
4939 | }
|
4940 | function defaultMalformedUriErrorHandler(error, urlSerializer, url) {
|
4941 | return urlSerializer.parse('/');
|
4942 | }
|
4943 | /**
|
4944 | * The equivalent `IsActiveMatchOptions` options for `Router.isActive` is called with `true`
|
4945 | * (exact = true).
|
4946 | */
|
4947 | const exactMatchOptions = {
|
4948 | paths: 'exact',
|
4949 | fragment: 'ignored',
|
4950 | matrixParams: 'ignored',
|
4951 | queryParams: 'exact'
|
4952 | };
|
4953 | /**
|
4954 | * The equivalent `IsActiveMatchOptions` options for `Router.isActive` is called with `false`
|
4955 | * (exact = false).
|
4956 | */
|
4957 | const subsetMatchOptions = {
|
4958 | paths: 'subset',
|
4959 | fragment: 'ignored',
|
4960 | matrixParams: 'ignored',
|
4961 | queryParams: 'subset'
|
4962 | };
|
4963 | /**
|
4964 | * @description
|
4965 | *
|
4966 | * A service that provides navigation among views and URL manipulation capabilities.
|
4967 | *
|
4968 | * @see `Route`.
|
4969 | * @see [Routing and Navigation Guide](guide/router).
|
4970 | *
|
4971 | * @ngModule RouterModule
|
4972 | *
|
4973 | * @publicApi
|
4974 | */
|
4975 | class Router {
|
4976 | // TODO(b/260747083): This should not exist and navigationId should be private in
|
4977 | // `NavigationTransitions`
|
4978 | get navigationId() {
|
4979 | return this.navigationTransitions.navigationId;
|
4980 | }
|
4981 | /**
|
4982 | * The ɵrouterPageId of whatever page is currently active in the browser history. This is
|
4983 | * important for computing the target page id for new navigations because we need to ensure each
|
4984 | * page id in the browser history is 1 more than the previous entry.
|
4985 | */
|
4986 | get browserPageId() {
|
4987 | return this.location.getState()?.ɵrouterPageId;
|
4988 | }
|
4989 | /**
|
4990 | * An event stream for routing events.
|
4991 | */
|
4992 | get events() {
|
4993 | // TODO(atscott): This _should_ be events.asObservable(). However, this change requires internal
|
4994 | // cleanup: tests are doing `(route.events as Subject<Event>).next(...)`. This isn't
|
4995 | // allowed/supported but we still have to fix these or file bugs against the teams before making
|
4996 | // the change.
|
4997 | return this.navigationTransitions.events;
|
4998 | }
|
4999 | constructor() {
|
5000 | this.disposed = false;
|
5001 | /**
|
5002 | * The id of the currently active page in the router.
|
5003 | * Updated to the transition's target id on a successful navigation.
|
5004 | *
|
5005 | * This is used to track what page the router last activated. When an attempted navigation fails,
|
5006 | * the router can then use this to compute how to restore the state back to the previously active
|
5007 | * page.
|
5008 | */
|
5009 | this.currentPageId = 0;
|
5010 | this.console = inject(ɵConsole);
|
5011 | this.isNgZoneEnabled = false;
|
5012 | this.options = inject(ROUTER_CONFIGURATION, { optional: true }) || {};
|
5013 | /**
|
5014 | * A handler for navigation errors in this NgModule.
|
5015 | *
|
5016 | * @deprecated Subscribe to the `Router` events and watch for `NavigationError` instead.
|
5017 | * `provideRouter` has the `withNavigationErrorHandler` feature to make this easier.
|
5018 | * @see `withNavigationErrorHandler`
|
5019 | */
|
5020 | this.errorHandler = this.options.errorHandler || defaultErrorHandler;
|
5021 | /**
|
5022 | * A handler for errors thrown by `Router.parseUrl(url)`
|
5023 | * when `url` contains an invalid character.
|
5024 | * The most common case is a `%` sign
|
5025 | * that's not encoded and is not part of a percent encoded sequence.
|
5026 | *
|
5027 | * @deprecated URI parsing errors should be handled in the `UrlSerializer`.
|
5028 | *
|
5029 | * @see `RouterModule`
|
5030 | */
|
5031 | this.malformedUriErrorHandler = this.options.malformedUriErrorHandler || defaultMalformedUriErrorHandler;
|
5032 | /**
|
5033 | * True if at least one navigation event has occurred,
|
5034 | * false otherwise.
|
5035 | */
|
5036 | this.navigated = false;
|
5037 | this.lastSuccessfulId = -1;
|
5038 | /**
|
5039 | * A strategy for extracting and merging URLs.
|
5040 | * Used for AngularJS to Angular migrations.
|
5041 | *
|
5042 | * @deprecated Configure using `providers` instead:
|
5043 | * `{provide: UrlHandlingStrategy, useClass: MyStrategy}`.
|
5044 | */
|
5045 | this.urlHandlingStrategy = inject(UrlHandlingStrategy);
|
5046 | /**
|
5047 | * A strategy for re-using routes.
|
5048 | *
|
5049 | * @deprecated Configure using `providers` instead:
|
5050 | * `{provide: RouteReuseStrategy, useClass: MyStrategy}`.
|
5051 | */
|
5052 | this.routeReuseStrategy = inject(RouteReuseStrategy);
|
5053 | /** Strategy used to create a UrlTree. */
|
5054 | this.urlCreationStrategy = inject(CreateUrlTreeStrategy);
|
5055 | /**
|
5056 | * A strategy for setting the title based on the `routerState`.
|
5057 | *
|
5058 | * @deprecated Configure using `providers` instead:
|
5059 | * `{provide: TitleStrategy, useClass: MyStrategy}`.
|
5060 | */
|
5061 | this.titleStrategy = inject(TitleStrategy);
|
5062 | /**
|
5063 | * How to handle a navigation request to the current URL.
|
5064 | *
|
5065 | *
|
5066 | * @deprecated Configure this through `provideRouter` or `RouterModule.forRoot` instead.
|
5067 | * @see `withRouterConfig`
|
5068 | * @see `provideRouter`
|
5069 | * @see `RouterModule`
|
5070 | */
|
5071 | this.onSameUrlNavigation = this.options.onSameUrlNavigation || 'ignore';
|
5072 | /**
|
5073 | * How to merge parameters, data, resolved data, and title from parent to child
|
5074 | * routes. One of:
|
5075 | *
|
5076 | * - `'emptyOnly'` : Inherit parent parameters, data, and resolved data
|
5077 | * for path-less or component-less routes.
|
5078 | * - `'always'` : Inherit parent parameters, data, and resolved data
|
5079 | * for all child routes.
|
5080 | *
|
5081 | * @deprecated Configure this through `provideRouter` or `RouterModule.forRoot` instead.
|
5082 | * @see `withRouterConfig`
|
5083 | * @see `provideRouter`
|
5084 | * @see `RouterModule`
|
5085 | */
|
5086 | this.paramsInheritanceStrategy = this.options.paramsInheritanceStrategy || 'emptyOnly';
|
5087 | /**
|
5088 | * Determines when the router updates the browser URL.
|
5089 | * By default (`"deferred"`), updates the browser URL after navigation has finished.
|
5090 | * Set to `'eager'` to update the browser URL at the beginning of navigation.
|
5091 | * You can choose to update early so that, if navigation fails,
|
5092 | * you can show an error message with the URL that failed.
|
5093 | *
|
5094 | * @deprecated Configure this through `provideRouter` or `RouterModule.forRoot` instead.
|
5095 | * @see `withRouterConfig`
|
5096 | * @see `provideRouter`
|
5097 | * @see `RouterModule`
|
5098 | */
|
5099 | this.urlUpdateStrategy = this.options.urlUpdateStrategy || 'deferred';
|
5100 | /**
|
5101 | * Configures how the Router attempts to restore state when a navigation is cancelled.
|
5102 | *
|
5103 | * 'replace' - Always uses `location.replaceState` to set the browser state to the state of the
|
5104 | * router before the navigation started. This means that if the URL of the browser is updated
|
5105 | * _before_ the navigation is canceled, the Router will simply replace the item in history rather
|
5106 | * than trying to restore to the previous location in the session history. This happens most
|
5107 | * frequently with `urlUpdateStrategy: 'eager'` and navigations with the browser back/forward
|
5108 | * buttons.
|
5109 | *
|
5110 | * 'computed' - Will attempt to return to the same index in the session history that corresponds
|
5111 | * to the Angular route when the navigation gets cancelled. For example, if the browser back
|
5112 | * button is clicked and the navigation is cancelled, the Router will trigger a forward navigation
|
5113 | * and vice versa.
|
5114 | *
|
5115 | * Note: the 'computed' option is incompatible with any `UrlHandlingStrategy` which only
|
5116 | * handles a portion of the URL because the history restoration navigates to the previous place in
|
5117 | * the browser history rather than simply resetting a portion of the URL.
|
5118 | *
|
5119 | * The default value is `replace`.
|
5120 | *
|
5121 | * @deprecated Configure this through `provideRouter` or `RouterModule.forRoot` instead.
|
5122 | * @see `withRouterConfig`
|
5123 | * @see `provideRouter`
|
5124 | * @see `RouterModule`
|
5125 | */
|
5126 | this.canceledNavigationResolution = this.options.canceledNavigationResolution || 'replace';
|
5127 | this.config = flatten(inject(ROUTES, { optional: true }) ?? []);
|
5128 | this.navigationTransitions = inject(NavigationTransitions);
|
5129 | this.urlSerializer = inject(UrlSerializer);
|
5130 | this.location = inject(Location);
|
5131 | this.isNgZoneEnabled = inject(NgZone) instanceof NgZone && NgZone.isInAngularZone();
|
5132 | this.resetConfig(this.config);
|
5133 | this.currentUrlTree = new UrlTree();
|
5134 | this.rawUrlTree = this.currentUrlTree;
|
5135 | this.browserUrlTree = this.currentUrlTree;
|
5136 | this.routerState = createEmptyState(this.currentUrlTree, null);
|
5137 | this.navigationTransitions.setupNavigations(this).subscribe(t => {
|
5138 | this.lastSuccessfulId = t.id;
|
5139 | this.currentPageId = t.targetPageId;
|
5140 | }, e => {
|
5141 | this.console.warn(`Unhandled Navigation Error: ${e}`);
|
5142 | });
|
5143 | }
|
5144 | /** @internal */
|
5145 | resetRootComponentType(rootComponentType) {
|
5146 | // TODO: vsavkin router 4.0 should make the root component set to null
|
5147 | // this will simplify the lifecycle of the router.
|
5148 | this.routerState.root.component = rootComponentType;
|
5149 | this.navigationTransitions.rootComponentType = rootComponentType;
|
5150 | }
|
5151 | /**
|
5152 | * Sets up the location change listener and performs the initial navigation.
|
5153 | */
|
5154 | initialNavigation() {
|
5155 | this.setUpLocationChangeListener();
|
5156 | if (!this.navigationTransitions.hasRequestedNavigation) {
|
5157 | const state = this.location.getState();
|
5158 | this.navigateToSyncWithBrowser(this.location.path(true), IMPERATIVE_NAVIGATION, state);
|
5159 | }
|
5160 | }
|
5161 | /**
|
5162 | * Sets up the location change listener. This listener detects navigations triggered from outside
|
5163 | * the Router (the browser back/forward buttons, for example) and schedules a corresponding Router
|
5164 | * navigation so that the correct events, guards, etc. are triggered.
|
5165 | */
|
5166 | setUpLocationChangeListener() {
|
5167 | // Don't need to use Zone.wrap any more, because zone.js
|
5168 | // already patch onPopState, so location change callback will
|
5169 | // run into ngZone
|
5170 | if (!this.locationSubscription) {
|
5171 | this.locationSubscription = this.location.subscribe(event => {
|
5172 | const source = event['type'] === 'popstate' ? 'popstate' : 'hashchange';
|
5173 | if (source === 'popstate') {
|
5174 | // The `setTimeout` was added in #12160 and is likely to support Angular/AngularJS
|
5175 | // hybrid apps.
|
5176 | setTimeout(() => {
|
5177 | this.navigateToSyncWithBrowser(event['url'], source, event.state);
|
5178 | }, 0);
|
5179 | }
|
5180 | });
|
5181 | }
|
5182 | }
|
5183 | /**
|
5184 | * Schedules a router navigation to synchronize Router state with the browser state.
|
5185 | *
|
5186 | * This is done as a response to a popstate event and the initial navigation. These
|
5187 | * two scenarios represent times when the browser URL/state has been updated and
|
5188 | * the Router needs to respond to ensure its internal state matches.
|
5189 | */
|
5190 | navigateToSyncWithBrowser(url, source, state) {
|
5191 | const extras = { replaceUrl: true };
|
5192 | // TODO: restoredState should always include the entire state, regardless
|
5193 | // of navigationId. This requires a breaking change to update the type on
|
5194 | // NavigationStart’s restoredState, which currently requires navigationId
|
5195 | // to always be present. The Router used to only restore history state if
|
5196 | // a navigationId was present.
|
5197 | // The stored navigationId is used by the RouterScroller to retrieve the scroll
|
5198 | // position for the page.
|
5199 | const restoredState = state?.navigationId ? state : null;
|
5200 | // Separate to NavigationStart.restoredState, we must also restore the state to
|
5201 | // history.state and generate a new navigationId, since it will be overwritten
|
5202 | if (state) {
|
5203 | const stateCopy = { ...state };
|
5204 | delete stateCopy.navigationId;
|
5205 | delete stateCopy.ɵrouterPageId;
|
5206 | if (Object.keys(stateCopy).length !== 0) {
|
5207 | extras.state = stateCopy;
|
5208 | }
|
5209 | }
|
5210 | const urlTree = this.parseUrl(url);
|
5211 | this.scheduleNavigation(urlTree, source, restoredState, extras);
|
5212 | }
|
5213 | /** The current URL. */
|
5214 | get url() {
|
5215 | return this.serializeUrl(this.currentUrlTree);
|
5216 | }
|
5217 | /**
|
5218 | * Returns the current `Navigation` object when the router is navigating,
|
5219 | * and `null` when idle.
|
5220 | */
|
5221 | getCurrentNavigation() {
|
5222 | return this.navigationTransitions.currentNavigation;
|
5223 | }
|
5224 | /**
|
5225 | * Resets the route configuration used for navigation and generating links.
|
5226 | *
|
5227 | * @param config The route array for the new configuration.
|
5228 | *
|
5229 | * @usageNotes
|
5230 | *
|
5231 | * ```
|
5232 | * router.resetConfig([
|
5233 | * { path: 'team/:id', component: TeamCmp, children: [
|
5234 | * { path: 'simple', component: SimpleCmp },
|
5235 | * { path: 'user/:name', component: UserCmp }
|
5236 | * ]}
|
5237 | * ]);
|
5238 | * ```
|
5239 | */
|
5240 | resetConfig(config) {
|
5241 | NG_DEV_MODE$2 && validateConfig(config);
|
5242 | this.config = config.map(standardizeConfig);
|
5243 | this.navigated = false;
|
5244 | this.lastSuccessfulId = -1;
|
5245 | }
|
5246 | /** @nodoc */
|
5247 | ngOnDestroy() {
|
5248 | this.dispose();
|
5249 | }
|
5250 | /** Disposes of the router. */
|
5251 | dispose() {
|
5252 | this.navigationTransitions.complete();
|
5253 | if (this.locationSubscription) {
|
5254 | this.locationSubscription.unsubscribe();
|
5255 | this.locationSubscription = undefined;
|
5256 | }
|
5257 | this.disposed = true;
|
5258 | }
|
5259 | /**
|
5260 | * Appends URL segments to the current URL tree to create a new URL tree.
|
5261 | *
|
5262 | * @param commands An array of URL fragments with which to construct the new URL tree.
|
5263 | * If the path is static, can be the literal URL string. For a dynamic path, pass an array of path
|
5264 | * segments, followed by the parameters for each segment.
|
5265 | * The fragments are applied to the current URL tree or the one provided in the `relativeTo`
|
5266 | * property of the options object, if supplied.
|
5267 | * @param navigationExtras Options that control the navigation strategy.
|
5268 | * @returns The new URL tree.
|
5269 | *
|
5270 | * @usageNotes
|
5271 | *
|
5272 | * ```
|
5273 | * // create /team/33/user/11
|
5274 | * router.createUrlTree(['/team', 33, 'user', 11]);
|
5275 | *
|
5276 | * // create /team/33;expand=true/user/11
|
5277 | * router.createUrlTree(['/team', 33, {expand: true}, 'user', 11]);
|
5278 | *
|
5279 | * // you can collapse static segments like this (this works only with the first passed-in value):
|
5280 | * router.createUrlTree(['/team/33/user', userId]);
|
5281 | *
|
5282 | * // If the first segment can contain slashes, and you do not want the router to split it,
|
5283 | * // you can do the following:
|
5284 | * router.createUrlTree([{segmentPath: '/one/two'}]);
|
5285 | *
|
5286 | * // create /team/33/(user/11//right:chat)
|
5287 | * router.createUrlTree(['/team', 33, {outlets: {primary: 'user/11', right: 'chat'}}]);
|
5288 | *
|
5289 | * // remove the right secondary node
|
5290 | * router.createUrlTree(['/team', 33, {outlets: {primary: 'user/11', right: null}}]);
|
5291 | *
|
5292 | * // assuming the current url is `/team/33/user/11` and the route points to `user/11`
|
5293 | *
|
5294 | * // navigate to /team/33/user/11/details
|
5295 | * router.createUrlTree(['details'], {relativeTo: route});
|
5296 | *
|
5297 | * // navigate to /team/33/user/22
|
5298 | * router.createUrlTree(['../22'], {relativeTo: route});
|
5299 | *
|
5300 | * // navigate to /team/44/user/22
|
5301 | * router.createUrlTree(['../../team/44/user/22'], {relativeTo: route});
|
5302 | *
|
5303 | * Note that a value of `null` or `undefined` for `relativeTo` indicates that the
|
5304 | * tree should be created relative to the root.
|
5305 | * ```
|
5306 | */
|
5307 | createUrlTree(commands, navigationExtras = {}) {
|
5308 | const { relativeTo, queryParams, fragment, queryParamsHandling, preserveFragment } = navigationExtras;
|
5309 | const f = preserveFragment ? this.currentUrlTree.fragment : fragment;
|
5310 | let q = null;
|
5311 | switch (queryParamsHandling) {
|
5312 | case 'merge':
|
5313 | q = { ...this.currentUrlTree.queryParams, ...queryParams };
|
5314 | break;
|
5315 | case 'preserve':
|
5316 | q = this.currentUrlTree.queryParams;
|
5317 | break;
|
5318 | default:
|
5319 | q = queryParams || null;
|
5320 | }
|
5321 | if (q !== null) {
|
5322 | q = this.removeEmptyProps(q);
|
5323 | }
|
5324 | return this.urlCreationStrategy.createUrlTree(relativeTo, this.routerState, this.currentUrlTree, commands, q, f ?? null);
|
5325 | }
|
5326 | /**
|
5327 | * Navigates to a view using an absolute route path.
|
5328 | *
|
5329 | * @param url An absolute path for a defined route. The function does not apply any delta to the
|
5330 | * current URL.
|
5331 | * @param extras An object containing properties that modify the navigation strategy.
|
5332 | *
|
5333 | * @returns A Promise that resolves to 'true' when navigation succeeds,
|
5334 | * to 'false' when navigation fails, or is rejected on error.
|
5335 | *
|
5336 | * @usageNotes
|
5337 | *
|
5338 | * The following calls request navigation to an absolute path.
|
5339 | *
|
5340 | * ```
|
5341 | * router.navigateByUrl("/team/33/user/11");
|
5342 | *
|
5343 | * // Navigate without updating the URL
|
5344 | * router.navigateByUrl("/team/33/user/11", { skipLocationChange: true });
|
5345 | * ```
|
5346 | *
|
5347 | * @see [Routing and Navigation guide](guide/router)
|
5348 | *
|
5349 | */
|
5350 | navigateByUrl(url, extras = {
|
5351 | skipLocationChange: false
|
5352 | }) {
|
5353 | if (NG_DEV_MODE$2) {
|
5354 | if (this.isNgZoneEnabled && !NgZone.isInAngularZone()) {
|
5355 | this.console.warn(`Navigation triggered outside Angular zone, did you forget to call 'ngZone.run()'?`);
|
5356 | }
|
5357 | if (url instanceof UrlTree && url._warnIfUsedForNavigation) {
|
5358 | this.console.warn(url._warnIfUsedForNavigation);
|
5359 | }
|
5360 | }
|
5361 | const urlTree = isUrlTree(url) ? url : this.parseUrl(url);
|
5362 | const mergedTree = this.urlHandlingStrategy.merge(urlTree, this.rawUrlTree);
|
5363 | return this.scheduleNavigation(mergedTree, IMPERATIVE_NAVIGATION, null, extras);
|
5364 | }
|
5365 | /**
|
5366 | * Navigate based on the provided array of commands and a starting point.
|
5367 | * If no starting route is provided, the navigation is absolute.
|
5368 | *
|
5369 | * @param commands An array of URL fragments with which to construct the target URL.
|
5370 | * If the path is static, can be the literal URL string. For a dynamic path, pass an array of path
|
5371 | * segments, followed by the parameters for each segment.
|
5372 | * The fragments are applied to the current URL or the one provided in the `relativeTo` property
|
5373 | * of the options object, if supplied.
|
5374 | * @param extras An options object that determines how the URL should be constructed or
|
5375 | * interpreted.
|
5376 | *
|
5377 | * @returns A Promise that resolves to `true` when navigation succeeds, to `false` when navigation
|
5378 | * fails,
|
5379 | * or is rejected on error.
|
5380 | *
|
5381 | * @usageNotes
|
5382 | *
|
5383 | * The following calls request navigation to a dynamic route path relative to the current URL.
|
5384 | *
|
5385 | * ```
|
5386 | * router.navigate(['team', 33, 'user', 11], {relativeTo: route});
|
5387 | *
|
5388 | * // Navigate without updating the URL, overriding the default behavior
|
5389 | * router.navigate(['team', 33, 'user', 11], {relativeTo: route, skipLocationChange: true});
|
5390 | * ```
|
5391 | *
|
5392 | * @see [Routing and Navigation guide](guide/router)
|
5393 | *
|
5394 | */
|
5395 | navigate(commands, extras = { skipLocationChange: false }) {
|
5396 | validateCommands(commands);
|
5397 | return this.navigateByUrl(this.createUrlTree(commands, extras), extras);
|
5398 | }
|
5399 | /** Serializes a `UrlTree` into a string */
|
5400 | serializeUrl(url) {
|
5401 | return this.urlSerializer.serialize(url);
|
5402 | }
|
5403 | /** Parses a string into a `UrlTree` */
|
5404 | parseUrl(url) {
|
5405 | let urlTree;
|
5406 | try {
|
5407 | urlTree = this.urlSerializer.parse(url);
|
5408 | }
|
5409 | catch (e) {
|
5410 | urlTree = this.malformedUriErrorHandler(e, this.urlSerializer, url);
|
5411 | }
|
5412 | return urlTree;
|
5413 | }
|
5414 | isActive(url, matchOptions) {
|
5415 | let options;
|
5416 | if (matchOptions === true) {
|
5417 | options = { ...exactMatchOptions };
|
5418 | }
|
5419 | else if (matchOptions === false) {
|
5420 | options = { ...subsetMatchOptions };
|
5421 | }
|
5422 | else {
|
5423 | options = matchOptions;
|
5424 | }
|
5425 | if (isUrlTree(url)) {
|
5426 | return containsTree(this.currentUrlTree, url, options);
|
5427 | }
|
5428 | const urlTree = this.parseUrl(url);
|
5429 | return containsTree(this.currentUrlTree, urlTree, options);
|
5430 | }
|
5431 | removeEmptyProps(params) {
|
5432 | return Object.keys(params).reduce((result, key) => {
|
5433 | const value = params[key];
|
5434 | if (value !== null && value !== undefined) {
|
5435 | result[key] = value;
|
5436 | }
|
5437 | return result;
|
5438 | }, {});
|
5439 | }
|
5440 | /** @internal */
|
5441 | scheduleNavigation(rawUrl, source, restoredState, extras, priorPromise) {
|
5442 | if (this.disposed) {
|
5443 | return Promise.resolve(false);
|
5444 | }
|
5445 | let resolve;
|
5446 | let reject;
|
5447 | let promise;
|
5448 | if (priorPromise) {
|
5449 | resolve = priorPromise.resolve;
|
5450 | reject = priorPromise.reject;
|
5451 | promise = priorPromise.promise;
|
5452 | }
|
5453 | else {
|
5454 | promise = new Promise((res, rej) => {
|
5455 | resolve = res;
|
5456 | reject = rej;
|
5457 | });
|
5458 | }
|
5459 | let targetPageId;
|
5460 | if (this.canceledNavigationResolution === 'computed') {
|
5461 | // If the `ɵrouterPageId` exist in the state then `targetpageId` should have the value of
|
5462 | // `ɵrouterPageId`. This is the case for something like a page refresh where we assign the
|
5463 | // target id to the previously set value for that page.
|
5464 | if (restoredState && restoredState.ɵrouterPageId) {
|
5465 | targetPageId = restoredState.ɵrouterPageId;
|
5466 | }
|
5467 | else {
|
5468 | // If we're replacing the URL or doing a silent navigation, we do not want to increment the
|
5469 | // page id because we aren't pushing a new entry to history.
|
5470 | if (extras.replaceUrl || extras.skipLocationChange) {
|
5471 | targetPageId = this.browserPageId ?? 0;
|
5472 | }
|
5473 | else {
|
5474 | targetPageId = (this.browserPageId ?? 0) + 1;
|
5475 | }
|
5476 | }
|
5477 | }
|
5478 | else {
|
5479 | // This is unused when `canceledNavigationResolution` is not computed.
|
5480 | targetPageId = 0;
|
5481 | }
|
5482 | this.navigationTransitions.handleNavigationRequest({
|
5483 | targetPageId,
|
5484 | source,
|
5485 | restoredState,
|
5486 | currentUrlTree: this.currentUrlTree,
|
5487 | currentRawUrl: this.currentUrlTree,
|
5488 | rawUrl,
|
5489 | extras,
|
5490 | resolve,
|
5491 | reject,
|
5492 | promise,
|
5493 | currentSnapshot: this.routerState.snapshot,
|
5494 | currentRouterState: this.routerState
|
5495 | });
|
5496 | // Make sure that the error is propagated even though `processNavigations` catch
|
5497 | // handler does not rethrow
|
5498 | return promise.catch((e) => {
|
5499 | return Promise.reject(e);
|
5500 | });
|
5501 | }
|
5502 | /** @internal */
|
5503 | setBrowserUrl(url, transition) {
|
5504 | const path = this.urlSerializer.serialize(url);
|
5505 | const state = {
|
5506 | ...transition.extras.state,
|
5507 | ...this.generateNgRouterState(transition.id, transition.targetPageId)
|
5508 | };
|
5509 | if (this.location.isCurrentPathEqualTo(path) || !!transition.extras.replaceUrl) {
|
5510 | this.location.replaceState(path, '', state);
|
5511 | }
|
5512 | else {
|
5513 | this.location.go(path, '', state);
|
5514 | }
|
5515 | }
|
5516 | /**
|
5517 | * Performs the necessary rollback action to restore the browser URL to the
|
5518 | * state before the transition.
|
5519 | * @internal
|
5520 | */
|
5521 | restoreHistory(transition, restoringFromCaughtError = false) {
|
5522 | if (this.canceledNavigationResolution === 'computed') {
|
5523 | const targetPagePosition = this.currentPageId - transition.targetPageId;
|
5524 | // The navigator change the location before triggered the browser event,
|
5525 | // so we need to go back to the current url if the navigation is canceled.
|
5526 | // Also, when navigation gets cancelled while using url update strategy eager, then we need to
|
5527 | // go back. Because, when `urlUpdateStrategy` is `eager`; `setBrowserUrl` method is called
|
5528 | // before any verification.
|
5529 | const browserUrlUpdateOccurred = (transition.source === 'popstate' || this.urlUpdateStrategy === 'eager' ||
|
5530 | this.currentUrlTree === this.getCurrentNavigation()?.finalUrl);
|
5531 | if (browserUrlUpdateOccurred && targetPagePosition !== 0) {
|
5532 | this.location.historyGo(targetPagePosition);
|
5533 | }
|
5534 | else if (this.currentUrlTree === this.getCurrentNavigation()?.finalUrl &&
|
5535 | targetPagePosition === 0) {
|
5536 | // We got to the activation stage (where currentUrlTree is set to the navigation's
|
5537 | // finalUrl), but we weren't moving anywhere in history (skipLocationChange or replaceUrl).
|
5538 | // We still need to reset the router state back to what it was when the navigation started.
|
5539 | this.resetState(transition);
|
5540 | // TODO(atscott): resetting the `browserUrlTree` should really be done in `resetState`.
|
5541 | // Investigate if this can be done by running TGP.
|
5542 | this.browserUrlTree = transition.currentUrlTree;
|
5543 | this.resetUrlToCurrentUrlTree();
|
5544 | }
|
5545 | else {
|
5546 | // The browser URL and router state was not updated before the navigation cancelled so
|
5547 | // there's no restoration needed.
|
5548 | }
|
5549 | }
|
5550 | else if (this.canceledNavigationResolution === 'replace') {
|
5551 | // TODO(atscott): It seems like we should _always_ reset the state here. It would be a no-op
|
5552 | // for `deferred` navigations that haven't change the internal state yet because guards
|
5553 | // reject. For 'eager' navigations, it seems like we also really should reset the state
|
5554 | // because the navigation was cancelled. Investigate if this can be done by running TGP.
|
5555 | if (restoringFromCaughtError) {
|
5556 | this.resetState(transition);
|
5557 | }
|
5558 | this.resetUrlToCurrentUrlTree();
|
5559 | }
|
5560 | }
|
5561 | resetState(t) {
|
5562 | this.routerState = t.currentRouterState;
|
5563 | this.currentUrlTree = t.currentUrlTree;
|
5564 | // Note here that we use the urlHandlingStrategy to get the reset `rawUrlTree` because it may be
|
5565 | // configured to handle only part of the navigation URL. This means we would only want to reset
|
5566 | // the part of the navigation handled by the Angular router rather than the whole URL. In
|
5567 | // addition, the URLHandlingStrategy may be configured to specifically preserve parts of the URL
|
5568 | // when merging, such as the query params so they are not lost on a refresh.
|
5569 | this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, t.rawUrl);
|
5570 | }
|
5571 | resetUrlToCurrentUrlTree() {
|
5572 | this.location.replaceState(this.urlSerializer.serialize(this.rawUrlTree), '', this.generateNgRouterState(this.lastSuccessfulId, this.currentPageId));
|
5573 | }
|
5574 | generateNgRouterState(navigationId, routerPageId) {
|
5575 | if (this.canceledNavigationResolution === 'computed') {
|
5576 | return { navigationId, ɵrouterPageId: routerPageId };
|
5577 | }
|
5578 | return { navigationId };
|
5579 | }
|
5580 | }
|
5581 | Router.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: Router, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
5582 | Router.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: Router, providedIn: 'root' });
|
5583 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: Router, decorators: [{
|
5584 | type: Injectable,
|
5585 | args: [{ providedIn: 'root' }]
|
5586 | }], ctorParameters: function () { return []; } });
|
5587 | function validateCommands(commands) {
|
5588 | for (let i = 0; i < commands.length; i++) {
|
5589 | const cmd = commands[i];
|
5590 | if (cmd == null) {
|
5591 | throw new ɵRuntimeError(4008 /* RuntimeErrorCode.NULLISH_COMMAND */, NG_DEV_MODE$2 && `The requested path contains ${cmd} segment at index ${i}`);
|
5592 | }
|
5593 | }
|
5594 | }
|
5595 |
|
5596 | /**
|
5597 | * @description
|
5598 | *
|
5599 | * When applied to an element in a template, makes that element a link
|
5600 | * that initiates navigation to a route. Navigation opens one or more routed components
|
5601 | * in one or more `<router-outlet>` locations on the page.
|
5602 | *
|
5603 | * Given a route configuration `[{ path: 'user/:name', component: UserCmp }]`,
|
5604 | * the following creates a static link to the route:
|
5605 | * `<a routerLink="/user/bob">link to user component</a>`
|
5606 | *
|
5607 | * You can use dynamic values to generate the link.
|
5608 | * For a dynamic link, pass an array of path segments,
|
5609 | * followed by the params for each segment.
|
5610 | * For example, `['/team', teamId, 'user', userName, {details: true}]`
|
5611 | * generates a link to `/team/11/user/bob;details=true`.
|
5612 | *
|
5613 | * Multiple static segments can be merged into one term and combined with dynamic segments.
|
5614 | * For example, `['/team/11/user', userName, {details: true}]`
|
5615 | *
|
5616 | * The input that you provide to the link is treated as a delta to the current URL.
|
5617 | * For instance, suppose the current URL is `/user/(box//aux:team)`.
|
5618 | * The link `<a [routerLink]="['/user/jim']">Jim</a>` creates the URL
|
5619 | * `/user/(jim//aux:team)`.
|
5620 | * See {@link Router#createUrlTree createUrlTree} for more information.
|
5621 | *
|
5622 | * @usageNotes
|
5623 | *
|
5624 | * You can use absolute or relative paths in a link, set query parameters,
|
5625 | * control how parameters are handled, and keep a history of navigation states.
|
5626 | *
|
5627 | * ### Relative link paths
|
5628 | *
|
5629 | * The first segment name can be prepended with `/`, `./`, or `../`.
|
5630 | * * If the first segment begins with `/`, the router looks up the route from the root of the
|
5631 | * app.
|
5632 | * * If the first segment begins with `./`, or doesn't begin with a slash, the router
|
5633 | * looks in the children of the current activated route.
|
5634 | * * If the first segment begins with `../`, the router goes up one level in the route tree.
|
5635 | *
|
5636 | * ### Setting and handling query params and fragments
|
5637 | *
|
5638 | * The following link adds a query parameter and a fragment to the generated URL:
|
5639 | *
|
5640 | * ```
|
5641 | * <a [routerLink]="['/user/bob']" [queryParams]="{debug: true}" fragment="education">
|
5642 | * link to user component
|
5643 | * </a>
|
5644 | * ```
|
5645 | * By default, the directive constructs the new URL using the given query parameters.
|
5646 | * The example generates the link: `/user/bob?debug=true#education`.
|
5647 | *
|
5648 | * You can instruct the directive to handle query parameters differently
|
5649 | * by specifying the `queryParamsHandling` option in the link.
|
5650 | * Allowed values are:
|
5651 | *
|
5652 | * - `'merge'`: Merge the given `queryParams` into the current query params.
|
5653 | * - `'preserve'`: Preserve the current query params.
|
5654 | *
|
5655 | * For example:
|
5656 | *
|
5657 | * ```
|
5658 | * <a [routerLink]="['/user/bob']" [queryParams]="{debug: true}" queryParamsHandling="merge">
|
5659 | * link to user component
|
5660 | * </a>
|
5661 | * ```
|
5662 | *
|
5663 | * See {@link UrlCreationOptions.queryParamsHandling UrlCreationOptions#queryParamsHandling}.
|
5664 | *
|
5665 | * ### Preserving navigation history
|
5666 | *
|
5667 | * You can provide a `state` value to be persisted to the browser's
|
5668 | * [`History.state` property](https://developer.mozilla.org/en-US/docs/Web/API/History#Properties).
|
5669 | * For example:
|
5670 | *
|
5671 | * ```
|
5672 | * <a [routerLink]="['/user/bob']" [state]="{tracingId: 123}">
|
5673 | * link to user component
|
5674 | * </a>
|
5675 | * ```
|
5676 | *
|
5677 | * Use {@link Router.getCurrentNavigation() Router#getCurrentNavigation} to retrieve a saved
|
5678 | * navigation-state value. For example, to capture the `tracingId` during the `NavigationStart`
|
5679 | * event:
|
5680 | *
|
5681 | * ```
|
5682 | * // Get NavigationStart events
|
5683 | * router.events.pipe(filter(e => e instanceof NavigationStart)).subscribe(e => {
|
5684 | * const navigation = router.getCurrentNavigation();
|
5685 | * tracingService.trace({id: navigation.extras.state.tracingId});
|
5686 | * });
|
5687 | * ```
|
5688 | *
|
5689 | * @ngModule RouterModule
|
5690 | *
|
5691 | * @publicApi
|
5692 | */
|
5693 | class RouterLink {
|
5694 | constructor(router, route, tabIndexAttribute, renderer, el, locationStrategy) {
|
5695 | this.router = router;
|
5696 | this.route = route;
|
5697 | this.tabIndexAttribute = tabIndexAttribute;
|
5698 | this.renderer = renderer;
|
5699 | this.el = el;
|
5700 | this.locationStrategy = locationStrategy;
|
5701 | this._preserveFragment = false;
|
5702 | this._skipLocationChange = false;
|
5703 | this._replaceUrl = false;
|
5704 | /**
|
5705 | * Represents an `href` attribute value applied to a host element,
|
5706 | * when a host element is `<a>`. For other tags, the value is `null`.
|
5707 | */
|
5708 | this.href = null;
|
5709 | this.commands = null;
|
5710 | /** @internal */
|
5711 | this.onChanges = new Subject();
|
5712 | const tagName = el.nativeElement.tagName?.toLowerCase();
|
5713 | this.isAnchorElement = tagName === 'a' || tagName === 'area';
|
5714 | if (this.isAnchorElement) {
|
5715 | this.subscription = router.events.subscribe((s) => {
|
5716 | if (s instanceof NavigationEnd) {
|
5717 | this.updateHref();
|
5718 | }
|
5719 | });
|
5720 | }
|
5721 | else {
|
5722 | this.setTabIndexIfNotOnNativeEl('0');
|
5723 | }
|
5724 | }
|
5725 | /**
|
5726 | * Passed to {@link Router#createUrlTree Router#createUrlTree} as part of the
|
5727 | * `UrlCreationOptions`.
|
5728 | * @see {@link UrlCreationOptions#preserveFragment UrlCreationOptions#preserveFragment}
|
5729 | * @see {@link Router#createUrlTree Router#createUrlTree}
|
5730 | */
|
5731 | set preserveFragment(preserveFragment) {
|
5732 | this._preserveFragment = ɵcoerceToBoolean(preserveFragment);
|
5733 | }
|
5734 | get preserveFragment() {
|
5735 | return this._preserveFragment;
|
5736 | }
|
5737 | /**
|
5738 | * Passed to {@link Router#navigateByUrl Router#navigateByUrl} as part of the
|
5739 | * `NavigationBehaviorOptions`.
|
5740 | * @see {@link NavigationBehaviorOptions#skipLocationChange NavigationBehaviorOptions#skipLocationChange}
|
5741 | * @see {@link Router#navigateByUrl Router#navigateByUrl}
|
5742 | */
|
5743 | set skipLocationChange(skipLocationChange) {
|
5744 | this._skipLocationChange = ɵcoerceToBoolean(skipLocationChange);
|
5745 | }
|
5746 | get skipLocationChange() {
|
5747 | return this._skipLocationChange;
|
5748 | }
|
5749 | /**
|
5750 | * Passed to {@link Router#navigateByUrl Router#navigateByUrl} as part of the
|
5751 | * `NavigationBehaviorOptions`.
|
5752 | * @see {@link NavigationBehaviorOptions#replaceUrl NavigationBehaviorOptions#replaceUrl}
|
5753 | * @see {@link Router#navigateByUrl Router#navigateByUrl}
|
5754 | */
|
5755 | set replaceUrl(replaceUrl) {
|
5756 | this._replaceUrl = ɵcoerceToBoolean(replaceUrl);
|
5757 | }
|
5758 | get replaceUrl() {
|
5759 | return this._replaceUrl;
|
5760 | }
|
5761 | /**
|
5762 | * Modifies the tab index if there was not a tabindex attribute on the element during
|
5763 | * instantiation.
|
5764 | */
|
5765 | setTabIndexIfNotOnNativeEl(newTabIndex) {
|
5766 | if (this.tabIndexAttribute != null /* both `null` and `undefined` */ || this.isAnchorElement) {
|
5767 | return;
|
5768 | }
|
5769 | this.applyAttributeValue('tabindex', newTabIndex);
|
5770 | }
|
5771 | /** @nodoc */
|
5772 | ngOnChanges(changes) {
|
5773 | if (this.isAnchorElement) {
|
5774 | this.updateHref();
|
5775 | }
|
5776 | // This is subscribed to by `RouterLinkActive` so that it knows to update when there are changes
|
5777 | // to the RouterLinks it's tracking.
|
5778 | this.onChanges.next(this);
|
5779 | }
|
5780 | /**
|
5781 | * Commands to pass to {@link Router#createUrlTree Router#createUrlTree}.
|
5782 | * - **array**: commands to pass to {@link Router#createUrlTree Router#createUrlTree}.
|
5783 | * - **string**: shorthand for array of commands with just the string, i.e. `['/route']`
|
5784 | * - **null|undefined**: effectively disables the `routerLink`
|
5785 | * @see {@link Router#createUrlTree Router#createUrlTree}
|
5786 | */
|
5787 | set routerLink(commands) {
|
5788 | if (commands != null) {
|
5789 | this.commands = Array.isArray(commands) ? commands : [commands];
|
5790 | this.setTabIndexIfNotOnNativeEl('0');
|
5791 | }
|
5792 | else {
|
5793 | this.commands = null;
|
5794 | this.setTabIndexIfNotOnNativeEl(null);
|
5795 | }
|
5796 | }
|
5797 | /** @nodoc */
|
5798 | onClick(button, ctrlKey, shiftKey, altKey, metaKey) {
|
5799 | if (this.urlTree === null) {
|
5800 | return true;
|
5801 | }
|
5802 | if (this.isAnchorElement) {
|
5803 | if (button !== 0 || ctrlKey || shiftKey || altKey || metaKey) {
|
5804 | return true;
|
5805 | }
|
5806 | if (typeof this.target === 'string' && this.target != '_self') {
|
5807 | return true;
|
5808 | }
|
5809 | }
|
5810 | const extras = {
|
5811 | skipLocationChange: this.skipLocationChange,
|
5812 | replaceUrl: this.replaceUrl,
|
5813 | state: this.state,
|
5814 | };
|
5815 | this.router.navigateByUrl(this.urlTree, extras);
|
5816 | // Return `false` for `<a>` elements to prevent default action
|
5817 | // and cancel the native behavior, since the navigation is handled
|
5818 | // by the Router.
|
5819 | return !this.isAnchorElement;
|
5820 | }
|
5821 | /** @nodoc */
|
5822 | ngOnDestroy() {
|
5823 | this.subscription?.unsubscribe();
|
5824 | }
|
5825 | updateHref() {
|
5826 | this.href = this.urlTree !== null && this.locationStrategy ?
|
5827 | this.locationStrategy?.prepareExternalUrl(this.router.serializeUrl(this.urlTree)) :
|
5828 | null;
|
5829 | const sanitizedValue = this.href === null ?
|
5830 | null :
|
5831 | // This class represents a directive that can be added to both `<a>` elements,
|
5832 | // as well as other elements. As a result, we can't define security context at
|
5833 | // compile time. So the security context is deferred to runtime.
|
5834 | // The `ɵɵsanitizeUrlOrResourceUrl` selects the necessary sanitizer function
|
5835 | // based on the tag and property names. The logic mimics the one from
|
5836 | // `packages/compiler/src/schema/dom_security_schema.ts`, which is used at compile time.
|
5837 | //
|
5838 | // Note: we should investigate whether we can switch to using `@HostBinding('attr.href')`
|
5839 | // instead of applying a value via a renderer, after a final merge of the
|
5840 | // `RouterLinkWithHref` directive.
|
5841 | ɵɵsanitizeUrlOrResourceUrl(this.href, this.el.nativeElement.tagName.toLowerCase(), 'href');
|
5842 | this.applyAttributeValue('href', sanitizedValue);
|
5843 | }
|
5844 | applyAttributeValue(attrName, attrValue) {
|
5845 | const renderer = this.renderer;
|
5846 | const nativeElement = this.el.nativeElement;
|
5847 | if (attrValue !== null) {
|
5848 | renderer.setAttribute(nativeElement, attrName, attrValue);
|
5849 | }
|
5850 | else {
|
5851 | renderer.removeAttribute(nativeElement, attrName);
|
5852 | }
|
5853 | }
|
5854 | get urlTree() {
|
5855 | if (this.commands === null) {
|
5856 | return null;
|
5857 | }
|
5858 | return this.router.createUrlTree(this.commands, {
|
5859 | // If the `relativeTo` input is not defined, we want to use `this.route` by default.
|
5860 | // Otherwise, we should use the value provided by the user in the input.
|
5861 | relativeTo: this.relativeTo !== undefined ? this.relativeTo : this.route,
|
5862 | queryParams: this.queryParams,
|
5863 | fragment: this.fragment,
|
5864 | queryParamsHandling: this.queryParamsHandling,
|
5865 | preserveFragment: this.preserveFragment,
|
5866 | });
|
5867 | }
|
5868 | }
|
5869 | RouterLink.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterLink, deps: [{ token: Router }, { token: ActivatedRoute }, { token: 'tabindex', attribute: true }, { token: i0.Renderer2 }, { token: i0.ElementRef }, { token: i3.LocationStrategy }], target: i0.ɵɵFactoryTarget.Directive });
|
5870 | RouterLink.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.3", type: RouterLink, isStandalone: true, selector: "[routerLink]", inputs: { target: "target", queryParams: "queryParams", fragment: "fragment", queryParamsHandling: "queryParamsHandling", state: "state", relativeTo: "relativeTo", preserveFragment: "preserveFragment", skipLocationChange: "skipLocationChange", replaceUrl: "replaceUrl", routerLink: "routerLink" }, host: { listeners: { "click": "onClick($event.button,$event.ctrlKey,$event.shiftKey,$event.altKey,$event.metaKey)" }, properties: { "attr.target": "this.target" } }, usesOnChanges: true, ngImport: i0 });
|
5871 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterLink, decorators: [{
|
5872 | type: Directive,
|
5873 | args: [{
|
5874 | selector: '[routerLink]',
|
5875 | standalone: true,
|
5876 | }]
|
5877 | }], ctorParameters: function () { return [{ type: Router }, { type: ActivatedRoute }, { type: undefined, decorators: [{
|
5878 | type: Attribute,
|
5879 | args: ['tabindex']
|
5880 | }] }, { type: i0.Renderer2 }, { type: i0.ElementRef }, { type: i3.LocationStrategy }]; }, propDecorators: { target: [{
|
5881 | type: HostBinding,
|
5882 | args: ['attr.target']
|
5883 | }, {
|
5884 | type: Input
|
5885 | }], queryParams: [{
|
5886 | type: Input
|
5887 | }], fragment: [{
|
5888 | type: Input
|
5889 | }], queryParamsHandling: [{
|
5890 | type: Input
|
5891 | }], state: [{
|
5892 | type: Input
|
5893 | }], relativeTo: [{
|
5894 | type: Input
|
5895 | }], preserveFragment: [{
|
5896 | type: Input
|
5897 | }], skipLocationChange: [{
|
5898 | type: Input
|
5899 | }], replaceUrl: [{
|
5900 | type: Input
|
5901 | }], routerLink: [{
|
5902 | type: Input
|
5903 | }], onClick: [{
|
5904 | type: HostListener,
|
5905 | args: ['click',
|
5906 | ['$event.button', '$event.ctrlKey', '$event.shiftKey', '$event.altKey', '$event.metaKey']]
|
5907 | }] } });
|
5908 |
|
5909 | /**
|
5910 | *
|
5911 | * @description
|
5912 | *
|
5913 | * Tracks whether the linked route of an element is currently active, and allows you
|
5914 | * to specify one or more CSS classes to add to the element when the linked route
|
5915 | * is active.
|
5916 | *
|
5917 | * Use this directive to create a visual distinction for elements associated with an active route.
|
5918 | * For example, the following code highlights the word "Bob" when the router
|
5919 | * activates the associated route:
|
5920 | *
|
5921 | * ```
|
5922 | * <a routerLink="/user/bob" routerLinkActive="active-link">Bob</a>
|
5923 | * ```
|
5924 | *
|
5925 | * Whenever the URL is either '/user' or '/user/bob', the "active-link" class is
|
5926 | * added to the anchor tag. If the URL changes, the class is removed.
|
5927 | *
|
5928 | * You can set more than one class using a space-separated string or an array.
|
5929 | * For example:
|
5930 | *
|
5931 | * ```
|
5932 | * <a routerLink="/user/bob" routerLinkActive="class1 class2">Bob</a>
|
5933 | * <a routerLink="/user/bob" [routerLinkActive]="['class1', 'class2']">Bob</a>
|
5934 | * ```
|
5935 | *
|
5936 | * To add the classes only when the URL matches the link exactly, add the option `exact: true`:
|
5937 | *
|
5938 | * ```
|
5939 | * <a routerLink="/user/bob" routerLinkActive="active-link" [routerLinkActiveOptions]="{exact:
|
5940 | * true}">Bob</a>
|
5941 | * ```
|
5942 | *
|
5943 | * To directly check the `isActive` status of the link, assign the `RouterLinkActive`
|
5944 | * instance to a template variable.
|
5945 | * For example, the following checks the status without assigning any CSS classes:
|
5946 | *
|
5947 | * ```
|
5948 | * <a routerLink="/user/bob" routerLinkActive #rla="routerLinkActive">
|
5949 | * Bob {{ rla.isActive ? '(already open)' : ''}}
|
5950 | * </a>
|
5951 | * ```
|
5952 | *
|
5953 | * You can apply the `RouterLinkActive` directive to an ancestor of linked elements.
|
5954 | * For example, the following sets the active-link class on the `<div>` parent tag
|
5955 | * when the URL is either '/user/jim' or '/user/bob'.
|
5956 | *
|
5957 | * ```
|
5958 | * <div routerLinkActive="active-link" [routerLinkActiveOptions]="{exact: true}">
|
5959 | * <a routerLink="/user/jim">Jim</a>
|
5960 | * <a routerLink="/user/bob">Bob</a>
|
5961 | * </div>
|
5962 | * ```
|
5963 | *
|
5964 | * The `RouterLinkActive` directive can also be used to set the aria-current attribute
|
5965 | * to provide an alternative distinction for active elements to visually impaired users.
|
5966 | *
|
5967 | * For example, the following code adds the 'active' class to the Home Page link when it is
|
5968 | * indeed active and in such case also sets its aria-current attribute to 'page':
|
5969 | *
|
5970 | * ```
|
5971 | * <a routerLink="/" routerLinkActive="active" ariaCurrentWhenActive="page">Home Page</a>
|
5972 | * ```
|
5973 | *
|
5974 | * @ngModule RouterModule
|
5975 | *
|
5976 | * @publicApi
|
5977 | */
|
5978 | class RouterLinkActive {
|
5979 | get isActive() {
|
5980 | return this._isActive;
|
5981 | }
|
5982 | constructor(router, element, renderer, cdr, link) {
|
5983 | this.router = router;
|
5984 | this.element = element;
|
5985 | this.renderer = renderer;
|
5986 | this.cdr = cdr;
|
5987 | this.link = link;
|
5988 | this.classes = [];
|
5989 | this._isActive = false;
|
5990 | /**
|
5991 | * Options to configure how to determine if the router link is active.
|
5992 | *
|
5993 | * These options are passed to the `Router.isActive()` function.
|
5994 | *
|
5995 | * @see Router.isActive
|
5996 | */
|
5997 | this.routerLinkActiveOptions = { exact: false };
|
5998 | /**
|
5999 | *
|
6000 | * You can use the output `isActiveChange` to get notified each time the link becomes
|
6001 | * active or inactive.
|
6002 | *
|
6003 | * Emits:
|
6004 | * true -> Route is active
|
6005 | * false -> Route is inactive
|
6006 | *
|
6007 | * ```
|
6008 | * <a
|
6009 | * routerLink="/user/bob"
|
6010 | * routerLinkActive="active-link"
|
6011 | * (isActiveChange)="this.onRouterLinkActive($event)">Bob</a>
|
6012 | * ```
|
6013 | */
|
6014 | this.isActiveChange = new EventEmitter();
|
6015 | this.routerEventsSubscription = router.events.subscribe((s) => {
|
6016 | if (s instanceof NavigationEnd) {
|
6017 | this.update();
|
6018 | }
|
6019 | });
|
6020 | }
|
6021 | /** @nodoc */
|
6022 | ngAfterContentInit() {
|
6023 | // `of(null)` is used to force subscribe body to execute once immediately (like `startWith`).
|
6024 | of(this.links.changes, of(null)).pipe(mergeAll()).subscribe(_ => {
|
6025 | this.update();
|
6026 | this.subscribeToEachLinkOnChanges();
|
6027 | });
|
6028 | }
|
6029 | subscribeToEachLinkOnChanges() {
|
6030 | this.linkInputChangesSubscription?.unsubscribe();
|
6031 | const allLinkChanges = [...this.links.toArray(), this.link]
|
6032 | .filter((link) => !!link)
|
6033 | .map(link => link.onChanges);
|
6034 | this.linkInputChangesSubscription = from(allLinkChanges).pipe(mergeAll()).subscribe(link => {
|
6035 | if (this._isActive !== this.isLinkActive(this.router)(link)) {
|
6036 | this.update();
|
6037 | }
|
6038 | });
|
6039 | }
|
6040 | set routerLinkActive(data) {
|
6041 | const classes = Array.isArray(data) ? data : data.split(' ');
|
6042 | this.classes = classes.filter(c => !!c);
|
6043 | }
|
6044 | /** @nodoc */
|
6045 | ngOnChanges(changes) {
|
6046 | this.update();
|
6047 | }
|
6048 | /** @nodoc */
|
6049 | ngOnDestroy() {
|
6050 | this.routerEventsSubscription.unsubscribe();
|
6051 | this.linkInputChangesSubscription?.unsubscribe();
|
6052 | }
|
6053 | update() {
|
6054 | if (!this.links || !this.router.navigated)
|
6055 | return;
|
6056 | Promise.resolve().then(() => {
|
6057 | const hasActiveLinks = this.hasActiveLinks();
|
6058 | if (this._isActive !== hasActiveLinks) {
|
6059 | this._isActive = hasActiveLinks;
|
6060 | this.cdr.markForCheck();
|
6061 | this.classes.forEach((c) => {
|
6062 | if (hasActiveLinks) {
|
6063 | this.renderer.addClass(this.element.nativeElement, c);
|
6064 | }
|
6065 | else {
|
6066 | this.renderer.removeClass(this.element.nativeElement, c);
|
6067 | }
|
6068 | });
|
6069 | if (hasActiveLinks && this.ariaCurrentWhenActive !== undefined) {
|
6070 | this.renderer.setAttribute(this.element.nativeElement, 'aria-current', this.ariaCurrentWhenActive.toString());
|
6071 | }
|
6072 | else {
|
6073 | this.renderer.removeAttribute(this.element.nativeElement, 'aria-current');
|
6074 | }
|
6075 | // Emit on isActiveChange after classes are updated
|
6076 | this.isActiveChange.emit(hasActiveLinks);
|
6077 | }
|
6078 | });
|
6079 | }
|
6080 | isLinkActive(router) {
|
6081 | const options = isActiveMatchOptions(this.routerLinkActiveOptions) ?
|
6082 | this.routerLinkActiveOptions :
|
6083 | // While the types should disallow `undefined` here, it's possible without strict inputs
|
6084 | (this.routerLinkActiveOptions.exact || false);
|
6085 | return (link) => link.urlTree ? router.isActive(link.urlTree, options) : false;
|
6086 | }
|
6087 | hasActiveLinks() {
|
6088 | const isActiveCheckFn = this.isLinkActive(this.router);
|
6089 | return this.link && isActiveCheckFn(this.link) || this.links.some(isActiveCheckFn);
|
6090 | }
|
6091 | }
|
6092 | RouterLinkActive.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterLinkActive, deps: [{ token: Router }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.ChangeDetectorRef }, { token: RouterLink, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
|
6093 | RouterLinkActive.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.3", type: RouterLinkActive, isStandalone: true, selector: "[routerLinkActive]", inputs: { routerLinkActiveOptions: "routerLinkActiveOptions", ariaCurrentWhenActive: "ariaCurrentWhenActive", routerLinkActive: "routerLinkActive" }, outputs: { isActiveChange: "isActiveChange" }, queries: [{ propertyName: "links", predicate: RouterLink, descendants: true }], exportAs: ["routerLinkActive"], usesOnChanges: true, ngImport: i0 });
|
6094 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterLinkActive, decorators: [{
|
6095 | type: Directive,
|
6096 | args: [{
|
6097 | selector: '[routerLinkActive]',
|
6098 | exportAs: 'routerLinkActive',
|
6099 | standalone: true,
|
6100 | }]
|
6101 | }], ctorParameters: function () { return [{ type: Router }, { type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i0.ChangeDetectorRef }, { type: RouterLink, decorators: [{
|
6102 | type: Optional
|
6103 | }] }]; }, propDecorators: { links: [{
|
6104 | type: ContentChildren,
|
6105 | args: [RouterLink, { descendants: true }]
|
6106 | }], routerLinkActiveOptions: [{
|
6107 | type: Input
|
6108 | }], ariaCurrentWhenActive: [{
|
6109 | type: Input
|
6110 | }], isActiveChange: [{
|
6111 | type: Output
|
6112 | }], routerLinkActive: [{
|
6113 | type: Input
|
6114 | }] } });
|
6115 | /**
|
6116 | * Use instead of `'paths' in options` to be compatible with property renaming
|
6117 | */
|
6118 | function isActiveMatchOptions(options) {
|
6119 | return !!options.paths;
|
6120 | }
|
6121 |
|
6122 | /**
|
6123 | * @description
|
6124 | *
|
6125 | * Provides a preloading strategy.
|
6126 | *
|
6127 | * @publicApi
|
6128 | */
|
6129 | class PreloadingStrategy {
|
6130 | }
|
6131 | /**
|
6132 | * @description
|
6133 | *
|
6134 | * Provides a preloading strategy that preloads all modules as quickly as possible.
|
6135 | *
|
6136 | * ```
|
6137 | * RouterModule.forRoot(ROUTES, {preloadingStrategy: PreloadAllModules})
|
6138 | * ```
|
6139 | *
|
6140 | * @publicApi
|
6141 | */
|
6142 | class PreloadAllModules {
|
6143 | preload(route, fn) {
|
6144 | return fn().pipe(catchError(() => of(null)));
|
6145 | }
|
6146 | }
|
6147 | PreloadAllModules.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: PreloadAllModules, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
6148 | PreloadAllModules.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: PreloadAllModules, providedIn: 'root' });
|
6149 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: PreloadAllModules, decorators: [{
|
6150 | type: Injectable,
|
6151 | args: [{ providedIn: 'root' }]
|
6152 | }] });
|
6153 | /**
|
6154 | * @description
|
6155 | *
|
6156 | * Provides a preloading strategy that does not preload any modules.
|
6157 | *
|
6158 | * This strategy is enabled by default.
|
6159 | *
|
6160 | * @publicApi
|
6161 | */
|
6162 | class NoPreloading {
|
6163 | preload(route, fn) {
|
6164 | return of(null);
|
6165 | }
|
6166 | }
|
6167 | NoPreloading.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: NoPreloading, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
6168 | NoPreloading.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: NoPreloading, providedIn: 'root' });
|
6169 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: NoPreloading, decorators: [{
|
6170 | type: Injectable,
|
6171 | args: [{ providedIn: 'root' }]
|
6172 | }] });
|
6173 | /**
|
6174 | * The preloader optimistically loads all router configurations to
|
6175 | * make navigations into lazily-loaded sections of the application faster.
|
6176 | *
|
6177 | * The preloader runs in the background. When the router bootstraps, the preloader
|
6178 | * starts listening to all navigation events. After every such event, the preloader
|
6179 | * will check if any configurations can be loaded lazily.
|
6180 | *
|
6181 | * If a route is protected by `canLoad` guards, the preloaded will not load it.
|
6182 | *
|
6183 | * @publicApi
|
6184 | */
|
6185 | class RouterPreloader {
|
6186 | constructor(router, compiler, injector, preloadingStrategy, loader) {
|
6187 | this.router = router;
|
6188 | this.injector = injector;
|
6189 | this.preloadingStrategy = preloadingStrategy;
|
6190 | this.loader = loader;
|
6191 | }
|
6192 | setUpPreloading() {
|
6193 | this.subscription =
|
6194 | this.router.events
|
6195 | .pipe(filter((e) => e instanceof NavigationEnd), concatMap(() => this.preload()))
|
6196 | .subscribe(() => { });
|
6197 | }
|
6198 | preload() {
|
6199 | return this.processRoutes(this.injector, this.router.config);
|
6200 | }
|
6201 | /** @nodoc */
|
6202 | ngOnDestroy() {
|
6203 | if (this.subscription) {
|
6204 | this.subscription.unsubscribe();
|
6205 | }
|
6206 | }
|
6207 | processRoutes(injector, routes) {
|
6208 | const res = [];
|
6209 | for (const route of routes) {
|
6210 | if (route.providers && !route._injector) {
|
6211 | route._injector =
|
6212 | createEnvironmentInjector(route.providers, injector, `Route: ${route.path}`);
|
6213 | }
|
6214 | const injectorForCurrentRoute = route._injector ?? injector;
|
6215 | const injectorForChildren = route._loadedInjector ?? injectorForCurrentRoute;
|
6216 | // Note that `canLoad` is only checked as a condition that prevents `loadChildren` and not
|
6217 | // `loadComponent`. `canLoad` guards only block loading of child routes by design. This
|
6218 | // happens as a consequence of needing to descend into children for route matching immediately
|
6219 | // while component loading is deferred until route activation. Because `canLoad` guards can
|
6220 | // have side effects, we cannot execute them here so we instead skip preloading altogether
|
6221 | // when present. Lastly, it remains to be decided whether `canLoad` should behave this way
|
6222 | // at all. Code splitting and lazy loading is separate from client-side authorization checks
|
6223 | // and should not be used as a security measure to prevent loading of code.
|
6224 | if ((route.loadChildren && !route._loadedRoutes && route.canLoad === undefined) ||
|
6225 | (route.loadComponent && !route._loadedComponent)) {
|
6226 | res.push(this.preloadConfig(injectorForCurrentRoute, route));
|
6227 | }
|
6228 | else if (route.children || route._loadedRoutes) {
|
6229 | res.push(this.processRoutes(injectorForChildren, (route.children ?? route._loadedRoutes)));
|
6230 | }
|
6231 | }
|
6232 | return from(res).pipe(mergeAll());
|
6233 | }
|
6234 | preloadConfig(injector, route) {
|
6235 | return this.preloadingStrategy.preload(route, () => {
|
6236 | let loadedChildren$;
|
6237 | if (route.loadChildren && route.canLoad === undefined) {
|
6238 | loadedChildren$ = this.loader.loadChildren(injector, route);
|
6239 | }
|
6240 | else {
|
6241 | loadedChildren$ = of(null);
|
6242 | }
|
6243 | const recursiveLoadChildren$ = loadedChildren$.pipe(mergeMap((config) => {
|
6244 | if (config === null) {
|
6245 | return of(void 0);
|
6246 | }
|
6247 | route._loadedRoutes = config.routes;
|
6248 | route._loadedInjector = config.injector;
|
6249 | // If the loaded config was a module, use that as the module/module injector going
|
6250 | // forward. Otherwise, continue using the current module/module injector.
|
6251 | return this.processRoutes(config.injector ?? injector, config.routes);
|
6252 | }));
|
6253 | if (route.loadComponent && !route._loadedComponent) {
|
6254 | const loadComponent$ = this.loader.loadComponent(route);
|
6255 | return from([recursiveLoadChildren$, loadComponent$]).pipe(mergeAll());
|
6256 | }
|
6257 | else {
|
6258 | return recursiveLoadChildren$;
|
6259 | }
|
6260 | });
|
6261 | }
|
6262 | }
|
6263 | RouterPreloader.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterPreloader, deps: [{ token: Router }, { token: i0.Compiler }, { token: i0.EnvironmentInjector }, { token: PreloadingStrategy }, { token: RouterConfigLoader }], target: i0.ɵɵFactoryTarget.Injectable });
|
6264 | RouterPreloader.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterPreloader, providedIn: 'root' });
|
6265 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterPreloader, decorators: [{
|
6266 | type: Injectable,
|
6267 | args: [{ providedIn: 'root' }]
|
6268 | }], ctorParameters: function () { return [{ type: Router }, { type: i0.Compiler }, { type: i0.EnvironmentInjector }, { type: PreloadingStrategy }, { type: RouterConfigLoader }]; } });
|
6269 |
|
6270 | const ROUTER_SCROLLER = new InjectionToken('');
|
6271 | class RouterScroller {
|
6272 | /** @nodoc */
|
6273 | constructor(urlSerializer, transitions, viewportScroller, zone, options = {}) {
|
6274 | this.urlSerializer = urlSerializer;
|
6275 | this.transitions = transitions;
|
6276 | this.viewportScroller = viewportScroller;
|
6277 | this.zone = zone;
|
6278 | this.options = options;
|
6279 | this.lastId = 0;
|
6280 | this.lastSource = 'imperative';
|
6281 | this.restoredId = 0;
|
6282 | this.store = {};
|
6283 | // Default both options to 'disabled'
|
6284 | options.scrollPositionRestoration = options.scrollPositionRestoration || 'disabled';
|
6285 | options.anchorScrolling = options.anchorScrolling || 'disabled';
|
6286 | }
|
6287 | init() {
|
6288 | // we want to disable the automatic scrolling because having two places
|
6289 | // responsible for scrolling results race conditions, especially given
|
6290 | // that browser don't implement this behavior consistently
|
6291 | if (this.options.scrollPositionRestoration !== 'disabled') {
|
6292 | this.viewportScroller.setHistoryScrollRestoration('manual');
|
6293 | }
|
6294 | this.routerEventsSubscription = this.createScrollEvents();
|
6295 | this.scrollEventsSubscription = this.consumeScrollEvents();
|
6296 | }
|
6297 | createScrollEvents() {
|
6298 | return this.transitions.events.subscribe(e => {
|
6299 | if (e instanceof NavigationStart) {
|
6300 | // store the scroll position of the current stable navigations.
|
6301 | this.store[this.lastId] = this.viewportScroller.getScrollPosition();
|
6302 | this.lastSource = e.navigationTrigger;
|
6303 | this.restoredId = e.restoredState ? e.restoredState.navigationId : 0;
|
6304 | }
|
6305 | else if (e instanceof NavigationEnd) {
|
6306 | this.lastId = e.id;
|
6307 | this.scheduleScrollEvent(e, this.urlSerializer.parse(e.urlAfterRedirects).fragment);
|
6308 | }
|
6309 | });
|
6310 | }
|
6311 | consumeScrollEvents() {
|
6312 | return this.transitions.events.subscribe(e => {
|
6313 | if (!(e instanceof Scroll))
|
6314 | return;
|
6315 | // a popstate event. The pop state event will always ignore anchor scrolling.
|
6316 | if (e.position) {
|
6317 | if (this.options.scrollPositionRestoration === 'top') {
|
6318 | this.viewportScroller.scrollToPosition([0, 0]);
|
6319 | }
|
6320 | else if (this.options.scrollPositionRestoration === 'enabled') {
|
6321 | this.viewportScroller.scrollToPosition(e.position);
|
6322 | }
|
6323 | // imperative navigation "forward"
|
6324 | }
|
6325 | else {
|
6326 | if (e.anchor && this.options.anchorScrolling === 'enabled') {
|
6327 | this.viewportScroller.scrollToAnchor(e.anchor);
|
6328 | }
|
6329 | else if (this.options.scrollPositionRestoration !== 'disabled') {
|
6330 | this.viewportScroller.scrollToPosition([0, 0]);
|
6331 | }
|
6332 | }
|
6333 | });
|
6334 | }
|
6335 | scheduleScrollEvent(routerEvent, anchor) {
|
6336 | this.zone.runOutsideAngular(() => {
|
6337 | // The scroll event needs to be delayed until after change detection. Otherwise, we may
|
6338 | // attempt to restore the scroll position before the router outlet has fully rendered the
|
6339 | // component by executing its update block of the template function.
|
6340 | setTimeout(() => {
|
6341 | this.zone.run(() => {
|
6342 | this.transitions.events.next(new Scroll(routerEvent, this.lastSource === 'popstate' ? this.store[this.restoredId] : null, anchor));
|
6343 | });
|
6344 | }, 0);
|
6345 | });
|
6346 | }
|
6347 | /** @nodoc */
|
6348 | ngOnDestroy() {
|
6349 | this.routerEventsSubscription?.unsubscribe();
|
6350 | this.scrollEventsSubscription?.unsubscribe();
|
6351 | }
|
6352 | }
|
6353 | RouterScroller.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterScroller, deps: "invalid", target: i0.ɵɵFactoryTarget.Injectable });
|
6354 | RouterScroller.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterScroller });
|
6355 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterScroller, decorators: [{
|
6356 | type: Injectable
|
6357 | }], ctorParameters: function () { return [{ type: UrlSerializer }, { type: NavigationTransitions }, { type: i3.ViewportScroller }, { type: i0.NgZone }, { type: undefined }]; } });
|
6358 |
|
6359 | var NavigationResult;
|
6360 | (function (NavigationResult) {
|
6361 | NavigationResult[NavigationResult["COMPLETE"] = 0] = "COMPLETE";
|
6362 | NavigationResult[NavigationResult["FAILED"] = 1] = "FAILED";
|
6363 | NavigationResult[NavigationResult["REDIRECTING"] = 2] = "REDIRECTING";
|
6364 | })(NavigationResult || (NavigationResult = {}));
|
6365 | /**
|
6366 | * Performs the given action once the router finishes its next/current navigation.
|
6367 | *
|
6368 | * The navigation is considered complete under the following conditions:
|
6369 | * - `NavigationCancel` event emits and the code is not `NavigationCancellationCode.Redirect` or
|
6370 | * `NavigationCancellationCode.SupersededByNewNavigation`. In these cases, the
|
6371 | * redirecting/superseding navigation must finish.
|
6372 | * - `NavigationError`, `NavigationEnd`, or `NavigationSkipped` event emits
|
6373 | */
|
6374 | function afterNextNavigation(router, action) {
|
6375 | router.events
|
6376 | .pipe(filter((e) => e instanceof NavigationEnd || e instanceof NavigationCancel ||
|
6377 | e instanceof NavigationError || e instanceof NavigationSkipped), map(e => {
|
6378 | if (e instanceof NavigationEnd || e instanceof NavigationSkipped) {
|
6379 | return NavigationResult.COMPLETE;
|
6380 | }
|
6381 | const redirecting = e instanceof NavigationCancel ?
|
6382 | (e.code === 0 /* NavigationCancellationCode.Redirect */ ||
|
6383 | e.code === 1 /* NavigationCancellationCode.SupersededByNewNavigation */) :
|
6384 | false;
|
6385 | return redirecting ? NavigationResult.REDIRECTING : NavigationResult.FAILED;
|
6386 | }), filter((result) => result !== NavigationResult.REDIRECTING), take(1))
|
6387 | .subscribe(() => {
|
6388 | action();
|
6389 | });
|
6390 | }
|
6391 |
|
6392 | const NG_DEV_MODE$1 = typeof ngDevMode === 'undefined' || ngDevMode;
|
6393 | /**
|
6394 | * Sets up providers necessary to enable `Router` functionality for the application.
|
6395 | * Allows to configure a set of routes as well as extra features that should be enabled.
|
6396 | *
|
6397 | * @usageNotes
|
6398 | *
|
6399 | * Basic example of how you can add a Router to your application:
|
6400 | * ```
|
6401 | * const appRoutes: Routes = [];
|
6402 | * bootstrapApplication(AppComponent, {
|
6403 | * providers: [provideRouter(appRoutes)]
|
6404 | * });
|
6405 | * ```
|
6406 | *
|
6407 | * You can also enable optional features in the Router by adding functions from the `RouterFeatures`
|
6408 | * type:
|
6409 | * ```
|
6410 | * const appRoutes: Routes = [];
|
6411 | * bootstrapApplication(AppComponent,
|
6412 | * {
|
6413 | * providers: [
|
6414 | * provideRouter(appRoutes,
|
6415 | * withDebugTracing(),
|
6416 | * withRouterConfig({paramsInheritanceStrategy: 'always'}))
|
6417 | * ]
|
6418 | * }
|
6419 | * );
|
6420 | * ```
|
6421 | *
|
6422 | * @see `RouterFeatures`
|
6423 | *
|
6424 | * @publicApi
|
6425 | * @param routes A set of `Route`s to use for the application routing table.
|
6426 | * @param features Optional features to configure additional router behaviors.
|
6427 | * @returns A set of providers to setup a Router.
|
6428 | */
|
6429 | function provideRouter(routes, ...features) {
|
6430 | return makeEnvironmentProviders([
|
6431 | { provide: ROUTES, multi: true, useValue: routes },
|
6432 | NG_DEV_MODE$1 ? { provide: ROUTER_IS_PROVIDED, useValue: true } : [],
|
6433 | { provide: ActivatedRoute, useFactory: rootRoute, deps: [Router] },
|
6434 | { provide: APP_BOOTSTRAP_LISTENER, multi: true, useFactory: getBootstrapListener },
|
6435 | features.map(feature => feature.ɵproviders),
|
6436 | ]);
|
6437 | }
|
6438 | function rootRoute(router) {
|
6439 | return router.routerState.root;
|
6440 | }
|
6441 | /**
|
6442 | * Helper function to create an object that represents a Router feature.
|
6443 | */
|
6444 | function routerFeature(kind, providers) {
|
6445 | return { ɵkind: kind, ɵproviders: providers };
|
6446 | }
|
6447 | /**
|
6448 | * An Injection token used to indicate whether `provideRouter` or `RouterModule.forRoot` was ever
|
6449 | * called.
|
6450 | */
|
6451 | const ROUTER_IS_PROVIDED = new InjectionToken('', { providedIn: 'root', factory: () => false });
|
6452 | const routerIsProvidedDevModeCheck = {
|
6453 | provide: ENVIRONMENT_INITIALIZER,
|
6454 | multi: true,
|
6455 | useFactory() {
|
6456 | return () => {
|
6457 | if (!inject(ROUTER_IS_PROVIDED)) {
|
6458 | console.warn('`provideRoutes` was called without `provideRouter` or `RouterModule.forRoot`. ' +
|
6459 | 'This is likely a mistake.');
|
6460 | }
|
6461 | };
|
6462 | }
|
6463 | };
|
6464 | /**
|
6465 | * Registers a [DI provider](guide/glossary#provider) for a set of routes.
|
6466 | * @param routes The route configuration to provide.
|
6467 | *
|
6468 | * @usageNotes
|
6469 | *
|
6470 | * ```
|
6471 | * @NgModule({
|
6472 | * providers: [provideRoutes(ROUTES)]
|
6473 | * })
|
6474 | * class LazyLoadedChildModule {}
|
6475 | * ```
|
6476 | *
|
6477 | * @deprecated If necessary, provide routes using the `ROUTES` `InjectionToken`.
|
6478 | * @see `ROUTES`
|
6479 | * @publicApi
|
6480 | */
|
6481 | function provideRoutes(routes) {
|
6482 | return [
|
6483 | { provide: ROUTES, multi: true, useValue: routes },
|
6484 | NG_DEV_MODE$1 ? routerIsProvidedDevModeCheck : [],
|
6485 | ];
|
6486 | }
|
6487 | /**
|
6488 | * Enables customizable scrolling behavior for router navigations.
|
6489 | *
|
6490 | * @usageNotes
|
6491 | *
|
6492 | * Basic example of how you can enable scrolling feature:
|
6493 | * ```
|
6494 | * const appRoutes: Routes = [];
|
6495 | * bootstrapApplication(AppComponent,
|
6496 | * {
|
6497 | * providers: [
|
6498 | * provideRouter(appRoutes, withInMemoryScrolling())
|
6499 | * ]
|
6500 | * }
|
6501 | * );
|
6502 | * ```
|
6503 | *
|
6504 | * @see `provideRouter`
|
6505 | * @see `ViewportScroller`
|
6506 | *
|
6507 | * @publicApi
|
6508 | * @param options Set of configuration parameters to customize scrolling behavior, see
|
6509 | * `InMemoryScrollingOptions` for additional information.
|
6510 | * @returns A set of providers for use with `provideRouter`.
|
6511 | */
|
6512 | function withInMemoryScrolling(options = {}) {
|
6513 | const providers = [{
|
6514 | provide: ROUTER_SCROLLER,
|
6515 | useFactory: () => {
|
6516 | const viewportScroller = inject(ViewportScroller);
|
6517 | const zone = inject(NgZone);
|
6518 | const transitions = inject(NavigationTransitions);
|
6519 | const urlSerializer = inject(UrlSerializer);
|
6520 | return new RouterScroller(urlSerializer, transitions, viewportScroller, zone, options);
|
6521 | },
|
6522 | }];
|
6523 | return routerFeature(4 /* RouterFeatureKind.InMemoryScrollingFeature */, providers);
|
6524 | }
|
6525 | function getBootstrapListener() {
|
6526 | const injector = inject(Injector);
|
6527 | return (bootstrappedComponentRef) => {
|
6528 | const ref = injector.get(ApplicationRef);
|
6529 | if (bootstrappedComponentRef !== ref.components[0]) {
|
6530 | return;
|
6531 | }
|
6532 | const router = injector.get(Router);
|
6533 | const bootstrapDone = injector.get(BOOTSTRAP_DONE);
|
6534 | if (injector.get(INITIAL_NAVIGATION) === 1 /* InitialNavigation.EnabledNonBlocking */) {
|
6535 | router.initialNavigation();
|
6536 | }
|
6537 | injector.get(ROUTER_PRELOADER, null, InjectFlags.Optional)?.setUpPreloading();
|
6538 | injector.get(ROUTER_SCROLLER, null, InjectFlags.Optional)?.init();
|
6539 | router.resetRootComponentType(ref.componentTypes[0]);
|
6540 | if (!bootstrapDone.closed) {
|
6541 | bootstrapDone.next();
|
6542 | bootstrapDone.unsubscribe();
|
6543 | }
|
6544 | };
|
6545 | }
|
6546 | /**
|
6547 | * A subject used to indicate that the bootstrapping phase is done. When initial navigation is
|
6548 | * `enabledBlocking`, the first navigation waits until bootstrapping is finished before continuing
|
6549 | * to the activation phase.
|
6550 | */
|
6551 | const BOOTSTRAP_DONE = new InjectionToken(NG_DEV_MODE$1 ? 'bootstrap done indicator' : '', {
|
6552 | factory: () => {
|
6553 | return new Subject();
|
6554 | }
|
6555 | });
|
6556 | const INITIAL_NAVIGATION = new InjectionToken(NG_DEV_MODE$1 ? 'initial navigation' : '', { providedIn: 'root', factory: () => 1 /* InitialNavigation.EnabledNonBlocking */ });
|
6557 | /**
|
6558 | * Configures initial navigation to start before the root component is created.
|
6559 | *
|
6560 | * The bootstrap is blocked until the initial navigation is complete. This value is required for
|
6561 | * [server-side rendering](guide/universal) to work.
|
6562 | *
|
6563 | * @usageNotes
|
6564 | *
|
6565 | * Basic example of how you can enable this navigation behavior:
|
6566 | * ```
|
6567 | * const appRoutes: Routes = [];
|
6568 | * bootstrapApplication(AppComponent,
|
6569 | * {
|
6570 | * providers: [
|
6571 | * provideRouter(appRoutes, withEnabledBlockingInitialNavigation())
|
6572 | * ]
|
6573 | * }
|
6574 | * );
|
6575 | * ```
|
6576 | *
|
6577 | * @see `provideRouter`
|
6578 | *
|
6579 | * @publicApi
|
6580 | * @returns A set of providers for use with `provideRouter`.
|
6581 | */
|
6582 | function withEnabledBlockingInitialNavigation() {
|
6583 | const providers = [
|
6584 | { provide: INITIAL_NAVIGATION, useValue: 0 /* InitialNavigation.EnabledBlocking */ },
|
6585 | {
|
6586 | provide: APP_INITIALIZER,
|
6587 | multi: true,
|
6588 | deps: [Injector],
|
6589 | useFactory: (injector) => {
|
6590 | const locationInitialized = injector.get(LOCATION_INITIALIZED, Promise.resolve());
|
6591 | return () => {
|
6592 | return locationInitialized.then(() => {
|
6593 | return new Promise(resolve => {
|
6594 | const router = injector.get(Router);
|
6595 | const bootstrapDone = injector.get(BOOTSTRAP_DONE);
|
6596 | afterNextNavigation(router, () => {
|
6597 | // Unblock APP_INITIALIZER in case the initial navigation was canceled or errored
|
6598 | // without a redirect.
|
6599 | resolve(true);
|
6600 | });
|
6601 | injector.get(NavigationTransitions).afterPreactivation = () => {
|
6602 | // Unblock APP_INITIALIZER once we get to `afterPreactivation`. At this point, we
|
6603 | // assume activation will complete successfully (even though this is not
|
6604 | // guaranteed).
|
6605 | resolve(true);
|
6606 | return bootstrapDone.closed ? of(void 0) : bootstrapDone;
|
6607 | };
|
6608 | router.initialNavigation();
|
6609 | });
|
6610 | });
|
6611 | };
|
6612 | }
|
6613 | },
|
6614 | ];
|
6615 | return routerFeature(2 /* RouterFeatureKind.EnabledBlockingInitialNavigationFeature */, providers);
|
6616 | }
|
6617 | /**
|
6618 | * Disables initial navigation.
|
6619 | *
|
6620 | * Use if there is a reason to have more control over when the router starts its initial navigation
|
6621 | * due to some complex initialization logic.
|
6622 | *
|
6623 | * @usageNotes
|
6624 | *
|
6625 | * Basic example of how you can disable initial navigation:
|
6626 | * ```
|
6627 | * const appRoutes: Routes = [];
|
6628 | * bootstrapApplication(AppComponent,
|
6629 | * {
|
6630 | * providers: [
|
6631 | * provideRouter(appRoutes, withDisabledInitialNavigation())
|
6632 | * ]
|
6633 | * }
|
6634 | * );
|
6635 | * ```
|
6636 | *
|
6637 | * @see `provideRouter`
|
6638 | *
|
6639 | * @returns A set of providers for use with `provideRouter`.
|
6640 | *
|
6641 | * @publicApi
|
6642 | */
|
6643 | function withDisabledInitialNavigation() {
|
6644 | const providers = [
|
6645 | {
|
6646 | provide: APP_INITIALIZER,
|
6647 | multi: true,
|
6648 | useFactory: () => {
|
6649 | const router = inject(Router);
|
6650 | return () => {
|
6651 | router.setUpLocationChangeListener();
|
6652 | };
|
6653 | }
|
6654 | },
|
6655 | { provide: INITIAL_NAVIGATION, useValue: 2 /* InitialNavigation.Disabled */ }
|
6656 | ];
|
6657 | return routerFeature(3 /* RouterFeatureKind.DisabledInitialNavigationFeature */, providers);
|
6658 | }
|
6659 | /**
|
6660 | * Enables logging of all internal navigation events to the console.
|
6661 | * Extra logging might be useful for debugging purposes to inspect Router event sequence.
|
6662 | *
|
6663 | * @usageNotes
|
6664 | *
|
6665 | * Basic example of how you can enable debug tracing:
|
6666 | * ```
|
6667 | * const appRoutes: Routes = [];
|
6668 | * bootstrapApplication(AppComponent,
|
6669 | * {
|
6670 | * providers: [
|
6671 | * provideRouter(appRoutes, withDebugTracing())
|
6672 | * ]
|
6673 | * }
|
6674 | * );
|
6675 | * ```
|
6676 | *
|
6677 | * @see `provideRouter`
|
6678 | *
|
6679 | * @returns A set of providers for use with `provideRouter`.
|
6680 | *
|
6681 | * @publicApi
|
6682 | */
|
6683 | function withDebugTracing() {
|
6684 | let providers = [];
|
6685 | if (NG_DEV_MODE$1) {
|
6686 | providers = [{
|
6687 | provide: ENVIRONMENT_INITIALIZER,
|
6688 | multi: true,
|
6689 | useFactory: () => {
|
6690 | const router = inject(Router);
|
6691 | return () => router.events.subscribe((e) => {
|
6692 | // tslint:disable:no-console
|
6693 | console.group?.(`Router Event: ${e.constructor.name}`);
|
6694 | console.log(stringifyEvent(e));
|
6695 | console.log(e);
|
6696 | console.groupEnd?.();
|
6697 | // tslint:enable:no-console
|
6698 | });
|
6699 | }
|
6700 | }];
|
6701 | }
|
6702 | else {
|
6703 | providers = [];
|
6704 | }
|
6705 | return routerFeature(1 /* RouterFeatureKind.DebugTracingFeature */, providers);
|
6706 | }
|
6707 | const ROUTER_PRELOADER = new InjectionToken(NG_DEV_MODE$1 ? 'router preloader' : '');
|
6708 | /**
|
6709 | * Allows to configure a preloading strategy to use. The strategy is configured by providing a
|
6710 | * reference to a class that implements a `PreloadingStrategy`.
|
6711 | *
|
6712 | * @usageNotes
|
6713 | *
|
6714 | * Basic example of how you can configure preloading:
|
6715 | * ```
|
6716 | * const appRoutes: Routes = [];
|
6717 | * bootstrapApplication(AppComponent,
|
6718 | * {
|
6719 | * providers: [
|
6720 | * provideRouter(appRoutes, withPreloading(PreloadAllModules))
|
6721 | * ]
|
6722 | * }
|
6723 | * );
|
6724 | * ```
|
6725 | *
|
6726 | * @see `provideRouter`
|
6727 | *
|
6728 | * @param preloadingStrategy A reference to a class that implements a `PreloadingStrategy` that
|
6729 | * should be used.
|
6730 | * @returns A set of providers for use with `provideRouter`.
|
6731 | *
|
6732 | * @publicApi
|
6733 | */
|
6734 | function withPreloading(preloadingStrategy) {
|
6735 | const providers = [
|
6736 | { provide: ROUTER_PRELOADER, useExisting: RouterPreloader },
|
6737 | { provide: PreloadingStrategy, useExisting: preloadingStrategy },
|
6738 | ];
|
6739 | return routerFeature(0 /* RouterFeatureKind.PreloadingFeature */, providers);
|
6740 | }
|
6741 | /**
|
6742 | * Allows to provide extra parameters to configure Router.
|
6743 | *
|
6744 | * @usageNotes
|
6745 | *
|
6746 | * Basic example of how you can provide extra configuration options:
|
6747 | * ```
|
6748 | * const appRoutes: Routes = [];
|
6749 | * bootstrapApplication(AppComponent,
|
6750 | * {
|
6751 | * providers: [
|
6752 | * provideRouter(appRoutes, withRouterConfig({
|
6753 | * onSameUrlNavigation: 'reload'
|
6754 | * }))
|
6755 | * ]
|
6756 | * }
|
6757 | * );
|
6758 | * ```
|
6759 | *
|
6760 | * @see `provideRouter`
|
6761 | *
|
6762 | * @param options A set of parameters to configure Router, see `RouterConfigOptions` for
|
6763 | * additional information.
|
6764 | * @returns A set of providers for use with `provideRouter`.
|
6765 | *
|
6766 | * @publicApi
|
6767 | */
|
6768 | function withRouterConfig(options) {
|
6769 | const providers = [
|
6770 | { provide: ROUTER_CONFIGURATION, useValue: options },
|
6771 | ];
|
6772 | return routerFeature(5 /* RouterFeatureKind.RouterConfigurationFeature */, providers);
|
6773 | }
|
6774 | /**
|
6775 | * Provides the location strategy that uses the URL fragment instead of the history API.
|
6776 | *
|
6777 | * @usageNotes
|
6778 | *
|
6779 | * Basic example of how you can use the hash location option:
|
6780 | * ```
|
6781 | * const appRoutes: Routes = [];
|
6782 | * bootstrapApplication(AppComponent,
|
6783 | * {
|
6784 | * providers: [
|
6785 | * provideRouter(appRoutes, withHashLocation())
|
6786 | * ]
|
6787 | * }
|
6788 | * );
|
6789 | * ```
|
6790 | *
|
6791 | * @see `provideRouter`
|
6792 | * @see `HashLocationStrategy`
|
6793 | *
|
6794 | * @returns A set of providers for use with `provideRouter`.
|
6795 | *
|
6796 | * @publicApi
|
6797 | */
|
6798 | function withHashLocation() {
|
6799 | const providers = [
|
6800 | { provide: LocationStrategy, useClass: HashLocationStrategy },
|
6801 | ];
|
6802 | return routerFeature(5 /* RouterFeatureKind.RouterConfigurationFeature */, providers);
|
6803 | }
|
6804 | /**
|
6805 | * Subscribes to the Router's navigation events and calls the given function when a
|
6806 | * `NavigationError` happens.
|
6807 | *
|
6808 | * This function is run inside application's injection context so you can use the `inject` function.
|
6809 | *
|
6810 | * @usageNotes
|
6811 | *
|
6812 | * Basic example of how you can use the error handler option:
|
6813 | * ```
|
6814 | * const appRoutes: Routes = [];
|
6815 | * bootstrapApplication(AppComponent,
|
6816 | * {
|
6817 | * providers: [
|
6818 | * provideRouter(appRoutes, withNavigationErrorHandler((e: NavigationError) =>
|
6819 | * inject(MyErrorTracker).trackError(e)))
|
6820 | * ]
|
6821 | * }
|
6822 | * );
|
6823 | * ```
|
6824 | *
|
6825 | * @see `NavigationError`
|
6826 | * @see `inject`
|
6827 | * @see `EnvironmentInjector#runInContext`
|
6828 | *
|
6829 | * @returns A set of providers for use with `provideRouter`.
|
6830 | *
|
6831 | * @publicApi
|
6832 | */
|
6833 | function withNavigationErrorHandler(fn) {
|
6834 | const providers = [{
|
6835 | provide: ENVIRONMENT_INITIALIZER,
|
6836 | multi: true,
|
6837 | useValue: () => {
|
6838 | const injector = inject(EnvironmentInjector);
|
6839 | inject(Router).events.subscribe((e) => {
|
6840 | if (e instanceof NavigationError) {
|
6841 | injector.runInContext(() => fn(e));
|
6842 | }
|
6843 | });
|
6844 | }
|
6845 | }];
|
6846 | return routerFeature(7 /* RouterFeatureKind.NavigationErrorHandlerFeature */, providers);
|
6847 | }
|
6848 |
|
6849 | const NG_DEV_MODE = typeof ngDevMode === 'undefined' || ngDevMode;
|
6850 | /**
|
6851 | * The directives defined in the `RouterModule`.
|
6852 | */
|
6853 | const ROUTER_DIRECTIVES = [RouterOutlet, RouterLink, RouterLinkActive, ɵEmptyOutletComponent];
|
6854 | /**
|
6855 | * @docsNotRequired
|
6856 | */
|
6857 | const ROUTER_FORROOT_GUARD = new InjectionToken(NG_DEV_MODE ? 'router duplicate forRoot guard' : 'ROUTER_FORROOT_GUARD');
|
6858 | // TODO(atscott): All of these except `ActivatedRoute` are `providedIn: 'root'`. They are only kept
|
6859 | // here to avoid a breaking change whereby the provider order matters based on where the
|
6860 | // `RouterModule`/`RouterTestingModule` is imported. These can/should be removed as a "breaking"
|
6861 | // change in a major version.
|
6862 | const ROUTER_PROVIDERS = [
|
6863 | Location,
|
6864 | { provide: UrlSerializer, useClass: DefaultUrlSerializer },
|
6865 | Router,
|
6866 | ChildrenOutletContexts,
|
6867 | { provide: ActivatedRoute, useFactory: rootRoute, deps: [Router] },
|
6868 | RouterConfigLoader,
|
6869 | // Only used to warn when `provideRoutes` is used without `RouterModule` or `provideRouter`. Can
|
6870 | // be removed when `provideRoutes` is removed.
|
6871 | NG_DEV_MODE ? { provide: ROUTER_IS_PROVIDED, useValue: true } : [],
|
6872 | ];
|
6873 | function routerNgProbeToken() {
|
6874 | return new NgProbeToken('Router', Router);
|
6875 | }
|
6876 | /**
|
6877 | * @description
|
6878 | *
|
6879 | * Adds directives and providers for in-app navigation among views defined in an application.
|
6880 | * Use the Angular `Router` service to declaratively specify application states and manage state
|
6881 | * transitions.
|
6882 | *
|
6883 | * You can import this NgModule multiple times, once for each lazy-loaded bundle.
|
6884 | * However, only one `Router` service can be active.
|
6885 | * To ensure this, there are two ways to register routes when importing this module:
|
6886 | *
|
6887 | * * The `forRoot()` method creates an `NgModule` that contains all the directives, the given
|
6888 | * routes, and the `Router` service itself.
|
6889 | * * The `forChild()` method creates an `NgModule` that contains all the directives and the given
|
6890 | * routes, but does not include the `Router` service.
|
6891 | *
|
6892 | * @see [Routing and Navigation guide](guide/router) for an
|
6893 | * overview of how the `Router` service should be used.
|
6894 | *
|
6895 | * @publicApi
|
6896 | */
|
6897 | class RouterModule {
|
6898 | constructor(guard) { }
|
6899 | /**
|
6900 | * Creates and configures a module with all the router providers and directives.
|
6901 | * Optionally sets up an application listener to perform an initial navigation.
|
6902 | *
|
6903 | * When registering the NgModule at the root, import as follows:
|
6904 | *
|
6905 | * ```
|
6906 | * @NgModule({
|
6907 | * imports: [RouterModule.forRoot(ROUTES)]
|
6908 | * })
|
6909 | * class MyNgModule {}
|
6910 | * ```
|
6911 | *
|
6912 | * @param routes An array of `Route` objects that define the navigation paths for the application.
|
6913 | * @param config An `ExtraOptions` configuration object that controls how navigation is performed.
|
6914 | * @return The new `NgModule`.
|
6915 | *
|
6916 | */
|
6917 | static forRoot(routes, config) {
|
6918 | return {
|
6919 | ngModule: RouterModule,
|
6920 | providers: [
|
6921 | ROUTER_PROVIDERS,
|
6922 | NG_DEV_MODE ? (config?.enableTracing ? withDebugTracing().ɵproviders : []) : [],
|
6923 | { provide: ROUTES, multi: true, useValue: routes },
|
6924 | {
|
6925 | provide: ROUTER_FORROOT_GUARD,
|
6926 | useFactory: provideForRootGuard,
|
6927 | deps: [[Router, new Optional(), new SkipSelf()]]
|
6928 | },
|
6929 | { provide: ROUTER_CONFIGURATION, useValue: config ? config : {} },
|
6930 | config?.useHash ? provideHashLocationStrategy() : providePathLocationStrategy(),
|
6931 | provideRouterScroller(),
|
6932 | config?.preloadingStrategy ? withPreloading(config.preloadingStrategy).ɵproviders : [],
|
6933 | { provide: NgProbeToken, multi: true, useFactory: routerNgProbeToken },
|
6934 | config?.initialNavigation ? provideInitialNavigation(config) : [],
|
6935 | provideRouterInitializer(),
|
6936 | ],
|
6937 | };
|
6938 | }
|
6939 | /**
|
6940 | * Creates a module with all the router directives and a provider registering routes,
|
6941 | * without creating a new Router service.
|
6942 | * When registering for submodules and lazy-loaded submodules, create the NgModule as follows:
|
6943 | *
|
6944 | * ```
|
6945 | * @NgModule({
|
6946 | * imports: [RouterModule.forChild(ROUTES)]
|
6947 | * })
|
6948 | * class MyNgModule {}
|
6949 | * ```
|
6950 | *
|
6951 | * @param routes An array of `Route` objects that define the navigation paths for the submodule.
|
6952 | * @return The new NgModule.
|
6953 | *
|
6954 | */
|
6955 | static forChild(routes) {
|
6956 | return {
|
6957 | ngModule: RouterModule,
|
6958 | providers: [{ provide: ROUTES, multi: true, useValue: routes }],
|
6959 | };
|
6960 | }
|
6961 | }
|
6962 | RouterModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterModule, deps: [{ token: ROUTER_FORROOT_GUARD, optional: true }], target: i0.ɵɵFactoryTarget.NgModule });
|
6963 | RouterModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.3", ngImport: i0, type: RouterModule, imports: [RouterOutlet, RouterLink, RouterLinkActive, ɵEmptyOutletComponent], exports: [RouterOutlet, RouterLink, RouterLinkActive, ɵEmptyOutletComponent] });
|
6964 | RouterModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterModule, imports: [ɵEmptyOutletComponent] });
|
6965 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterModule, decorators: [{
|
6966 | type: NgModule,
|
6967 | args: [{
|
6968 | imports: ROUTER_DIRECTIVES,
|
6969 | exports: ROUTER_DIRECTIVES,
|
6970 | }]
|
6971 | }], ctorParameters: function () { return [{ type: undefined, decorators: [{
|
6972 | type: Optional
|
6973 | }, {
|
6974 | type: Inject,
|
6975 | args: [ROUTER_FORROOT_GUARD]
|
6976 | }] }]; } });
|
6977 | /**
|
6978 | * For internal use by `RouterModule` only. Note that this differs from `withInMemoryRouterScroller`
|
6979 | * because it reads from the `ExtraOptions` which should not be used in the standalone world.
|
6980 | */
|
6981 | function provideRouterScroller() {
|
6982 | return {
|
6983 | provide: ROUTER_SCROLLER,
|
6984 | useFactory: () => {
|
6985 | const viewportScroller = inject(ViewportScroller);
|
6986 | const zone = inject(NgZone);
|
6987 | const config = inject(ROUTER_CONFIGURATION);
|
6988 | const transitions = inject(NavigationTransitions);
|
6989 | const urlSerializer = inject(UrlSerializer);
|
6990 | if (config.scrollOffset) {
|
6991 | viewportScroller.setOffset(config.scrollOffset);
|
6992 | }
|
6993 | return new RouterScroller(urlSerializer, transitions, viewportScroller, zone, config);
|
6994 | },
|
6995 | };
|
6996 | }
|
6997 | // Note: For internal use only with `RouterModule`. Standalone setup via `provideRouter` should
|
6998 | // provide hash location directly via `{provide: LocationStrategy, useClass: HashLocationStrategy}`.
|
6999 | function provideHashLocationStrategy() {
|
7000 | return { provide: LocationStrategy, useClass: HashLocationStrategy };
|
7001 | }
|
7002 | // Note: For internal use only with `RouterModule`. Standalone setup via `provideRouter` does not
|
7003 | // need this at all because `PathLocationStrategy` is the default factory for `LocationStrategy`.
|
7004 | function providePathLocationStrategy() {
|
7005 | return { provide: LocationStrategy, useClass: PathLocationStrategy };
|
7006 | }
|
7007 | function provideForRootGuard(router) {
|
7008 | if (NG_DEV_MODE && router) {
|
7009 | throw new ɵRuntimeError(4007 /* RuntimeErrorCode.FOR_ROOT_CALLED_TWICE */, `The Router was provided more than once. This can happen if 'forRoot' is used outside of the root injector.` +
|
7010 | ` Lazy loaded modules should use RouterModule.forChild() instead.`);
|
7011 | }
|
7012 | return 'guarded';
|
7013 | }
|
7014 | // Note: For internal use only with `RouterModule`. Standalone router setup with `provideRouter`
|
7015 | // users call `withXInitialNavigation` directly.
|
7016 | function provideInitialNavigation(config) {
|
7017 | return [
|
7018 | config.initialNavigation === 'disabled' ? withDisabledInitialNavigation().ɵproviders : [],
|
7019 | config.initialNavigation === 'enabledBlocking' ?
|
7020 | withEnabledBlockingInitialNavigation().ɵproviders :
|
7021 | [],
|
7022 | ];
|
7023 | }
|
7024 | // TODO(atscott): This should not be in the public API
|
7025 | /**
|
7026 | * A [DI token](guide/glossary/#di-token) for the router initializer that
|
7027 | * is called after the app is bootstrapped.
|
7028 | *
|
7029 | * @publicApi
|
7030 | */
|
7031 | const ROUTER_INITIALIZER = new InjectionToken(NG_DEV_MODE ? 'Router Initializer' : '');
|
7032 | function provideRouterInitializer() {
|
7033 | return [
|
7034 | // ROUTER_INITIALIZER token should be removed. It's public API but shouldn't be. We can just
|
7035 | // have `getBootstrapListener` directly attached to APP_BOOTSTRAP_LISTENER.
|
7036 | { provide: ROUTER_INITIALIZER, useFactory: getBootstrapListener },
|
7037 | { provide: APP_BOOTSTRAP_LISTENER, multi: true, useExisting: ROUTER_INITIALIZER },
|
7038 | ];
|
7039 | }
|
7040 |
|
7041 | /**
|
7042 | * @module
|
7043 | * @description
|
7044 | * Entry point for all public APIs of the router package.
|
7045 | */
|
7046 | /**
|
7047 | * @publicApi
|
7048 | */
|
7049 | const VERSION = new Version('15.2.3');
|
7050 |
|
7051 | /**
|
7052 | * @module
|
7053 | * @description
|
7054 | * Entry point for all public APIs of this package.
|
7055 | */
|
7056 | // This file only reexports content of the `src` folder. Keep it that way.
|
7057 |
|
7058 | // This file is not used to build this module. It is only used during editing
|
7059 |
|
7060 | /**
|
7061 | * Generated bundle index. Do not edit.
|
7062 | */
|
7063 |
|
7064 | export { ActivatedRoute, ActivatedRouteSnapshot, ActivationEnd, ActivationStart, BaseRouteReuseStrategy, ChildActivationEnd, ChildActivationStart, ChildrenOutletContexts, DefaultTitleStrategy, DefaultUrlSerializer, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationSkipped, NavigationStart, NoPreloading, OutletContext, PRIMARY_OUTLET, PreloadAllModules, PreloadingStrategy, ROUTER_CONFIGURATION, ROUTER_INITIALIZER, ROUTES, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteReuseStrategy, Router, RouterEvent, RouterLink, RouterLinkActive, RouterLink as RouterLinkWithHref, RouterModule, RouterOutlet, RouterPreloader, RouterState, RouterStateSnapshot, RoutesRecognized, Scroll, TitleStrategy, UrlHandlingStrategy, UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree, VERSION, convertToParamMap, createUrlTreeFromSnapshot, defaultUrlMatcher, provideRouter, provideRoutes, withDebugTracing, withDisabledInitialNavigation, withEnabledBlockingInitialNavigation, withHashLocation, withInMemoryScrolling, withNavigationErrorHandler, withPreloading, withRouterConfig, ɵEmptyOutletComponent, ROUTER_PROVIDERS as ɵROUTER_PROVIDERS, afterNextNavigation as ɵafterNextNavigation, flatten as ɵflatten, withPreloading as ɵwithPreloading };
|
7065 | //# sourceMappingURL=router.mjs.map
|