source: trip-planner-front/node_modules/@angular/router/fesm2015/router.js@ ceaed42

Last change on this file since ceaed42 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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