UNPKG

297 kBJavaScriptView Raw
1/**
2 * @license Angular v15.2.3
3 * (c) 2010-2022 Google LLC. https://angular.io/
4 * License: MIT
5 */
6
7import * as i0 from '@angular/core';
8import { ɵ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';
9import { from, of, BehaviorSubject, EmptyError, combineLatest, concat, defer, pipe, throwError, Observable, EMPTY, ConnectableObservable, Subject } from 'rxjs';
10import * as i3 from '@angular/common';
11import { Location, ViewportScroller, LOCATION_INITIALIZED, LocationStrategy, HashLocationStrategy, PathLocationStrategy } from '@angular/common';
12import { 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';
13import * as i1 from '@angular/platform-browser';
14
15/**
16 * The primary routing outlet.
17 *
18 * @publicApi
19 */
20const PRIMARY_OUTLET = 'primary';
21/**
22 * A private symbol used to store the value of `Route.title` inside the `Route.data` if it is a
23 * static string or `Route.resolve` if anything else. This allows us to reuse the existing route
24 * data/resolvers to support the title feature without new instrumentation in the `Router` pipeline.
25 */
26const RouteTitleKey = Symbol('RouteTitle');
27class 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 * Converts a `Params` instance to a `ParamMap`.
54 * @param params The instance to convert.
55 * @returns The new map instance.
56 *
57 * @publicApi
58 */
59function convertToParamMap(params) {
60 return new ParamsAsMap(params);
61}
62/**
63 * Matches the route configuration (`route`) against the actual URL (`segments`).
64 *
65 * When no matcher is defined on a `Route`, this is the matcher used by the Router by default.
66 *
67 * @param segments The remaining unmatched segments in the current navigation
68 * @param segmentGroup The current segment group being matched
69 * @param route The `Route` to match against.
70 *
71 * @see UrlMatchResult
72 * @see Route
73 *
74 * @returns The resulting match information or `null` if the `route` should not match.
75 * @publicApi
76 */
77function defaultUrlMatcher(segments, segmentGroup, route) {
78 const parts = route.path.split('/');
79 if (parts.length > segments.length) {
80 // The actual URL is shorter than the config, no match
81 return null;
82 }
83 if (route.pathMatch === 'full' &&
84 (segmentGroup.hasChildren() || parts.length < segments.length)) {
85 // The config is longer than the actual URL but we are looking for a full match, return null
86 return null;
87 }
88 const posParams = {};
89 // Check each config part against the actual URL
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 // The actual URL part does not match the config, no match
99 return null;
100 }
101 }
102 return { consumed: segments.slice(0, parts.length), posParams };
103}
104
105function 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}
114function shallowEqual(a, b) {
115 // While `undefined` should never be possible, it would sometimes be the case in IE 11
116 // and pre-chromium Edge. The check below accounts for this edge case.
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 * Test equality for arrays of strings or a string.
133 */
134function 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 * Flattens single-level nested arrays.
148 */
149function flatten(arr) {
150 return Array.prototype.concat.apply([], arr);
151}
152/**
153 * Return the last element of an array.
154 */
155function last(a) {
156 return a.length > 0 ? a[a.length - 1] : null;
157}
158/**
159 * Verifys all booleans in an array are `true`.
160 */
161function and(bools) {
162 return !bools.some(v => !v);
163}
164function forEach(map, callback) {
165 for (const prop in map) {
166 if (map.hasOwnProperty(prop)) {
167 callback(map[prop], prop);
168 }
169 }
170}
171function wrapIntoObservable(value) {
172 if (ɵisObservable(value)) {
173 return value;
174 }
175 if (ɵisPromise(value)) {
176 // Use `Promise.resolve()` to wrap promise-like instances.
177 // Required ie when a Resolver returns a AngularJS `$q` promise to correctly trigger the
178 // change detection.
179 return from(Promise.resolve(value));
180 }
181 return of(value);
182}
183
184const NG_DEV_MODE$b = typeof ngDevMode === 'undefined' || ngDevMode;
185const pathCompareMap = {
186 'exact': equalSegmentGroups,
187 'subset': containsSegmentGroup,
188};
189const paramCompareMap = {
190 'exact': equalParams,
191 'subset': containsParams,
192 'ignored': () => true,
193};
194function 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}
199function equalParams(container, containee) {
200 // TODO: This does not handle array params correctly.
201 return shallowEqual(container, containee);
202}
203function 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}
219function 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}
223function containsSegmentGroup(container, containee, matrixParams) {
224 return containsSegmentGroupHelper(container, containee, containee.segments, matrixParams);
225}
226function 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}
263function matrixParamsMatch(containerPaths, containeePaths, options) {
264 return containeePaths.every((containeeSegment, i) => {
265 return paramCompareMap[options](containerPaths[i].parameters, containeeSegment.parameters);
266 });
267}
268/**
269 * @description
270 *
271 * Represents the parsed URL.
272 *
273 * Since a router state is a tree, and the URL is nothing but a serialized state, the URL is a
274 * serialized tree.
275 * UrlTree is a data structure that provides a lot of affordances in dealing with URLs
276 *
277 * @usageNotes
278 * ### Example
279 *
280 * ```
281 * @Component({templateUrl:'template.html'})
282 * class MyComponent {
283 * constructor(router: Router) {
284 * const tree: UrlTree =
285 * router.parseUrl('/team/33/(user/victor//support:help)?debug=true#fragment');
286 * const f = tree.fragment; // return 'fragment'
287 * const q = tree.queryParams; // returns {debug: 'true'}
288 * const g: UrlSegmentGroup = tree.root.children[PRIMARY_OUTLET];
289 * const s: UrlSegment[] = g.segments; // returns 2 segments 'team' and '33'
290 * g.children[PRIMARY_OUTLET].segments; // returns 2 segments 'user' and 'victor'
291 * g.children['support'].segments; // return 1 segment 'help'
292 * }
293 * }
294 * ```
295 *
296 * @publicApi
297 */
298class UrlTree {
299 constructor(
300 /** The root segment group of the URL tree */
301 root = new UrlSegmentGroup([], {}),
302 /** The query params of the URL */
303 queryParams = {},
304 /** The fragment of the URL */
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 /* RuntimeErrorCode.INVALID_ROOT_URL_SEGMENT */, '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 /** @docsNotRequired */
323 toString() {
324 return DEFAULT_SERIALIZER.serialize(this);
325 }
326}
327/**
328 * @description
329 *
330 * Represents the parsed URL segment group.
331 *
332 * See `UrlTree` for more information.
333 *
334 * @publicApi
335 */
336class 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 /** The parent node in the url tree */
345 this.parent = null;
346 forEach(children, (v, k) => v.parent = this);
347 }
348 /** Whether the segment has child segments */
349 hasChildren() {
350 return this.numberOfChildren > 0;
351 }
352 /** Number of child segments */
353 get numberOfChildren() {
354 return Object.keys(this.children).length;
355 }
356 /** @docsNotRequired */
357 toString() {
358 return serializePaths(this);
359 }
360}
361/**
362 * @description
363 *
364 * Represents a single URL segment.
365 *
366 * A UrlSegment is a part of a URL between the two slashes. It contains a path and the matrix
367 * parameters associated with the segment.
368 *
369 * @usageNotes
370 * ### Example
371 *
372 * ```
373 * @Component({templateUrl:'template.html'})
374 * class MyComponent {
375 * constructor(router: Router) {
376 * const tree: UrlTree = router.parseUrl('/team;id=33');
377 * const g: UrlSegmentGroup = tree.root.children[PRIMARY_OUTLET];
378 * const s: UrlSegment[] = g.segments;
379 * s[0].path; // returns 'team'
380 * s[0].parameters; // returns {id: 33}
381 * }
382 * }
383 * ```
384 *
385 * @publicApi
386 */
387class 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 /** @docsNotRequired */
403 toString() {
404 return serializePath(this);
405 }
406}
407function equalSegments(as, bs) {
408 return equalPath(as, bs) && as.every((a, i) => shallowEqual(a.parameters, bs[i].parameters));
409}
410function equalPath(as, bs) {
411 if (as.length !== bs.length)
412 return false;
413 return as.every((a, i) => a.path === bs[i].path);
414}
415function 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 * @description
431 *
432 * Serializes and deserializes a URL string into a URL tree.
433 *
434 * The url serialization strategy is customizable. You can
435 * make all URLs case insensitive by providing a custom UrlSerializer.
436 *
437 * See `DefaultUrlSerializer` for an example of a URL serializer.
438 *
439 * @publicApi
440 */
441class UrlSerializer {
442}
443UrlSerializer.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: UrlSerializer, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
444UrlSerializer.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: UrlSerializer, providedIn: 'root', useFactory: () => new DefaultUrlSerializer() });
445i0.ɵɵ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 * @description
451 *
452 * A default implementation of the `UrlSerializer`.
453 *
454 * Example URLs:
455 *
456 * ```
457 * /inbox/33(popup:compose)
458 * /inbox/33;open=true/messages/44
459 * ```
460 *
461 * DefaultUrlSerializer uses parentheses to serialize secondary segments (e.g., popup:compose), the
462 * colon syntax to specify the outlet, and the ';parameter=value' syntax (e.g., open=true) to
463 * specify route specific parameters.
464 *
465 * @publicApi
466 */
467class DefaultUrlSerializer {
468 /** Parses a url into a `UrlTree` */
469 parse(url) {
470 const p = new UrlParser(url);
471 return new UrlTree(p.parseRootSegment(), p.parseQueryParams(), p.parseFragment());
472 }
473 /** Converts a `UrlTree` into a url */
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}
481const DEFAULT_SERIALIZER = new DefaultUrlSerializer();
482function serializePaths(segment) {
483 return segment.segments.map(p => serializePath(p)).join('/');
484}
485function 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 // use no parenthesis if the only child is a primary outlet route
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 * Encodes a URI string with the default encoding. This function will only ever be called from
517 * `encodeUriQuery` or `encodeUriSegment` as it's the base set of encodings to be used. We need
518 * a custom encoding because encodeURIComponent is too aggressive and encodes stuff that doesn't
519 * have to be encoded per https://url.spec.whatwg.org.
520 */
521function 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 * This function should be used to encode both keys and values in a query string key/value. In
530 * the following URL, you need to call encodeUriQuery on "k" and "v":
531 *
532 * http://www.site.org/html;mk=mv?k=v#f
533 */
534function encodeUriQuery(s) {
535 return encodeUriString(s).replace(/%3B/gi, ';');
536}
537/**
538 * This function should be used to encode a URL fragment. In the following URL, you need to call
539 * encodeUriFragment on "f":
540 *
541 * http://www.site.org/html;mk=mv?k=v#f
542 */
543function encodeUriFragment(s) {
544 return encodeURI(s);
545}
546/**
547 * This function should be run on any URI segment as well as the key and value in a key/value
548 * pair for matrix params. In the following URL, you need to call encodeUriSegment on "html",
549 * "mk", and "mv":
550 *
551 * http://www.site.org/html;mk=mv?k=v#f
552 */
553function encodeUriSegment(s) {
554 return encodeUriString(s).replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/%26/gi, '&');
555}
556function decode(s) {
557 return decodeURIComponent(s);
558}
559// Query keys/values should have the "+" replaced first, as "+" in a query string is " ".
560// decodeURIComponent function will not decode "+" as a space.
561function decodeQuery(s) {
562 return decode(s.replace(/\+/g, '%20'));
563}
564function serializePath(path) {
565 return `${encodeUriSegment(path.path)}${serializeMatrixParams(path.parameters)}`;
566}
567function serializeMatrixParams(params) {
568 return Object.keys(params)
569 .map(key => `;${encodeUriSegment(key)}=${encodeUriSegment(params[key])}`)
570 .join('');
571}
572function 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}
583const SEGMENT_RE = /^[^\/()?;=#]+/;
584function matchSegments(str) {
585 const match = str.match(SEGMENT_RE);
586 return match ? match[0] : '';
587}
588const QUERY_PARAM_RE = /^[^=?&#]+/;
589// Return the name of the query param at the start of the string or an empty string
590function matchQueryParams(str) {
591 const match = str.match(QUERY_PARAM_RE);
592 return match ? match[0] : '';
593}
594const QUERY_PARAM_VALUE_RE = /^[^&#]+/;
595// Return the value of the query param at the start of the string or an empty string
596function matchUrlQueryParamValue(str) {
597 const match = str.match(QUERY_PARAM_VALUE_RE);
598 return match ? match[0] : '';
599}
600class 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 // The root segment group never has segments
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 // parse a segment with its matrix parameters
653 // ie `name;k1=v1;k2`
654 parseSegment() {
655 const path = matchSegments(this.remaining);
656 if (path === '' && this.peekStartsWith(';')) {
657 throw new ɵRuntimeError(4009 /* RuntimeErrorCode.EMPTY_PATH_WITH_PARAMS */, 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 // Parse a single query parameter `name[=value]`
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 // Append to existing values
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 // Create a new value
713 params[decodedKey] = decodedVal;
714 }
715 }
716 // parse `(a/b//outlet_name:c/d)`
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 // if is is not one of these characters, then the segment was unescaped
724 // or the group was not closed
725 if (next !== '/' && next !== ')' && next !== ';') {
726 throw new ɵRuntimeError(4010 /* RuntimeErrorCode.UNPARSABLE_URL */, 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 // Consumes the prefix when it is present and returns whether it has been consumed
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 /* RuntimeErrorCode.UNEXPECTED_VALUE_IN_URL */, NG_DEV_MODE$b && `Expected "${str}".`);
758 }
759 }
760}
761function createRoot(rootCandidate) {
762 return rootCandidate.segments.length > 0 ?
763 new UrlSegmentGroup([], { [PRIMARY_OUTLET]: rootCandidate }) :
764 rootCandidate;
765}
766/**
767 * Recursively merges primary segment children into their parents and also drops empty children
768 * (those which have no segments and no children themselves). The latter prevents serializing a
769 * group into something like `/a(aux:)`, where `aux` is an empty child segment.
770 */
771function 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 // don't add empty children
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 * When possible, merges the primary outlet child into the parent `UrlSegmentGroup`.
786 *
787 * When a segment group has only one child which is a primary outlet, merges that child into the
788 * parent. That is, the child segment group's segments are merged into the `s` and the child's
789 * children become the children of `s`. Think of this like a 'squash', merging the child segment
790 * group into the parent.
791 */
792function 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}
799function isUrlTree(v) {
800 return v instanceof UrlTree;
801}
802
803const NG_DEV_MODE$a = typeof ngDevMode === 'undefined' || ngDevMode;
804/**
805 * Creates a `UrlTree` relative to an `ActivatedRouteSnapshot`.
806 *
807 * @publicApi
808 *
809 *
810 * @param relativeTo The `ActivatedRouteSnapshot` to apply the commands to
811 * @param commands An array of URL fragments with which to construct the new URL tree.
812 * If the path is static, can be the literal URL string. For a dynamic path, pass an array of path
813 * segments, followed by the parameters for each segment.
814 * The fragments are applied to the one provided in the `relativeTo` parameter.
815 * @param queryParams The query parameters for the `UrlTree`. `null` if the `UrlTree` does not have
816 * any query parameters.
817 * @param fragment The fragment for the `UrlTree`. `null` if the `UrlTree` does not have a fragment.
818 *
819 * @usageNotes
820 *
821 * ```
822 * // create /team/33/user/11
823 * createUrlTreeFromSnapshot(snapshot, ['/team', 33, 'user', 11]);
824 *
825 * // create /team/33;expand=true/user/11
826 * createUrlTreeFromSnapshot(snapshot, ['/team', 33, {expand: true}, 'user', 11]);
827 *
828 * // you can collapse static segments like this (this works only with the first passed-in value):
829 * createUrlTreeFromSnapshot(snapshot, ['/team/33/user', userId]);
830 *
831 * // If the first segment can contain slashes, and you do not want the router to split it,
832 * // you can do the following:
833 * createUrlTreeFromSnapshot(snapshot, [{segmentPath: '/one/two'}]);
834 *
835 * // create /team/33/(user/11//right:chat)
836 * createUrlTreeFromSnapshot(snapshot, ['/team', 33, {outlets: {primary: 'user/11', right:
837 * 'chat'}}], null, null);
838 *
839 * // remove the right secondary node
840 * createUrlTreeFromSnapshot(snapshot, ['/team', 33, {outlets: {primary: 'user/11', right: null}}]);
841 *
842 * // For the examples below, assume the current URL is for the `/team/33/user/11` and the
843 * `ActivatedRouteSnapshot` points to `user/11`:
844 *
845 * // navigate to /team/33/user/11/details
846 * createUrlTreeFromSnapshot(snapshot, ['details']);
847 *
848 * // navigate to /team/33/user/22
849 * createUrlTreeFromSnapshot(snapshot, ['../22']);
850 *
851 * // navigate to /team/44/user/22
852 * createUrlTreeFromSnapshot(snapshot, ['../../team/44/user/22']);
853 * ```
854 */
855function createUrlTreeFromSnapshot(relativeTo, commands, queryParams = null, fragment = null) {
856 const relativeToUrlSegmentGroup = createSegmentGroupFromRoute(relativeTo);
857 return createUrlTreeFromSegmentGroup(relativeToUrlSegmentGroup, commands, queryParams, fragment);
858}
859function 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}
877function createUrlTreeFromSegmentGroup(relativeTo, commands, queryParams, fragment) {
878 let root = relativeTo;
879 while (root.parent) {
880 root = root.parent;
881 }
882 // There are no commands so the `UrlTree` goes to the same path as the one created from the
883 // `UrlSegmentGroup`. All we need to do is update the `queryParams` and `fragment` without
884 // applying any other logic.
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}
898function 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 // Note: The types should disallow `snapshot` from being `undefined` but due to test mocks, this
914 // may be the case. Since we try to access it at an earlier point before the refactor to add the
915 // warning for `relativeLinkResolution: 'legacy'`, this may cause failures in tests where it
916 // didn't before.
917 const result = createTreeUsingPathIndex(route.snapshot?._lastPathIndex);
918 return result;
919}
920function isMatrixParams(command) {
921 return typeof command === 'object' && command != null && !command.outlets && !command.segmentPath;
922}
923/**
924 * Determines if a given command has an `outlets` map. When we encounter a command
925 * with an outlets k/v map, we need to apply each outlet individually to the existing segment.
926 */
927function isCommandWithOutlets(command) {
928 return typeof command === 'object' && command != null && command.outlets;
929}
930function 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 * Replaces the `oldSegment` which is located in some child of the `current` with the `newSegment`.
949 * This also has the effect of creating new `UrlSegmentGroup` copies to update references. This
950 * shouldn't be necessary but the fallback logic for an invalid ActivatedRoute in the creation uses
951 * the Router's current url tree. If we don't create new segment groups, we end up modifying that
952 * value.
953 */
954function 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}
966class 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 /* RuntimeErrorCode.ROOT_SEGMENT_MATRIX_PARAMS */, 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 /* RuntimeErrorCode.MISPLACED_OUTLETS_COMMAND */, 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/** Transforms commands to a normalized `Navigation` */
984function 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 // skip './a'
1010 }
1011 else if (partIndex == 0 && urlPart === '') { // '/a'
1012 isAbsolute = true;
1013 }
1014 else if (urlPart === '..') { // '../a'
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}
1027class Position {
1028 constructor(segmentGroup, processChildren, index) {
1029 this.segmentGroup = segmentGroup;
1030 this.processChildren = processChildren;
1031 this.index = index;
1032 }
1033}
1034function findStartingPositionForTargetGroup(nav, root, target) {
1035 if (nav.isAbsolute) {
1036 return new Position(root, true, 0);
1037 }
1038 if (!target) {
1039 // `NaN` is used only to maintain backwards compatibility with incorrectly mocked
1040 // `ActivatedRouteSnapshot` in tests. In prior versions of this code, the position here was
1041 // determined based on an internal property that was rarely mocked, resulting in `NaN`. In
1042 // reality, this code path should _never_ be touched since `target` is not allowed to be falsey.
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}
1052function findStartingPosition(nav, tree, segmentGroup, lastPathIndex) {
1053 if (nav.isAbsolute) {
1054 return new Position(tree.root, true, 0);
1055 }
1056 if (lastPathIndex === -1) {
1057 // Pathless ActivatedRoute has _lastPathIndex === -1 but should not process children
1058 // see issue #26224, #13011, #35687
1059 // However, if the ActivatedRoute is the root we should process children like above.
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}
1067function 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 /* RuntimeErrorCode.INVALID_DOUBLE_DOTS */, NG_DEV_MODE$a && 'Invalid number of \'../\'');
1076 }
1077 ci = g.segments.length;
1078 }
1079 return new Position(g, false, ci - dd);
1080}
1081function getOutlets(commands) {
1082 if (isCommandWithOutlets(commands[0])) {
1083 return commands[0].outlets;
1084 }
1085 return { [PRIMARY_OUTLET]: commands };
1086}
1087function 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}
1115function 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 // If the set of commands does not apply anything to the primary outlet and the child segment is
1123 // an empty path primary segment on its own, we want to skip applying the commands at this
1124 // level. Imagine the following config:
1125 //
1126 // {path: '', children: [{path: '**', outlet: 'popup'}]}.
1127 //
1128 // Navigation to /(popup:a) will activate the child outlet correctly Given a follow-up
1129 // navigation with commands
1130 // ['/', {outlets: {'popup': 'b'}}], we _would not_ want to apply the outlet commands to the
1131 // root segment because that would result in
1132 // //(popup:a)(popup:b) since the outlet command got applied one level above where it appears in
1133 // the `ActivatedRoute` rather than updating the existing one.
1134 //
1135 // Because empty paths do not appear in the URL segments and the fact that the segments used in
1136 // the output `UrlTree` are squashed to eliminate these empty paths where possible
1137 // https://github.com/angular/angular/blob/13f10de40e25c6900ca55bd83b36bd533dacfa9e/packages/router/src/url_tree.ts#L755
1138 // it can be hard to determine what is the right thing to do when applying commands to a
1139 // `UrlSegmentGroup` that is created from an "unsquashed"/expanded `ActivatedRoute` tree.
1140 // This code effectively "squashes" empty path primary routes when they have no siblings on
1141 // the same level of the tree.
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}
1163function 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 // Do not try to consume command as part of the prefixing if it has outlets because it can
1173 // contain outlets other than the one being processed. Consuming the outlets command would
1174 // result in other outlets being ignored.
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}
1196function 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 // if we start with an object literal, we need to reuse the path part from the segment
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}
1225function 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}
1237function stringify(params) {
1238 const res = {};
1239 forEach(params, (v, k) => res[k] = `${v}`);
1240 return res;
1241}
1242function compare(path, params, segment) {
1243 return path == segment.path && shallowEqual(params, segment.parameters);
1244}
1245
1246const IMPERATIVE_NAVIGATION = 'imperative';
1247/**
1248 * Base for events the router goes through, as opposed to events tied to a specific
1249 * route. Fired one time for any given navigation.
1250 *
1251 * The following code shows how a class subscribes to router events.
1252 *
1253 * ```ts
1254 * import {Event, RouterEvent, Router} from '@angular/router';
1255 *
1256 * class MyService {
1257 * constructor(public router: Router) {
1258 * router.events.pipe(
1259 * filter((e: Event): e is RouterEvent => e instanceof RouterEvent)
1260 * ).subscribe((e: RouterEvent) => {
1261 * // Do something
1262 * });
1263 * }
1264 * }
1265 * ```
1266 *
1267 * @see `Event`
1268 * @see [Router events summary](guide/router-reference#router-events)
1269 * @publicApi
1270 */
1271class 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 * An event triggered when a navigation starts.
1283 *
1284 * @publicApi
1285 */
1286class 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 /* EventType.NavigationStart */;
1298 this.navigationTrigger = navigationTrigger;
1299 this.restoredState = restoredState;
1300 }
1301 /** @docsNotRequired */
1302 toString() {
1303 return `NavigationStart(id: ${this.id}, url: '${this.url}')`;
1304 }
1305}
1306/**
1307 * An event triggered when a navigation ends successfully.
1308 *
1309 * @see `NavigationStart`
1310 * @see `NavigationCancel`
1311 * @see `NavigationError`
1312 *
1313 * @publicApi
1314 */
1315class 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 /* EventType.NavigationEnd */;
1326 }
1327 /** @docsNotRequired */
1328 toString() {
1329 return `NavigationEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}')`;
1330 }
1331}
1332/**
1333 * An event triggered when a navigation is canceled, directly or indirectly.
1334 * This can happen for several reasons including when a route guard
1335 * returns `false` or initiates a redirect by returning a `UrlTree`.
1336 *
1337 * @see `NavigationStart`
1338 * @see `NavigationEnd`
1339 * @see `NavigationError`
1340 *
1341 * @publicApi
1342 */
1343class 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 /* EventType.NavigationCancel */;
1364 }
1365 /** @docsNotRequired */
1366 toString() {
1367 return `NavigationCancel(id: ${this.id}, url: '${this.url}')`;
1368 }
1369}
1370/**
1371 * An event triggered when a navigation is skipped.
1372 * This can happen for a couple reasons including onSameUrlHandling
1373 * is set to `ignore` and the navigation URL is not different than the
1374 * current state.
1375 *
1376 * @publicApi
1377 */
1378class 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 /* EventType.NavigationSkipped */;
1399 }
1400}
1401/**
1402 * An event triggered when a navigation fails due to an unexpected error.
1403 *
1404 * @see `NavigationStart`
1405 * @see `NavigationEnd`
1406 * @see `NavigationCancel`
1407 *
1408 * @publicApi
1409 */
1410class 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 /* EventType.NavigationError */;
1429 }
1430 /** @docsNotRequired */
1431 toString() {
1432 return `NavigationError(id: ${this.id}, url: '${this.url}', error: ${this.error})`;
1433 }
1434}
1435/**
1436 * An event triggered when routes are recognized.
1437 *
1438 * @publicApi
1439 */
1440class 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 /* EventType.RoutesRecognized */;
1454 }
1455 /** @docsNotRequired */
1456 toString() {
1457 return `RoutesRecognized(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
1458 }
1459}
1460/**
1461 * An event triggered at the start of the Guard phase of routing.
1462 *
1463 * @see `GuardsCheckEnd`
1464 *
1465 * @publicApi
1466 */
1467class 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 /* EventType.GuardsCheckStart */;
1481 }
1482 toString() {
1483 return `GuardsCheckStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
1484 }
1485}
1486/**
1487 * An event triggered at the end of the Guard phase of routing.
1488 *
1489 * @see `GuardsCheckStart`
1490 *
1491 * @publicApi
1492 */
1493class 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 /* EventType.GuardsCheckEnd */;
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 * An event triggered at the start of the Resolve phase of routing.
1517 *
1518 * Runs in the "resolve" phase whether or not there is anything to resolve.
1519 * In future, may change to only run when there are things to be resolved.
1520 *
1521 * @see `ResolveEnd`
1522 *
1523 * @publicApi
1524 */
1525class 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 /* EventType.ResolveStart */;
1539 }
1540 toString() {
1541 return `ResolveStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
1542 }
1543}
1544/**
1545 * An event triggered at the end of the Resolve phase of routing.
1546 * @see `ResolveStart`.
1547 *
1548 * @publicApi
1549 */
1550class 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 /* EventType.ResolveEnd */;
1564 }
1565 toString() {
1566 return `ResolveEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
1567 }
1568}
1569/**
1570 * An event triggered before lazy loading a route configuration.
1571 *
1572 * @see `RouteConfigLoadEnd`
1573 *
1574 * @publicApi
1575 */
1576class RouteConfigLoadStart {
1577 constructor(
1578 /** @docsNotRequired */
1579 route) {
1580 this.route = route;
1581 this.type = 9 /* EventType.RouteConfigLoadStart */;
1582 }
1583 toString() {
1584 return `RouteConfigLoadStart(path: ${this.route.path})`;
1585 }
1586}
1587/**
1588 * An event triggered when a route has been lazy loaded.
1589 *
1590 * @see `RouteConfigLoadStart`
1591 *
1592 * @publicApi
1593 */
1594class RouteConfigLoadEnd {
1595 constructor(
1596 /** @docsNotRequired */
1597 route) {
1598 this.route = route;
1599 this.type = 10 /* EventType.RouteConfigLoadEnd */;
1600 }
1601 toString() {
1602 return `RouteConfigLoadEnd(path: ${this.route.path})`;
1603 }
1604}
1605/**
1606 * An event triggered at the start of the child-activation
1607 * part of the Resolve phase of routing.
1608 * @see `ChildActivationEnd`
1609 * @see `ResolveStart`
1610 *
1611 * @publicApi
1612 */
1613class ChildActivationStart {
1614 constructor(
1615 /** @docsNotRequired */
1616 snapshot) {
1617 this.snapshot = snapshot;
1618 this.type = 11 /* EventType.ChildActivationStart */;
1619 }
1620 toString() {
1621 const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
1622 return `ChildActivationStart(path: '${path}')`;
1623 }
1624}
1625/**
1626 * An event triggered at the end of the child-activation part
1627 * of the Resolve phase of routing.
1628 * @see `ChildActivationStart`
1629 * @see `ResolveStart`
1630 * @publicApi
1631 */
1632class ChildActivationEnd {
1633 constructor(
1634 /** @docsNotRequired */
1635 snapshot) {
1636 this.snapshot = snapshot;
1637 this.type = 12 /* EventType.ChildActivationEnd */;
1638 }
1639 toString() {
1640 const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
1641 return `ChildActivationEnd(path: '${path}')`;
1642 }
1643}
1644/**
1645 * An event triggered at the start of the activation part
1646 * of the Resolve phase of routing.
1647 * @see `ActivationEnd`
1648 * @see `ResolveStart`
1649 *
1650 * @publicApi
1651 */
1652class ActivationStart {
1653 constructor(
1654 /** @docsNotRequired */
1655 snapshot) {
1656 this.snapshot = snapshot;
1657 this.type = 13 /* EventType.ActivationStart */;
1658 }
1659 toString() {
1660 const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
1661 return `ActivationStart(path: '${path}')`;
1662 }
1663}
1664/**
1665 * An event triggered at the end of the activation part
1666 * of the Resolve phase of routing.
1667 * @see `ActivationStart`
1668 * @see `ResolveStart`
1669 *
1670 * @publicApi
1671 */
1672class ActivationEnd {
1673 constructor(
1674 /** @docsNotRequired */
1675 snapshot) {
1676 this.snapshot = snapshot;
1677 this.type = 14 /* EventType.ActivationEnd */;
1678 }
1679 toString() {
1680 const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
1681 return `ActivationEnd(path: '${path}')`;
1682 }
1683}
1684/**
1685 * An event triggered by scrolling.
1686 *
1687 * @publicApi
1688 */
1689class 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 /* EventType.Scroll */;
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}
1707function stringifyEvent(routerEvent) {
1708 if (!('type' in routerEvent)) {
1709 return `Unknown Router Event: ${routerEvent.constructor.name}`;
1710 }
1711 switch (routerEvent.type) {
1712 case 14 /* EventType.ActivationEnd */:
1713 return `ActivationEnd(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
1714 case 13 /* EventType.ActivationStart */:
1715 return `ActivationStart(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
1716 case 12 /* EventType.ChildActivationEnd */:
1717 return `ChildActivationEnd(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
1718 case 11 /* EventType.ChildActivationStart */:
1719 return `ChildActivationStart(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
1720 case 8 /* EventType.GuardsCheckEnd */:
1721 return `GuardsCheckEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state}, shouldActivate: ${routerEvent.shouldActivate})`;
1722 case 7 /* EventType.GuardsCheckStart */:
1723 return `GuardsCheckStart(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
1724 case 2 /* EventType.NavigationCancel */:
1725 return `NavigationCancel(id: ${routerEvent.id}, url: '${routerEvent.url}')`;
1726 case 16 /* EventType.NavigationSkipped */:
1727 return `NavigationSkipped(id: ${routerEvent.id}, url: '${routerEvent.url}')`;
1728 case 1 /* EventType.NavigationEnd */:
1729 return `NavigationEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}')`;
1730 case 3 /* EventType.NavigationError */:
1731 return `NavigationError(id: ${routerEvent.id}, url: '${routerEvent.url}', error: ${routerEvent.error})`;
1732 case 0 /* EventType.NavigationStart */:
1733 return `NavigationStart(id: ${routerEvent.id}, url: '${routerEvent.url}')`;
1734 case 6 /* EventType.ResolveEnd */:
1735 return `ResolveEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
1736 case 5 /* EventType.ResolveStart */:
1737 return `ResolveStart(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
1738 case 10 /* EventType.RouteConfigLoadEnd */:
1739 return `RouteConfigLoadEnd(path: ${routerEvent.route.path})`;
1740 case 9 /* EventType.RouteConfigLoadStart */:
1741 return `RouteConfigLoadStart(path: ${routerEvent.route.path})`;
1742 case 4 /* EventType.RoutesRecognized */:
1743 return `RoutesRecognized(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
1744 case 15 /* EventType.Scroll */:
1745 const pos = routerEvent.position ? `${routerEvent.position[0]}, ${routerEvent.position[1]}` : null;
1746 return `Scroll(anchor: '${routerEvent.anchor}', position: '${pos}')`;
1747 }
1748}
1749
1750const NG_DEV_MODE$9 = typeof ngDevMode === 'undefined' || ngDevMode;
1751class 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}
1768LegacyCreateUrlTree.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: LegacyCreateUrlTree, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1769LegacyCreateUrlTree.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: LegacyCreateUrlTree });
1770i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: LegacyCreateUrlTree, decorators: [{
1771 type: Injectable
1772 }] });
1773class 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 // This is strictly for backwards compatibility with tests that create
1782 // invalid `ActivatedRoute` mocks.
1783 // Note: the difference between having this fallback for invalid `ActivatedRoute` setups and
1784 // just throwing is ~500 test failures. Fixing all of those tests by hand is not feasible at
1785 // the moment.
1786 if (typeof commands[0] !== 'string' || !commands[0].startsWith('/')) {
1787 // Navigations that were absolute in the old way of creating UrlTrees
1788 // would still work because they wouldn't attempt to match the
1789 // segments in the `ActivatedRoute` to the `currentUrlTree` but
1790 // instead just replace the root segment with the navigation result.
1791 // Non-absolute navigations would fail to apply the commands because
1792 // the logic could not find the segment to replace (so they'd act like there were no
1793 // commands).
1794 commands = [];
1795 }
1796 relativeToUrlSegmentGroup = currentUrlTree.root;
1797 }
1798 return createUrlTreeFromSegmentGroup(relativeToUrlSegmentGroup, commands, queryParams, fragment);
1799 }
1800}
1801CreateUrlTreeUsingSnapshot.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: CreateUrlTreeUsingSnapshot, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1802CreateUrlTreeUsingSnapshot.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: CreateUrlTreeUsingSnapshot });
1803i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: CreateUrlTreeUsingSnapshot, decorators: [{
1804 type: Injectable
1805 }] });
1806class CreateUrlTreeStrategy {
1807}
1808CreateUrlTreeStrategy.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: CreateUrlTreeStrategy, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1809CreateUrlTreeStrategy.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: CreateUrlTreeStrategy, providedIn: 'root', useClass: LegacyCreateUrlTree });
1810i0.ɵɵ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
1815class Tree {
1816 constructor(root) {
1817 this._root = root;
1818 }
1819 get root() {
1820 return this._root.value;
1821 }
1822 /**
1823 * @internal
1824 */
1825 parent(t) {
1826 const p = this.pathFromRoot(t);
1827 return p.length > 1 ? p[p.length - 2] : null;
1828 }
1829 /**
1830 * @internal
1831 */
1832 children(t) {
1833 const n = findNode(t, this._root);
1834 return n ? n.children.map(t => t.value) : [];
1835 }
1836 /**
1837 * @internal
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 * @internal
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 * @internal
1855 */
1856 pathFromRoot(t) {
1857 return findPath(t, this._root).map(s => s.value);
1858 }
1859}
1860// DFS for the node matching the value
1861function 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// Return the path to the node with the given value using DFS
1872function 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}
1884class TreeNode {
1885 constructor(value, children) {
1886 this.value = value;
1887 this.children = children;
1888 }
1889 toString() {
1890 return `TreeNode(${this.value})`;
1891 }
1892}
1893// Return the list of T indexed by outlet name
1894function 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 * Represents the state of the router as a tree of activated routes.
1904 *
1905 * @usageNotes
1906 *
1907 * Every node in the route tree is an `ActivatedRoute` instance
1908 * that knows about the "consumed" URL segments, the extracted parameters,
1909 * and the resolved data.
1910 * Use the `ActivatedRoute` properties to traverse the tree from any node.
1911 *
1912 * The following fragment shows how a component gets the root node
1913 * of the current state to establish its own route tree:
1914 *
1915 * ```
1916 * @Component({templateUrl:'template.html'})
1917 * class MyComponent {
1918 * constructor(router: Router) {
1919 * const state: RouterState = router.routerState;
1920 * const root: ActivatedRoute = state.root;
1921 * const child = root.firstChild;
1922 * const id: Observable<string> = child.params.map(p => p.id);
1923 * //...
1924 * }
1925 * }
1926 * ```
1927 *
1928 * @see `ActivatedRoute`
1929 * @see [Getting route information](guide/router#getting-route-information)
1930 *
1931 * @publicApi
1932 */
1933class RouterState extends Tree {
1934 /** @internal */
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}
1946function 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}
1957function 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 * Provides access to information about a route associated with a component
1967 * that is loaded in an outlet.
1968 * Use to traverse the `RouterState` tree and extract information from nodes.
1969 *
1970 * The following example shows how to construct a component using information from a
1971 * currently activated route.
1972 *
1973 * Note: the observables in this class only emit when the current and previous values differ based
1974 * on shallow equality. For example, changing deeply nested properties in resolved `data` will not
1975 * cause the `ActivatedRoute.data` `Observable` to emit a new value.
1976 *
1977 * {@example router/activated-route/module.ts region="activated-route"
1978 * header="activated-route.component.ts"}
1979 *
1980 * @see [Getting route information](guide/router#getting-route-information)
1981 *
1982 * @publicApi
1983 */
1984class ActivatedRoute {
1985 /** @internal */
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 /** An Observable of the resolved route title */
2009 this.title = this.data?.pipe(map((d) => d[RouteTitleKey])) ?? of(undefined);
2010 this._futureSnapshot = futureSnapshot;
2011 }
2012 /** The configuration used to match this route. */
2013 get routeConfig() {
2014 return this._futureSnapshot.routeConfig;
2015 }
2016 /** The root of the router state. */
2017 get root() {
2018 return this._routerState.root;
2019 }
2020 /** The parent of this route in the router state tree. */
2021 get parent() {
2022 return this._routerState.parent(this);
2023 }
2024 /** The first child of this route in the router state tree. */
2025 get firstChild() {
2026 return this._routerState.firstChild(this);
2027 }
2028 /** The children of this route in the router state tree. */
2029 get children() {
2030 return this._routerState.children(this);
2031 }
2032 /** The path from the root of the router state tree to this route. */
2033 get pathFromRoot() {
2034 return this._routerState.pathFromRoot(this);
2035 }
2036 /**
2037 * An Observable that contains a map of the required and optional parameters
2038 * specific to the route.
2039 * The map supports retrieving single and multiple values from the same parameter.
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 * An Observable that contains a map of the query parameters available to all routes.
2049 * The map supports retrieving single and multiple values from the query parameter.
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 * Returns the inherited params, data, and resolve for a given route.
2064 * By default, this only inherits values up to the nearest path-less or component-less route.
2065 * @internal
2066 */
2067function 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 // current route is an empty path => inherits its parent's params and data
2076 if (current.routeConfig && current.routeConfig.path === '') {
2077 inheritingStartingFrom--;
2078 // parent is componentless => current route should inherit its params and data
2079 }
2080 else if (!parent.component) {
2081 inheritingStartingFrom--;
2082 }
2083 else {
2084 break;
2085 }
2086 }
2087 }
2088 return flattenInherited(pathFromRoot.slice(inheritingStartingFrom));
2089}
2090/** @internal */
2091function 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 * @description
2101 *
2102 * Contains the information about a route associated with a component loaded in an
2103 * outlet at a particular moment in time. ActivatedRouteSnapshot can also be used to
2104 * traverse the router state tree.
2105 *
2106 * The following example initializes a component with route information extracted
2107 * from the snapshot of the root node at the time of creation.
2108 *
2109 * ```
2110 * @Component({templateUrl:'./my-component.html'})
2111 * class MyComponent {
2112 * constructor(route: ActivatedRoute) {
2113 * const id: string = route.snapshot.params.id;
2114 * const url: string = route.snapshot.url.join('');
2115 * const user = route.snapshot.data.user;
2116 * }
2117 * }
2118 * ```
2119 *
2120 * @publicApi
2121 */
2122class ActivatedRouteSnapshot {
2123 /** The resolved route title */
2124 get title() {
2125 // Note: This _must_ be a getter because the data is mutated in the resolvers. Title will not be
2126 // available at the time of class instantiation.
2127 return this.data?.[RouteTitleKey];
2128 }
2129 /** @internal */
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 */
2240class 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}
2253function setRouterState(state, node) {
2254 node.value._routerState = state;
2255 node.children.forEach(c => setRouterState(state, c));
2256}
2257function 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 */
2266function 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}
2293function 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
2300function createRouterState(routeReuseStrategy, curr, prevState) {
2301 const root = createNode(routeReuseStrategy, curr._root, prevState ? prevState._root : undefined);
2302 return new RouterState(root, curr);
2303}
2304function 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}
2328function 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}
2338function 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
2342const NAVIGATION_CANCELING_ERROR = 'ngNavigationCancelingError';
2343function 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}
2350function 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}
2359function isRedirectingNavigationCancelingError$1(error) {
2360 return isNavigationCancelingError$1(error) && isUrlTree(error.url);
2361}
2362function isNavigationCancelingError$1(error) {
2363 return error && error[NAVIGATION_CANCELING_ERROR];
2364}
2365
2366/**
2367 * Store contextual information about a `RouterOutlet`
2368 *
2369 * @publicApi
2370 */
2371class 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 */
2390class 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}
2437ChildrenOutletContexts.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: ChildrenOutletContexts, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2438ChildrenOutletContexts.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: ChildrenOutletContexts, providedIn: 'root' });
2439i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: ChildrenOutletContexts, decorators: [{
2440 type: Injectable,
2441 args: [{ providedIn: 'root' }]
2442 }] });
2443
2444const 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 */
2496class 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}
2652RouterOutlet.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterOutlet, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2653RouterOutlet.ɵ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 });
2654i0.ɵɵ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 }] } });
2676class 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}
2692function 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 */
2705class ɵ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"] }] });
2709i0.ɵɵ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 */
2726function 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}
2733function getLoadedRoutes(route) {
2734 return route._loadedRoutes;
2735}
2736function getLoadedInjector(route) {
2737 return route._loadedInjector;
2738}
2739function getLoadedComponent(route) {
2740 return route._loadedComponent;
2741}
2742function getProvidersInjector(route) {
2743 return route._injector;
2744}
2745function 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}
2753function 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}
2762function 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}
2828function 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 */
2848function 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. */
2858function 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 */
2865function 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 */
2882function 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
2905const activateRoutes = (rootContexts, routeReuseStrategy, forwardEvent) => map(t => {
2906 new ActivateRoutes(routeReuseStrategy, t.targetRouterState, t.currentRouterState, forwardEvent)
2907 .activate(rootContexts);
2908 return t;
2909});
2910class 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
3073class CanActivate {
3074 constructor(path) {
3075 this.path = path;
3076 this.route = this.path[this.path.length - 1];
3077 }
3078}
3079class CanDeactivate {
3080 constructor(component, route) {
3081 this.component = component;
3082 this.route = route;
3083 }
3084}
3085function 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}
3090function 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}
3096function 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}
3111function 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}
3125function 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}
3171function 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}
3191function 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 */
3229function isFunction(v) {
3230 return typeof v === 'function';
3231}
3232function isBoolean(v) {
3233 return typeof v === 'boolean';
3234}
3235function isCanLoad(guard) {
3236 return guard && isFunction(guard.canLoad);
3237}
3238function isCanActivate(guard) {
3239 return guard && isFunction(guard.canActivate);
3240}
3241function isCanActivateChild(guard) {
3242 return guard && isFunction(guard.canActivateChild);
3243}
3244function isCanDeactivate(guard) {
3245 return guard && isFunction(guard.canDeactivate);
3246}
3247function isCanMatch(guard) {
3248 return guard && isFunction(guard.canMatch);
3249}
3250function isRedirectingNavigationCancelingError(error) {
3251 return isNavigationCancelingError(error) && isUrlTree(error.url);
3252}
3253function isNavigationCancelingError(error) {
3254 return error && error[NAVIGATION_CANCELING_ERROR];
3255}
3256function isEmptyError(e) {
3257 return e instanceof EmptyError || e?.name === 'EmptyError';
3258}
3259
3260const INITIAL_VALUE = Symbol('INITIAL_VALUE');
3261function 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
3287function 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}
3301function 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}
3306function 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 */
3321function 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 */
3335function fireChildActivationStart(snapshot, forwardEvent) {
3336 if (snapshot !== null && forwardEvent) {
3337 forwardEvent(new ChildActivationStart(snapshot));
3338 }
3339 return of(true);
3340}
3341function 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}
3357function 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}
3378function 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}
3392function 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}
3407function redirectIfUrlTree(urlSerializer) {
3408 return pipe(tap((result) => {
3409 if (!isUrlTree(result))
3410 return;
3411 throw redirectingNavigationError(urlSerializer, result);
3412 }), map(result => result === true));
3413}
3414function 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
3429const noMatch$1 = {
3430 matched: false,
3431 consumedSegments: [],
3432 remainingSegments: [],
3433 parameters: {},
3434 positionalParamSegments: {}
3435};
3436function 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}
3447function 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}
3480function 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}
3500function 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}
3512function 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}
3527function containsEmptyPathMatchesWithNamedOutlets(segmentGroup, slicedSegments, routes) {
3528 return routes.some(r => emptyPathMatch(segmentGroup, slicedSegments, r) && getOutlet(r) !== PRIMARY_OUTLET);
3529}
3530function containsEmptyPathMatches(segmentGroup, slicedSegments, routes) {
3531 return routes.some(r => emptyPathMatch(segmentGroup, slicedSegments, r));
3532}
3533function 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 */
3544function 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}
3565function noLeftoversInUrl(segmentGroup, segments, outlet) {
3566 return segments.length === 0 && !segmentGroup.children[outlet];
3567}
3568
3569const NG_DEV_MODE$7 = typeof ngDevMode === 'undefined' || ngDevMode;
3570class NoMatch$1 {
3571 constructor(segmentGroup) {
3572 this.segmentGroup = segmentGroup || null;
3573 }
3574}
3575class AbsoluteRedirect {
3576 constructor(urlTree) {
3577 this.urlTree = urlTree;
3578 }
3579}
3580function noMatch(segmentGroup) {
3581 return throwError(new NoMatch$1(segmentGroup));
3582}
3583function absoluteRedirect(newTree) {
3584 return throwError(new AbsoluteRedirect(newTree));
3585}
3586function 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}
3590function 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 */
3599function applyRedirects$1(injector, configLoader, urlSerializer, urlTree, config) {
3600 return new ApplyRedirects(injector, configLoader, urlSerializer, urlTree, config).apply();
3601}
3602class 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
3883function 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
3888const NG_DEV_MODE$6 = typeof ngDevMode === 'undefined' || !!ngDevMode;
3889class NoMatch {
3890}
3891function 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}
3895function 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}
3907class 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}
4069function 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}
4078function getChildConfig(route) {
4079 if (route.children) {
4080 return route.children;
4081 }
4082 if (route.loadChildren) {
4083 return route._loadedRoutes;
4084 }
4085 return [];
4086}
4087function 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 */
4096function 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}
4124function 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}
4136function getSourceSegmentGroup(segmentGroup) {
4137 let s = segmentGroup;
4138 while (s._sourceSegment) {
4139 s = s._sourceSegment;
4140 }
4141 return s;
4142}
4143function 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}
4152function 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}
4161function getData(route) {
4162 return route.data || {};
4163}
4164function getResolve(route) {
4165 return route.resolve || {};
4166}
4167
4168function 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
4173function 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}
4184function 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}
4199function 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}
4210function getDataKeys(obj) {
4211 return [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)];
4212}
4213function 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}
4221function 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 */
4231function 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.
4243function deprecatedLoadChildrenString(injector, loadChildren) {
4244 return null;
4245}
4246
4247const 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 */
4258const ROUTES = new InjectionToken('ROUTES');
4259class 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}
4351RouterConfigLoader.ɵ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 });
4352RouterConfigLoader.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterConfigLoader, providedIn: 'root' });
4353i0.ɵɵ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 }]; } });
4357function 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}
4363function 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
4369const NG_DEV_MODE$4 = typeof ngDevMode === 'undefined' || !!ngDevMode;
4370class 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}
4732NavigationTransitions.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: NavigationTransitions, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4733NavigationTransitions.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: NavigationTransitions, providedIn: 'root' });
4734i0.ɵɵ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 []; } });
4738function 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 */
4765class 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}
4786TitleStrategy.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: TitleStrategy, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4787TitleStrategy.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: TitleStrategy, providedIn: 'root', useFactory: () => inject(DefaultTitleStrategy) });
4788i0.ɵɵ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 */
4795class 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}
4812DefaultTitleStrategy.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultTitleStrategy, deps: [{ token: i1.Title }], target: i0.ɵɵFactoryTarget.Injectable });
4813DefaultTitleStrategy.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultTitleStrategy, providedIn: 'root' });
4814i0.ɵɵ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 */
4826class RouteReuseStrategy {
4827}
4828RouteReuseStrategy.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouteReuseStrategy, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4829RouteReuseStrategy.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouteReuseStrategy, providedIn: 'root', useFactory: () => inject(DefaultRouteReuseStrategy) });
4830i0.ɵɵ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 */
4851class 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}
4880class DefaultRouteReuseStrategy extends BaseRouteReuseStrategy {
4881}
4882DefaultRouteReuseStrategy.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultRouteReuseStrategy, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
4883DefaultRouteReuseStrategy.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultRouteReuseStrategy, providedIn: 'root' });
4884i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultRouteReuseStrategy, decorators: [{
4885 type: Injectable,
4886 args: [{ providedIn: 'root' }]
4887 }] });
4888
4889const 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 */
4895const 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 */
4907class UrlHandlingStrategy {
4908}
4909UrlHandlingStrategy.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: UrlHandlingStrategy, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4910UrlHandlingStrategy.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: UrlHandlingStrategy, providedIn: 'root', useFactory: () => inject(DefaultUrlHandlingStrategy) });
4911i0.ɵɵ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 */
4918class DefaultUrlHandlingStrategy {
4919 shouldProcessUrl(url) {
4920 return true;
4921 }
4922 extract(url) {
4923 return url;
4924 }
4925 merge(newUrlPart, wholeUrl) {
4926 return newUrlPart;
4927 }
4928}
4929DefaultUrlHandlingStrategy.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultUrlHandlingStrategy, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4930DefaultUrlHandlingStrategy.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultUrlHandlingStrategy, providedIn: 'root' });
4931i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: DefaultUrlHandlingStrategy, decorators: [{
4932 type: Injectable,
4933 args: [{ providedIn: 'root' }]
4934 }] });
4935
4936const NG_DEV_MODE$2 = typeof ngDevMode === 'undefined' || !!ngDevMode;
4937function defaultErrorHandler(error) {
4938 throw error;
4939}
4940function 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 */
4947const 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 */
4957const 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 */
4975class 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}
5581Router.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: Router, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
5582Router.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: Router, providedIn: 'root' });
5583i0.ɵɵ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 []; } });
5587function 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 */
5693class 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}
5869RouterLink.ɵ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 });
5870RouterLink.ɵ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 });
5871i0.ɵɵ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 */
5978class 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}
6092RouterLinkActive.ɵ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 });
6093RouterLinkActive.ɵ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 });
6094i0.ɵɵ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 */
6118function isActiveMatchOptions(options) {
6119 return !!options.paths;
6120}
6121
6122/**
6123 * @description
6124 *
6125 * Provides a preloading strategy.
6126 *
6127 * @publicApi
6128 */
6129class 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 */
6142class PreloadAllModules {
6143 preload(route, fn) {
6144 return fn().pipe(catchError(() => of(null)));
6145 }
6146}
6147PreloadAllModules.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: PreloadAllModules, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
6148PreloadAllModules.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: PreloadAllModules, providedIn: 'root' });
6149i0.ɵɵ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 */
6162class NoPreloading {
6163 preload(route, fn) {
6164 return of(null);
6165 }
6166}
6167NoPreloading.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: NoPreloading, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
6168NoPreloading.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: NoPreloading, providedIn: 'root' });
6169i0.ɵɵ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 */
6185class 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}
6263RouterPreloader.ɵ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 });
6264RouterPreloader.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterPreloader, providedIn: 'root' });
6265i0.ɵɵ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
6270const ROUTER_SCROLLER = new InjectionToken('');
6271class 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}
6353RouterScroller.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterScroller, deps: "invalid", target: i0.ɵɵFactoryTarget.Injectable });
6354RouterScroller.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterScroller });
6355i0.ɵɵ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
6359var 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 */
6374function 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
6392const 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 */
6429function 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}
6438function rootRoute(router) {
6439 return router.routerState.root;
6440}
6441/**
6442 * Helper function to create an object that represents a Router feature.
6443 */
6444function 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 */
6451const ROUTER_IS_PROVIDED = new InjectionToken('', { providedIn: 'root', factory: () => false });
6452const 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 */
6481function 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 */
6512function 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}
6525function 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 */
6551const BOOTSTRAP_DONE = new InjectionToken(NG_DEV_MODE$1 ? 'bootstrap done indicator' : '', {
6552 factory: () => {
6553 return new Subject();
6554 }
6555});
6556const 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 */
6582function 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 */
6643function 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 */
6683function 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}
6707const 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 */
6734function 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 */
6768function 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 */
6798function 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 */
6833function 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
6849const NG_DEV_MODE = typeof ngDevMode === 'undefined' || ngDevMode;
6850/**
6851 * The directives defined in the `RouterModule`.
6852 */
6853const ROUTER_DIRECTIVES = [RouterOutlet, RouterLink, RouterLinkActive, ɵEmptyOutletComponent];
6854/**
6855 * @docsNotRequired
6856 */
6857const 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.
6862const 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];
6873function 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 */
6897class 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}
6962RouterModule.ɵ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 });
6963RouterModule.ɵ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] });
6964RouterModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.3", ngImport: i0, type: RouterModule, imports: [ɵEmptyOutletComponent] });
6965i0.ɵɵ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 */
6981function 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}`.
6999function 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`.
7004function providePathLocationStrategy() {
7005 return { provide: LocationStrategy, useClass: PathLocationStrategy };
7006}
7007function 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.
7016function 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 */
7031const ROUTER_INITIALIZER = new InjectionToken(NG_DEV_MODE ? 'Router Initializer' : '');
7032function 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 */
7049const 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
7064export { 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