| /** |
| * @license Angular v11.2.14 |
| * (c) 2010-2021 Google LLC. https://angular.io/ |
| * License: MIT |
| */ |
| |
| import { Location, LocationStrategy, ViewportScroller, PlatformLocation, APP_BASE_HREF, HashLocationStrategy, PathLocationStrategy, ɵgetDOM, LOCATION_INITIALIZED } from '@angular/common'; |
| import { ɵ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'; |
| import { from, of, BehaviorSubject, combineLatest, Observable, EmptyError, concat, defer, EMPTY, ConnectableObservable, Subject } from 'rxjs'; |
| import { map, switchMap, take, startWith, scan, filter, catchError, concatMap, last as last$1, first, mergeMap, tap, takeLast, refCount, finalize, mergeAll } from 'rxjs/operators'; |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * Base for events the router goes through, as opposed to events tied to a specific |
| * route. Fired one time for any given navigation. |
| * |
| * The following code shows how a class subscribes to router events. |
| * |
| * ```ts |
| * class MyService { |
| * constructor(public router: Router, logger: Logger) { |
| * router.events.pipe( |
| * filter((e: Event): e is RouterEvent => e instanceof RouterEvent) |
| * ).subscribe((e: RouterEvent) => { |
| * logger.log(e.id, e.url); |
| * }); |
| * } |
| * } |
| * ``` |
| * |
| * @see `Event` |
| * @see [Router events summary](guide/router#router-events) |
| * @publicApi |
| */ |
| class RouterEvent { |
| constructor( |
| /** A unique ID that the router assigns to every router navigation. */ |
| id, |
| /** The URL that is the destination for this navigation. */ |
| url) { |
| this.id = id; |
| this.url = url; |
| } |
| } |
| /** |
| * An event triggered when a navigation starts. |
| * |
| * @publicApi |
| */ |
| class NavigationStart extends RouterEvent { |
| constructor( |
| /** @docsNotRequired */ |
| id, |
| /** @docsNotRequired */ |
| url, |
| /** @docsNotRequired */ |
| navigationTrigger = 'imperative', |
| /** @docsNotRequired */ |
| restoredState = null) { |
| super(id, url); |
| this.navigationTrigger = navigationTrigger; |
| this.restoredState = restoredState; |
| } |
| /** @docsNotRequired */ |
| toString() { |
| return `NavigationStart(id: ${this.id}, url: '${this.url}')`; |
| } |
| } |
| /** |
| * An event triggered when a navigation ends successfully. |
| * |
| * @see `NavigationStart` |
| * @see `NavigationCancel` |
| * @see `NavigationError` |
| * |
| * @publicApi |
| */ |
| class NavigationEnd extends RouterEvent { |
| constructor( |
| /** @docsNotRequired */ |
| id, |
| /** @docsNotRequired */ |
| url, |
| /** @docsNotRequired */ |
| urlAfterRedirects) { |
| super(id, url); |
| this.urlAfterRedirects = urlAfterRedirects; |
| } |
| /** @docsNotRequired */ |
| toString() { |
| return `NavigationEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}')`; |
| } |
| } |
| /** |
| * An event triggered when a navigation is canceled, directly or indirectly. |
| * This can happen when a route guard |
| * returns `false` or initiates a redirect by returning a `UrlTree`. |
| * |
| * @see `NavigationStart` |
| * @see `NavigationEnd` |
| * @see `NavigationError` |
| * |
| * @publicApi |
| */ |
| class NavigationCancel extends RouterEvent { |
| constructor( |
| /** @docsNotRequired */ |
| id, |
| /** @docsNotRequired */ |
| url, |
| /** @docsNotRequired */ |
| reason) { |
| super(id, url); |
| this.reason = reason; |
| } |
| /** @docsNotRequired */ |
| toString() { |
| return `NavigationCancel(id: ${this.id}, url: '${this.url}')`; |
| } |
| } |
| /** |
| * An event triggered when a navigation fails due to an unexpected error. |
| * |
| * @see `NavigationStart` |
| * @see `NavigationEnd` |
| * @see `NavigationCancel` |
| * |
| * @publicApi |
| */ |
| class NavigationError extends RouterEvent { |
| constructor( |
| /** @docsNotRequired */ |
| id, |
| /** @docsNotRequired */ |
| url, |
| /** @docsNotRequired */ |
| error) { |
| super(id, url); |
| this.error = error; |
| } |
| /** @docsNotRequired */ |
| toString() { |
| return `NavigationError(id: ${this.id}, url: '${this.url}', error: ${this.error})`; |
| } |
| } |
| /** |
| * An event triggered when routes are recognized. |
| * |
| * @publicApi |
| */ |
| class RoutesRecognized extends RouterEvent { |
| constructor( |
| /** @docsNotRequired */ |
| id, |
| /** @docsNotRequired */ |
| url, |
| /** @docsNotRequired */ |
| urlAfterRedirects, |
| /** @docsNotRequired */ |
| state) { |
| super(id, url); |
| this.urlAfterRedirects = urlAfterRedirects; |
| this.state = state; |
| } |
| /** @docsNotRequired */ |
| toString() { |
| return `RoutesRecognized(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`; |
| } |
| } |
| /** |
| * An event triggered at the start of the Guard phase of routing. |
| * |
| * @see `GuardsCheckEnd` |
| * |
| * @publicApi |
| */ |
| class GuardsCheckStart extends RouterEvent { |
| constructor( |
| /** @docsNotRequired */ |
| id, |
| /** @docsNotRequired */ |
| url, |
| /** @docsNotRequired */ |
| urlAfterRedirects, |
| /** @docsNotRequired */ |
| state) { |
| super(id, url); |
| this.urlAfterRedirects = urlAfterRedirects; |
| this.state = state; |
| } |
| toString() { |
| return `GuardsCheckStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`; |
| } |
| } |
| /** |
| * An event triggered at the end of the Guard phase of routing. |
| * |
| * @see `GuardsCheckStart` |
| * |
| * @publicApi |
| */ |
| class GuardsCheckEnd extends RouterEvent { |
| constructor( |
| /** @docsNotRequired */ |
| id, |
| /** @docsNotRequired */ |
| url, |
| /** @docsNotRequired */ |
| urlAfterRedirects, |
| /** @docsNotRequired */ |
| state, |
| /** @docsNotRequired */ |
| shouldActivate) { |
| super(id, url); |
| this.urlAfterRedirects = urlAfterRedirects; |
| this.state = state; |
| this.shouldActivate = shouldActivate; |
| } |
| toString() { |
| return `GuardsCheckEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state}, shouldActivate: ${this.shouldActivate})`; |
| } |
| } |
| /** |
| * An event triggered at the start of the Resolve phase of routing. |
| * |
| * Runs in the "resolve" phase whether or not there is anything to resolve. |
| * In future, may change to only run when there are things to be resolved. |
| * |
| * @see `ResolveEnd` |
| * |
| * @publicApi |
| */ |
| class ResolveStart extends RouterEvent { |
| constructor( |
| /** @docsNotRequired */ |
| id, |
| /** @docsNotRequired */ |
| url, |
| /** @docsNotRequired */ |
| urlAfterRedirects, |
| /** @docsNotRequired */ |
| state) { |
| super(id, url); |
| this.urlAfterRedirects = urlAfterRedirects; |
| this.state = state; |
| } |
| toString() { |
| return `ResolveStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`; |
| } |
| } |
| /** |
| * An event triggered at the end of the Resolve phase of routing. |
| * @see `ResolveStart`. |
| * |
| * @publicApi |
| */ |
| class ResolveEnd extends RouterEvent { |
| constructor( |
| /** @docsNotRequired */ |
| id, |
| /** @docsNotRequired */ |
| url, |
| /** @docsNotRequired */ |
| urlAfterRedirects, |
| /** @docsNotRequired */ |
| state) { |
| super(id, url); |
| this.urlAfterRedirects = urlAfterRedirects; |
| this.state = state; |
| } |
| toString() { |
| return `ResolveEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`; |
| } |
| } |
| /** |
| * An event triggered before lazy loading a route configuration. |
| * |
| * @see `RouteConfigLoadEnd` |
| * |
| * @publicApi |
| */ |
| class RouteConfigLoadStart { |
| constructor( |
| /** @docsNotRequired */ |
| route) { |
| this.route = route; |
| } |
| toString() { |
| return `RouteConfigLoadStart(path: ${this.route.path})`; |
| } |
| } |
| /** |
| * An event triggered when a route has been lazy loaded. |
| * |
| * @see `RouteConfigLoadStart` |
| * |
| * @publicApi |
| */ |
| class RouteConfigLoadEnd { |
| constructor( |
| /** @docsNotRequired */ |
| route) { |
| this.route = route; |
| } |
| toString() { |
| return `RouteConfigLoadEnd(path: ${this.route.path})`; |
| } |
| } |
| /** |
| * An event triggered at the start of the child-activation |
| * part of the Resolve phase of routing. |
| * @see `ChildActivationEnd` |
| * @see `ResolveStart` |
| * |
| * @publicApi |
| */ |
| class ChildActivationStart { |
| constructor( |
| /** @docsNotRequired */ |
| snapshot) { |
| this.snapshot = snapshot; |
| } |
| toString() { |
| const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || ''; |
| return `ChildActivationStart(path: '${path}')`; |
| } |
| } |
| /** |
| * An event triggered at the end of the child-activation part |
| * of the Resolve phase of routing. |
| * @see `ChildActivationStart` |
| * @see `ResolveStart` |
| * @publicApi |
| */ |
| class ChildActivationEnd { |
| constructor( |
| /** @docsNotRequired */ |
| snapshot) { |
| this.snapshot = snapshot; |
| } |
| toString() { |
| const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || ''; |
| return `ChildActivationEnd(path: '${path}')`; |
| } |
| } |
| /** |
| * An event triggered at the start of the activation part |
| * of the Resolve phase of routing. |
| * @see `ActivationEnd` |
| * @see `ResolveStart` |
| * |
| * @publicApi |
| */ |
| class ActivationStart { |
| constructor( |
| /** @docsNotRequired */ |
| snapshot) { |
| this.snapshot = snapshot; |
| } |
| toString() { |
| const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || ''; |
| return `ActivationStart(path: '${path}')`; |
| } |
| } |
| /** |
| * An event triggered at the end of the activation part |
| * of the Resolve phase of routing. |
| * @see `ActivationStart` |
| * @see `ResolveStart` |
| * |
| * @publicApi |
| */ |
| class ActivationEnd { |
| constructor( |
| /** @docsNotRequired */ |
| snapshot) { |
| this.snapshot = snapshot; |
| } |
| toString() { |
| const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || ''; |
| return `ActivationEnd(path: '${path}')`; |
| } |
| } |
| /** |
| * An event triggered by scrolling. |
| * |
| * @publicApi |
| */ |
| class Scroll { |
| constructor( |
| /** @docsNotRequired */ |
| routerEvent, |
| /** @docsNotRequired */ |
| position, |
| /** @docsNotRequired */ |
| anchor) { |
| this.routerEvent = routerEvent; |
| this.position = position; |
| this.anchor = anchor; |
| } |
| toString() { |
| const pos = this.position ? `${this.position[0]}, ${this.position[1]}` : null; |
| return `Scroll(anchor: '${this.anchor}', position: '${pos}')`; |
| } |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * The primary routing outlet. |
| * |
| * @publicApi |
| */ |
| const PRIMARY_OUTLET = 'primary'; |
| class ParamsAsMap { |
| constructor(params) { |
| this.params = params || {}; |
| } |
| has(name) { |
| return Object.prototype.hasOwnProperty.call(this.params, name); |
| } |
| get(name) { |
| if (this.has(name)) { |
| const v = this.params[name]; |
| return Array.isArray(v) ? v[0] : v; |
| } |
| return null; |
| } |
| getAll(name) { |
| if (this.has(name)) { |
| const v = this.params[name]; |
| return Array.isArray(v) ? v : [v]; |
| } |
| return []; |
| } |
| get keys() { |
| return Object.keys(this.params); |
| } |
| } |
| /** |
| * Converts a `Params` instance to a `ParamMap`. |
| * @param params The instance to convert. |
| * @returns The new map instance. |
| * |
| * @publicApi |
| */ |
| function convertToParamMap(params) { |
| return new ParamsAsMap(params); |
| } |
| const NAVIGATION_CANCELING_ERROR = 'ngNavigationCancelingError'; |
| function navigationCancelingError(message) { |
| const error = Error('NavigationCancelingError: ' + message); |
| error[NAVIGATION_CANCELING_ERROR] = true; |
| return error; |
| } |
| function isNavigationCancelingError(error) { |
| return error && error[NAVIGATION_CANCELING_ERROR]; |
| } |
| // Matches the route configuration (`route`) against the actual URL (`segments`). |
| function defaultUrlMatcher(segments, segmentGroup, route) { |
| const parts = route.path.split('/'); |
| if (parts.length > segments.length) { |
| // The actual URL is shorter than the config, no match |
| return null; |
| } |
| if (route.pathMatch === 'full' && |
| (segmentGroup.hasChildren() || parts.length < segments.length)) { |
| // The config is longer than the actual URL but we are looking for a full match, return null |
| return null; |
| } |
| const posParams = {}; |
| // Check each config part against the actual URL |
| for (let index = 0; index < parts.length; index++) { |
| const part = parts[index]; |
| const segment = segments[index]; |
| const isParameter = part.startsWith(':'); |
| if (isParameter) { |
| posParams[part.substring(1)] = segment; |
| } |
| else if (part !== segment.path) { |
| // The actual URL part does not match the config, no match |
| return null; |
| } |
| } |
| return { consumed: segments.slice(0, parts.length), posParams }; |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| function shallowEqualArrays(a, b) { |
| if (a.length !== b.length) |
| return false; |
| for (let i = 0; i < a.length; ++i) { |
| if (!shallowEqual(a[i], b[i])) |
| return false; |
| } |
| return true; |
| } |
| function shallowEqual(a, b) { |
| // While `undefined` should never be possible, it would sometimes be the case in IE 11 |
| // and pre-chromium Edge. The check below accounts for this edge case. |
| const k1 = a ? Object.keys(a) : undefined; |
| const k2 = b ? Object.keys(b) : undefined; |
| if (!k1 || !k2 || k1.length != k2.length) { |
| return false; |
| } |
| let key; |
| for (let i = 0; i < k1.length; i++) { |
| key = k1[i]; |
| if (!equalArraysOrString(a[key], b[key])) { |
| return false; |
| } |
| } |
| return true; |
| } |
| /** |
| * Test equality for arrays of strings or a string. |
| */ |
| function equalArraysOrString(a, b) { |
| if (Array.isArray(a) && Array.isArray(b)) { |
| if (a.length !== b.length) |
| return false; |
| const aSorted = [...a].sort(); |
| const bSorted = [...b].sort(); |
| return aSorted.every((val, index) => bSorted[index] === val); |
| } |
| else { |
| return a === b; |
| } |
| } |
| /** |
| * Flattens single-level nested arrays. |
| */ |
| function flatten(arr) { |
| return Array.prototype.concat.apply([], arr); |
| } |
| /** |
| * Return the last element of an array. |
| */ |
| function last(a) { |
| return a.length > 0 ? a[a.length - 1] : null; |
| } |
| /** |
| * Verifys all booleans in an array are `true`. |
| */ |
| function and(bools) { |
| return !bools.some(v => !v); |
| } |
| function forEach(map, callback) { |
| for (const prop in map) { |
| if (map.hasOwnProperty(prop)) { |
| callback(map[prop], prop); |
| } |
| } |
| } |
| function wrapIntoObservable(value) { |
| if (ɵisObservable(value)) { |
| return value; |
| } |
| if (ɵisPromise(value)) { |
| // Use `Promise.resolve()` to wrap promise-like instances. |
| // Required ie when a Resolver returns a AngularJS `$q` promise to correctly trigger the |
| // change detection. |
| return from(Promise.resolve(value)); |
| } |
| return of(value); |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| function createEmptyUrlTree() { |
| return new UrlTree(new UrlSegmentGroup([], {}), {}, null); |
| } |
| function containsTree(container, containee, exact) { |
| if (exact) { |
| return equalQueryParams(container.queryParams, containee.queryParams) && |
| equalSegmentGroups(container.root, containee.root); |
| } |
| return containsQueryParams(container.queryParams, containee.queryParams) && |
| containsSegmentGroup(container.root, containee.root); |
| } |
| function equalQueryParams(container, containee) { |
| // TODO: This does not handle array params correctly. |
| return shallowEqual(container, containee); |
| } |
| function equalSegmentGroups(container, containee) { |
| if (!equalPath(container.segments, containee.segments)) |
| return false; |
| if (container.numberOfChildren !== containee.numberOfChildren) |
| return false; |
| for (const c in containee.children) { |
| if (!container.children[c]) |
| return false; |
| if (!equalSegmentGroups(container.children[c], containee.children[c])) |
| return false; |
| } |
| return true; |
| } |
| function containsQueryParams(container, containee) { |
| return Object.keys(containee).length <= Object.keys(container).length && |
| Object.keys(containee).every(key => equalArraysOrString(container[key], containee[key])); |
| } |
| function containsSegmentGroup(container, containee) { |
| return containsSegmentGroupHelper(container, containee, containee.segments); |
| } |
| function containsSegmentGroupHelper(container, containee, containeePaths) { |
| if (container.segments.length > containeePaths.length) { |
| const current = container.segments.slice(0, containeePaths.length); |
| if (!equalPath(current, containeePaths)) |
| return false; |
| if (containee.hasChildren()) |
| return false; |
| return true; |
| } |
| else if (container.segments.length === containeePaths.length) { |
| if (!equalPath(container.segments, containeePaths)) |
| return false; |
| for (const c in containee.children) { |
| if (!container.children[c]) |
| return false; |
| if (!containsSegmentGroup(container.children[c], containee.children[c])) |
| return false; |
| } |
| return true; |
| } |
| else { |
| const current = containeePaths.slice(0, container.segments.length); |
| const next = containeePaths.slice(container.segments.length); |
| if (!equalPath(container.segments, current)) |
| return false; |
| if (!container.children[PRIMARY_OUTLET]) |
| return false; |
| return containsSegmentGroupHelper(container.children[PRIMARY_OUTLET], containee, next); |
| } |
| } |
| /** |
| * @description |
| * |
| * Represents the parsed URL. |
| * |
| * Since a router state is a tree, and the URL is nothing but a serialized state, the URL is a |
| * serialized tree. |
| * UrlTree is a data structure that provides a lot of affordances in dealing with URLs |
| * |
| * @usageNotes |
| * ### Example |
| * |
| * ``` |
| * @Component({templateUrl:'template.html'}) |
| * class MyComponent { |
| * constructor(router: Router) { |
| * const tree: UrlTree = |
| * router.parseUrl('/team/33/(user/victor//support:help)?debug=true#fragment'); |
| * const f = tree.fragment; // return 'fragment' |
| * const q = tree.queryParams; // returns {debug: 'true'} |
| * const g: UrlSegmentGroup = tree.root.children[PRIMARY_OUTLET]; |
| * const s: UrlSegment[] = g.segments; // returns 2 segments 'team' and '33' |
| * g.children[PRIMARY_OUTLET].segments; // returns 2 segments 'user' and 'victor' |
| * g.children['support'].segments; // return 1 segment 'help' |
| * } |
| * } |
| * ``` |
| * |
| * @publicApi |
| */ |
| class UrlTree { |
| /** @internal */ |
| constructor( |
| /** The root segment group of the URL tree */ |
| root, |
| /** The query params of the URL */ |
| queryParams, |
| /** The fragment of the URL */ |
| fragment) { |
| this.root = root; |
| this.queryParams = queryParams; |
| this.fragment = fragment; |
| } |
| get queryParamMap() { |
| if (!this._queryParamMap) { |
| this._queryParamMap = convertToParamMap(this.queryParams); |
| } |
| return this._queryParamMap; |
| } |
| /** @docsNotRequired */ |
| toString() { |
| return DEFAULT_SERIALIZER.serialize(this); |
| } |
| } |
| /** |
| * @description |
| * |
| * Represents the parsed URL segment group. |
| * |
| * See `UrlTree` for more information. |
| * |
| * @publicApi |
| */ |
| class UrlSegmentGroup { |
| constructor( |
| /** The URL segments of this group. See `UrlSegment` for more information */ |
| segments, |
| /** The list of children of this group */ |
| children) { |
| this.segments = segments; |
| this.children = children; |
| /** The parent node in the url tree */ |
| this.parent = null; |
| forEach(children, (v, k) => v.parent = this); |
| } |
| /** Whether the segment has child segments */ |
| hasChildren() { |
| return this.numberOfChildren > 0; |
| } |
| /** Number of child segments */ |
| get numberOfChildren() { |
| return Object.keys(this.children).length; |
| } |
| /** @docsNotRequired */ |
| toString() { |
| return serializePaths(this); |
| } |
| } |
| /** |
| * @description |
| * |
| * Represents a single URL segment. |
| * |
| * A UrlSegment is a part of a URL between the two slashes. It contains a path and the matrix |
| * parameters associated with the segment. |
| * |
| * @usageNotes |
| * ### Example |
| * |
| * ``` |
| * @Component({templateUrl:'template.html'}) |
| * class MyComponent { |
| * constructor(router: Router) { |
| * const tree: UrlTree = router.parseUrl('/team;id=33'); |
| * const g: UrlSegmentGroup = tree.root.children[PRIMARY_OUTLET]; |
| * const s: UrlSegment[] = g.segments; |
| * s[0].path; // returns 'team' |
| * s[0].parameters; // returns {id: 33} |
| * } |
| * } |
| * ``` |
| * |
| * @publicApi |
| */ |
| class UrlSegment { |
| constructor( |
| /** The path part of a URL segment */ |
| path, |
| /** The matrix parameters associated with a segment */ |
| parameters) { |
| this.path = path; |
| this.parameters = parameters; |
| } |
| get parameterMap() { |
| if (!this._parameterMap) { |
| this._parameterMap = convertToParamMap(this.parameters); |
| } |
| return this._parameterMap; |
| } |
| /** @docsNotRequired */ |
| toString() { |
| return serializePath(this); |
| } |
| } |
| function equalSegments(as, bs) { |
| return equalPath(as, bs) && as.every((a, i) => shallowEqual(a.parameters, bs[i].parameters)); |
| } |
| function equalPath(as, bs) { |
| if (as.length !== bs.length) |
| return false; |
| return as.every((a, i) => a.path === bs[i].path); |
| } |
| function mapChildrenIntoArray(segment, fn) { |
| let res = []; |
| forEach(segment.children, (child, childOutlet) => { |
| if (childOutlet === PRIMARY_OUTLET) { |
| res = res.concat(fn(child, childOutlet)); |
| } |
| }); |
| forEach(segment.children, (child, childOutlet) => { |
| if (childOutlet !== PRIMARY_OUTLET) { |
| res = res.concat(fn(child, childOutlet)); |
| } |
| }); |
| return res; |
| } |
| /** |
| * @description |
| * |
| * Serializes and deserializes a URL string into a URL tree. |
| * |
| * The url serialization strategy is customizable. You can |
| * make all URLs case insensitive by providing a custom UrlSerializer. |
| * |
| * See `DefaultUrlSerializer` for an example of a URL serializer. |
| * |
| * @publicApi |
| */ |
| class UrlSerializer { |
| } |
| /** |
| * @description |
| * |
| * A default implementation of the `UrlSerializer`. |
| * |
| * Example URLs: |
| * |
| * ``` |
| * /inbox/33(popup:compose) |
| * /inbox/33;open=true/messages/44 |
| * ``` |
| * |
| * DefaultUrlSerializer uses parentheses to serialize secondary segments (e.g., popup:compose), the |
| * colon syntax to specify the outlet, and the ';parameter=value' syntax (e.g., open=true) to |
| * specify route specific parameters. |
| * |
| * @publicApi |
| */ |
| class DefaultUrlSerializer { |
| /** Parses a url into a `UrlTree` */ |
| parse(url) { |
| const p = new UrlParser(url); |
| return new UrlTree(p.parseRootSegment(), p.parseQueryParams(), p.parseFragment()); |
| } |
| /** Converts a `UrlTree` into a url */ |
| serialize(tree) { |
| const segment = `/${serializeSegment(tree.root, true)}`; |
| const query = serializeQueryParams(tree.queryParams); |
| const fragment = typeof tree.fragment === `string` ? `#${encodeUriFragment(tree.fragment)}` : ''; |
| return `${segment}${query}${fragment}`; |
| } |
| } |
| const DEFAULT_SERIALIZER = new DefaultUrlSerializer(); |
| function serializePaths(segment) { |
| return segment.segments.map(p => serializePath(p)).join('/'); |
| } |
| function serializeSegment(segment, root) { |
| if (!segment.hasChildren()) { |
| return serializePaths(segment); |
| } |
| if (root) { |
| const primary = segment.children[PRIMARY_OUTLET] ? |
| serializeSegment(segment.children[PRIMARY_OUTLET], false) : |
| ''; |
| const children = []; |
| forEach(segment.children, (v, k) => { |
| if (k !== PRIMARY_OUTLET) { |
| children.push(`${k}:${serializeSegment(v, false)}`); |
| } |
| }); |
| return children.length > 0 ? `${primary}(${children.join('//')})` : primary; |
| } |
| else { |
| const children = mapChildrenIntoArray(segment, (v, k) => { |
| if (k === PRIMARY_OUTLET) { |
| return [serializeSegment(segment.children[PRIMARY_OUTLET], false)]; |
| } |
| return [`${k}:${serializeSegment(v, false)}`]; |
| }); |
| // use no parenthesis if the only child is a primary outlet route |
| if (Object.keys(segment.children).length === 1 && segment.children[PRIMARY_OUTLET] != null) { |
| return `${serializePaths(segment)}/${children[0]}`; |
| } |
| return `${serializePaths(segment)}/(${children.join('//')})`; |
| } |
| } |
| /** |
| * Encodes a URI string with the default encoding. This function will only ever be called from |
| * `encodeUriQuery` or `encodeUriSegment` as it's the base set of encodings to be used. We need |
| * a custom encoding because encodeURIComponent is too aggressive and encodes stuff that doesn't |
| * have to be encoded per https://url.spec.whatwg.org. |
| */ |
| function encodeUriString(s) { |
| return encodeURIComponent(s) |
| .replace(/%40/g, '@') |
| .replace(/%3A/gi, ':') |
| .replace(/%24/g, '$') |
| .replace(/%2C/gi, ','); |
| } |
| /** |
| * This function should be used to encode both keys and values in a query string key/value. In |
| * the following URL, you need to call encodeUriQuery on "k" and "v": |
| * |
| * http://www.site.org/html;mk=mv?k=v#f |
| */ |
| function encodeUriQuery(s) { |
| return encodeUriString(s).replace(/%3B/gi, ';'); |
| } |
| /** |
| * This function should be used to encode a URL fragment. In the following URL, you need to call |
| * encodeUriFragment on "f": |
| * |
| * http://www.site.org/html;mk=mv?k=v#f |
| */ |
| function encodeUriFragment(s) { |
| return encodeURI(s); |
| } |
| /** |
| * This function should be run on any URI segment as well as the key and value in a key/value |
| * pair for matrix params. In the following URL, you need to call encodeUriSegment on "html", |
| * "mk", and "mv": |
| * |
| * http://www.site.org/html;mk=mv?k=v#f |
| */ |
| function encodeUriSegment(s) { |
| return encodeUriString(s).replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/%26/gi, '&'); |
| } |
| function decode(s) { |
| return decodeURIComponent(s); |
| } |
| // Query keys/values should have the "+" replaced first, as "+" in a query string is " ". |
| // decodeURIComponent function will not decode "+" as a space. |
| function decodeQuery(s) { |
| return decode(s.replace(/\+/g, '%20')); |
| } |
| function serializePath(path) { |
| return `${encodeUriSegment(path.path)}${serializeMatrixParams(path.parameters)}`; |
| } |
| function serializeMatrixParams(params) { |
| return Object.keys(params) |
| .map(key => `;${encodeUriSegment(key)}=${encodeUriSegment(params[key])}`) |
| .join(''); |
| } |
| function serializeQueryParams(params) { |
| const strParams = Object.keys(params).map((name) => { |
| const value = params[name]; |
| return Array.isArray(value) ? |
| value.map(v => `${encodeUriQuery(name)}=${encodeUriQuery(v)}`).join('&') : |
| `${encodeUriQuery(name)}=${encodeUriQuery(value)}`; |
| }); |
| return strParams.length ? `?${strParams.join('&')}` : ''; |
| } |
| const SEGMENT_RE = /^[^\/()?;=#]+/; |
| function matchSegments(str) { |
| const match = str.match(SEGMENT_RE); |
| return match ? match[0] : ''; |
| } |
| const QUERY_PARAM_RE = /^[^=?&#]+/; |
| // Return the name of the query param at the start of the string or an empty string |
| function matchQueryParams(str) { |
| const match = str.match(QUERY_PARAM_RE); |
| return match ? match[0] : ''; |
| } |
| const QUERY_PARAM_VALUE_RE = /^[^?&#]+/; |
| // Return the value of the query param at the start of the string or an empty string |
| function matchUrlQueryParamValue(str) { |
| const match = str.match(QUERY_PARAM_VALUE_RE); |
| return match ? match[0] : ''; |
| } |
| class UrlParser { |
| constructor(url) { |
| this.url = url; |
| this.remaining = url; |
| } |
| parseRootSegment() { |
| this.consumeOptional('/'); |
| if (this.remaining === '' || this.peekStartsWith('?') || this.peekStartsWith('#')) { |
| return new UrlSegmentGroup([], {}); |
| } |
| // The root segment group never has segments |
| return new UrlSegmentGroup([], this.parseChildren()); |
| } |
| parseQueryParams() { |
| const params = {}; |
| if (this.consumeOptional('?')) { |
| do { |
| this.parseQueryParam(params); |
| } while (this.consumeOptional('&')); |
| } |
| return params; |
| } |
| parseFragment() { |
| return this.consumeOptional('#') ? decodeURIComponent(this.remaining) : null; |
| } |
| parseChildren() { |
| if (this.remaining === '') { |
| return {}; |
| } |
| this.consumeOptional('/'); |
| const segments = []; |
| if (!this.peekStartsWith('(')) { |
| segments.push(this.parseSegment()); |
| } |
| while (this.peekStartsWith('/') && !this.peekStartsWith('//') && !this.peekStartsWith('/(')) { |
| this.capture('/'); |
| segments.push(this.parseSegment()); |
| } |
| let children = {}; |
| if (this.peekStartsWith('/(')) { |
| this.capture('/'); |
| children = this.parseParens(true); |
| } |
| let res = {}; |
| if (this.peekStartsWith('(')) { |
| res = this.parseParens(false); |
| } |
| if (segments.length > 0 || Object.keys(children).length > 0) { |
| res[PRIMARY_OUTLET] = new UrlSegmentGroup(segments, children); |
| } |
| return res; |
| } |
| // parse a segment with its matrix parameters |
| // ie `name;k1=v1;k2` |
| parseSegment() { |
| const path = matchSegments(this.remaining); |
| if (path === '' && this.peekStartsWith(';')) { |
| throw new Error(`Empty path url segment cannot have parameters: '${this.remaining}'.`); |
| } |
| this.capture(path); |
| return new UrlSegment(decode(path), this.parseMatrixParams()); |
| } |
| parseMatrixParams() { |
| const params = {}; |
| while (this.consumeOptional(';')) { |
| this.parseParam(params); |
| } |
| return params; |
| } |
| parseParam(params) { |
| const key = matchSegments(this.remaining); |
| if (!key) { |
| return; |
| } |
| this.capture(key); |
| let value = ''; |
| if (this.consumeOptional('=')) { |
| const valueMatch = matchSegments(this.remaining); |
| if (valueMatch) { |
| value = valueMatch; |
| this.capture(value); |
| } |
| } |
| params[decode(key)] = decode(value); |
| } |
| // Parse a single query parameter `name[=value]` |
| parseQueryParam(params) { |
| const key = matchQueryParams(this.remaining); |
| if (!key) { |
| return; |
| } |
| this.capture(key); |
| let value = ''; |
| if (this.consumeOptional('=')) { |
| const valueMatch = matchUrlQueryParamValue(this.remaining); |
| if (valueMatch) { |
| value = valueMatch; |
| this.capture(value); |
| } |
| } |
| const decodedKey = decodeQuery(key); |
| const decodedVal = decodeQuery(value); |
| if (params.hasOwnProperty(decodedKey)) { |
| // Append to existing values |
| let currentVal = params[decodedKey]; |
| if (!Array.isArray(currentVal)) { |
| currentVal = [currentVal]; |
| params[decodedKey] = currentVal; |
| } |
| currentVal.push(decodedVal); |
| } |
| else { |
| // Create a new value |
| params[decodedKey] = decodedVal; |
| } |
| } |
| // parse `(a/b//outlet_name:c/d)` |
| parseParens(allowPrimary) { |
| const segments = {}; |
| this.capture('('); |
| while (!this.consumeOptional(')') && this.remaining.length > 0) { |
| const path = matchSegments(this.remaining); |
| const next = this.remaining[path.length]; |
| // if is is not one of these characters, then the segment was unescaped |
| // or the group was not closed |
| if (next !== '/' && next !== ')' && next !== ';') { |
| throw new Error(`Cannot parse url '${this.url}'`); |
| } |
| let outletName = undefined; |
| if (path.indexOf(':') > -1) { |
| outletName = path.substr(0, path.indexOf(':')); |
| this.capture(outletName); |
| this.capture(':'); |
| } |
| else if (allowPrimary) { |
| outletName = PRIMARY_OUTLET; |
| } |
| const children = this.parseChildren(); |
| segments[outletName] = Object.keys(children).length === 1 ? children[PRIMARY_OUTLET] : |
| new UrlSegmentGroup([], children); |
| this.consumeOptional('//'); |
| } |
| return segments; |
| } |
| peekStartsWith(str) { |
| return this.remaining.startsWith(str); |
| } |
| // Consumes the prefix when it is present and returns whether it has been consumed |
| consumeOptional(str) { |
| if (this.peekStartsWith(str)) { |
| this.remaining = this.remaining.substring(str.length); |
| return true; |
| } |
| return false; |
| } |
| capture(str) { |
| if (!this.consumeOptional(str)) { |
| throw new Error(`Expected "${str}".`); |
| } |
| } |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| class Tree { |
| constructor(root) { |
| this._root = root; |
| } |
| get root() { |
| return this._root.value; |
| } |
| /** |
| * @internal |
| */ |
| parent(t) { |
| const p = this.pathFromRoot(t); |
| return p.length > 1 ? p[p.length - 2] : null; |
| } |
| /** |
| * @internal |
| */ |
| children(t) { |
| const n = findNode(t, this._root); |
| return n ? n.children.map(t => t.value) : []; |
| } |
| /** |
| * @internal |
| */ |
| firstChild(t) { |
| const n = findNode(t, this._root); |
| return n && n.children.length > 0 ? n.children[0].value : null; |
| } |
| /** |
| * @internal |
| */ |
| siblings(t) { |
| const p = findPath(t, this._root); |
| if (p.length < 2) |
| return []; |
| const c = p[p.length - 2].children.map(c => c.value); |
| return c.filter(cc => cc !== t); |
| } |
| /** |
| * @internal |
| */ |
| pathFromRoot(t) { |
| return findPath(t, this._root).map(s => s.value); |
| } |
| } |
| // DFS for the node matching the value |
| function findNode(value, node) { |
| if (value === node.value) |
| return node; |
| for (const child of node.children) { |
| const node = findNode(value, child); |
| if (node) |
| return node; |
| } |
| return null; |
| } |
| // Return the path to the node with the given value using DFS |
| function findPath(value, node) { |
| if (value === node.value) |
| return [node]; |
| for (const child of node.children) { |
| const path = findPath(value, child); |
| if (path.length) { |
| path.unshift(node); |
| return path; |
| } |
| } |
| return []; |
| } |
| class TreeNode { |
| constructor(value, children) { |
| this.value = value; |
| this.children = children; |
| } |
| toString() { |
| return `TreeNode(${this.value})`; |
| } |
| } |
| // Return the list of T indexed by outlet name |
| function nodeChildrenAsMap(node) { |
| const map = {}; |
| if (node) { |
| node.children.forEach(child => map[child.value.outlet] = child); |
| } |
| return map; |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * Represents the state of the router as a tree of activated routes. |
| * |
| * @usageNotes |
| * |
| * Every node in the route tree is an `ActivatedRoute` instance |
| * that knows about the "consumed" URL segments, the extracted parameters, |
| * and the resolved data. |
| * Use the `ActivatedRoute` properties to traverse the tree from any node. |
| * |
| * The following fragment shows how a component gets the root node |
| * of the current state to establish its own route tree: |
| * |
| * ``` |
| * @Component({templateUrl:'template.html'}) |
| * class MyComponent { |
| * constructor(router: Router) { |
| * const state: RouterState = router.routerState; |
| * const root: ActivatedRoute = state.root; |
| * const child = root.firstChild; |
| * const id: Observable<string> = child.params.map(p => p.id); |
| * //... |
| * } |
| * } |
| * ``` |
| * |
| * @see `ActivatedRoute` |
| * @see [Getting route information](guide/router#getting-route-information) |
| * |
| * @publicApi |
| */ |
| class RouterState extends Tree { |
| /** @internal */ |
| constructor(root, |
| /** The current snapshot of the router state */ |
| snapshot) { |
| super(root); |
| this.snapshot = snapshot; |
| setRouterState(this, root); |
| } |
| toString() { |
| return this.snapshot.toString(); |
| } |
| } |
| function createEmptyState(urlTree, rootComponent) { |
| const snapshot = createEmptyStateSnapshot(urlTree, rootComponent); |
| const emptyUrl = new BehaviorSubject([new UrlSegment('', {})]); |
| const emptyParams = new BehaviorSubject({}); |
| const emptyData = new BehaviorSubject({}); |
| const emptyQueryParams = new BehaviorSubject({}); |
| const fragment = new BehaviorSubject(''); |
| const activated = new ActivatedRoute(emptyUrl, emptyParams, emptyQueryParams, fragment, emptyData, PRIMARY_OUTLET, rootComponent, snapshot.root); |
| activated.snapshot = snapshot.root; |
| return new RouterState(new TreeNode(activated, []), snapshot); |
| } |
| function createEmptyStateSnapshot(urlTree, rootComponent) { |
| const emptyParams = {}; |
| const emptyData = {}; |
| const emptyQueryParams = {}; |
| const fragment = ''; |
| const activated = new ActivatedRouteSnapshot([], emptyParams, emptyQueryParams, fragment, emptyData, PRIMARY_OUTLET, rootComponent, null, urlTree.root, -1, {}); |
| return new RouterStateSnapshot('', new TreeNode(activated, [])); |
| } |
| /** |
| * Provides access to information about a route associated with a component |
| * that is loaded in an outlet. |
| * Use to traverse the `RouterState` tree and extract information from nodes. |
| * |
| * The following example shows how to construct a component using information from a |
| * currently activated route. |
| * |
| * {@example router/activated-route/module.ts region="activated-route" |
| * header="activated-route.component.ts"} |
| * |
| * @see [Getting route information](guide/router#getting-route-information) |
| * |
| * @publicApi |
| */ |
| class ActivatedRoute { |
| /** @internal */ |
| constructor( |
| /** An observable of the URL segments matched by this route. */ |
| url, |
| /** An observable of the matrix parameters scoped to this route. */ |
| params, |
| /** An observable of the query parameters shared by all the routes. */ |
| queryParams, |
| /** An observable of the URL fragment shared by all the routes. */ |
| fragment, |
| /** An observable of the static and resolved data of this route. */ |
| data, |
| /** The outlet name of the route, a constant. */ |
| outlet, |
| /** The component of the route, a constant. */ |
| // TODO(vsavkin): remove |string |
| component, futureSnapshot) { |
| this.url = url; |
| this.params = params; |
| this.queryParams = queryParams; |
| this.fragment = fragment; |
| this.data = data; |
| this.outlet = outlet; |
| this.component = component; |
| this._futureSnapshot = futureSnapshot; |
| } |
| /** The configuration used to match this route. */ |
| get routeConfig() { |
| return this._futureSnapshot.routeConfig; |
| } |
| /** The root of the router state. */ |
| get root() { |
| return this._routerState.root; |
| } |
| /** The parent of this route in the router state tree. */ |
| get parent() { |
| return this._routerState.parent(this); |
| } |
| /** The first child of this route in the router state tree. */ |
| get firstChild() { |
| return this._routerState.firstChild(this); |
| } |
| /** The children of this route in the router state tree. */ |
| get children() { |
| return this._routerState.children(this); |
| } |
| /** The path from the root of the router state tree to this route. */ |
| get pathFromRoot() { |
| return this._routerState.pathFromRoot(this); |
| } |
| /** |
| * An Observable that contains a map of the required and optional parameters |
| * specific to the route. |
| * The map supports retrieving single and multiple values from the same parameter. |
| */ |
| get paramMap() { |
| if (!this._paramMap) { |
| this._paramMap = this.params.pipe(map((p) => convertToParamMap(p))); |
| } |
| return this._paramMap; |
| } |
| /** |
| * An Observable that contains a map of the query parameters available to all routes. |
| * The map supports retrieving single and multiple values from the query parameter. |
| */ |
| get queryParamMap() { |
| if (!this._queryParamMap) { |
| this._queryParamMap = |
| this.queryParams.pipe(map((p) => convertToParamMap(p))); |
| } |
| return this._queryParamMap; |
| } |
| toString() { |
| return this.snapshot ? this.snapshot.toString() : `Future(${this._futureSnapshot})`; |
| } |
| } |
| /** |
| * Returns the inherited params, data, and resolve for a given route. |
| * By default, this only inherits values up to the nearest path-less or component-less route. |
| * @internal |
| */ |
| function inheritedParamsDataResolve(route, paramsInheritanceStrategy = 'emptyOnly') { |
| const pathFromRoot = route.pathFromRoot; |
| let inheritingStartingFrom = 0; |
| if (paramsInheritanceStrategy !== 'always') { |
| inheritingStartingFrom = pathFromRoot.length - 1; |
| while (inheritingStartingFrom >= 1) { |
| const current = pathFromRoot[inheritingStartingFrom]; |
| const parent = pathFromRoot[inheritingStartingFrom - 1]; |
| // current route is an empty path => inherits its parent's params and data |
| if (current.routeConfig && current.routeConfig.path === '') { |
| inheritingStartingFrom--; |
| // parent is componentless => current route should inherit its params and data |
| } |
| else if (!parent.component) { |
| inheritingStartingFrom--; |
| } |
| else { |
| break; |
| } |
| } |
| } |
| return flattenInherited(pathFromRoot.slice(inheritingStartingFrom)); |
| } |
| /** @internal */ |
| function flattenInherited(pathFromRoot) { |
| return pathFromRoot.reduce((res, curr) => { |
| const params = Object.assign(Object.assign({}, res.params), curr.params); |
| const data = Object.assign(Object.assign({}, res.data), curr.data); |
| const resolve = Object.assign(Object.assign({}, res.resolve), curr._resolvedData); |
| return { params, data, resolve }; |
| }, { params: {}, data: {}, resolve: {} }); |
| } |
| /** |
| * @description |
| * |
| * Contains the information about a route associated with a component loaded in an |
| * outlet at a particular moment in time. ActivatedRouteSnapshot can also be used to |
| * traverse the router state tree. |
| * |
| * The following example initializes a component with route information extracted |
| * from the snapshot of the root node at the time of creation. |
| * |
| * ``` |
| * @Component({templateUrl:'./my-component.html'}) |
| * class MyComponent { |
| * constructor(route: ActivatedRoute) { |
| * const id: string = route.snapshot.params.id; |
| * const url: string = route.snapshot.url.join(''); |
| * const user = route.snapshot.data.user; |
| * } |
| * } |
| * ``` |
| * |
| * @publicApi |
| */ |
| class ActivatedRouteSnapshot { |
| /** @internal */ |
| constructor( |
| /** The URL segments matched by this route */ |
| url, |
| /** |
| * The matrix parameters scoped to this route. |
| * |
| * You can compute all params (or data) in the router state or to get params outside |
| * of an activated component by traversing the `RouterState` tree as in the following |
| * example: |
| * ``` |
| * collectRouteParams(router: Router) { |
| * let params = {}; |
| * let stack: ActivatedRouteSnapshot[] = [router.routerState.snapshot.root]; |
| * while (stack.length > 0) { |
| * const route = stack.pop()!; |
| * params = {...params, ...route.params}; |
| * stack.push(...route.children); |
| * } |
| * return params; |
| * } |
| * ``` |
| */ |
| params, |
| /** The query parameters shared by all the routes */ |
| queryParams, |
| /** The URL fragment shared by all the routes */ |
| fragment, |
| /** The static and resolved data of this route */ |
| data, |
| /** The outlet name of the route */ |
| outlet, |
| /** The component of the route */ |
| component, routeConfig, urlSegment, lastPathIndex, resolve) { |
| this.url = url; |
| this.params = params; |
| this.queryParams = queryParams; |
| this.fragment = fragment; |
| this.data = data; |
| this.outlet = outlet; |
| this.component = component; |
| this.routeConfig = routeConfig; |
| this._urlSegment = urlSegment; |
| this._lastPathIndex = lastPathIndex; |
| this._resolve = resolve; |
| } |
| /** The root of the router state */ |
| get root() { |
| return this._routerState.root; |
| } |
| /** The parent of this route in the router state tree */ |
| get parent() { |
| return this._routerState.parent(this); |
| } |
| /** The first child of this route in the router state tree */ |
| get firstChild() { |
| return this._routerState.firstChild(this); |
| } |
| /** The children of this route in the router state tree */ |
| get children() { |
| return this._routerState.children(this); |
| } |
| /** The path from the root of the router state tree to this route */ |
| get pathFromRoot() { |
| return this._routerState.pathFromRoot(this); |
| } |
| get paramMap() { |
| if (!this._paramMap) { |
| this._paramMap = convertToParamMap(this.params); |
| } |
| return this._paramMap; |
| } |
| get queryParamMap() { |
| if (!this._queryParamMap) { |
| this._queryParamMap = convertToParamMap(this.queryParams); |
| } |
| return this._queryParamMap; |
| } |
| toString() { |
| const url = this.url.map(segment => segment.toString()).join('/'); |
| const matched = this.routeConfig ? this.routeConfig.path : ''; |
| return `Route(url:'${url}', path:'${matched}')`; |
| } |
| } |
| /** |
| * @description |
| * |
| * Represents the state of the router at a moment in time. |
| * |
| * This is a tree of activated route snapshots. Every node in this tree knows about |
| * the "consumed" URL segments, the extracted parameters, and the resolved data. |
| * |
| * The following example shows how a component is initialized with information |
| * from the snapshot of the root node's state at the time of creation. |
| * |
| * ``` |
| * @Component({templateUrl:'template.html'}) |
| * class MyComponent { |
| * constructor(router: Router) { |
| * const state: RouterState = router.routerState; |
| * const snapshot: RouterStateSnapshot = state.snapshot; |
| * const root: ActivatedRouteSnapshot = snapshot.root; |
| * const child = root.firstChild; |
| * const id: Observable<string> = child.params.map(p => p.id); |
| * //... |
| * } |
| * } |
| * ``` |
| * |
| * @publicApi |
| */ |
| class RouterStateSnapshot extends Tree { |
| /** @internal */ |
| constructor( |
| /** The url from which this snapshot was created */ |
| url, root) { |
| super(root); |
| this.url = url; |
| setRouterState(this, root); |
| } |
| toString() { |
| return serializeNode(this._root); |
| } |
| } |
| function setRouterState(state, node) { |
| node.value._routerState = state; |
| node.children.forEach(c => setRouterState(state, c)); |
| } |
| function serializeNode(node) { |
| const c = node.children.length > 0 ? ` { ${node.children.map(serializeNode).join(', ')} } ` : ''; |
| return `${node.value}${c}`; |
| } |
| /** |
| * The expectation is that the activate route is created with the right set of parameters. |
| * So we push new values into the observables only when they are not the initial values. |
| * And we detect that by checking if the snapshot field is set. |
| */ |
| function advanceActivatedRoute(route) { |
| if (route.snapshot) { |
| const currentSnapshot = route.snapshot; |
| const nextSnapshot = route._futureSnapshot; |
| route.snapshot = nextSnapshot; |
| if (!shallowEqual(currentSnapshot.queryParams, nextSnapshot.queryParams)) { |
| route.queryParams.next(nextSnapshot.queryParams); |
| } |
| if (currentSnapshot.fragment !== nextSnapshot.fragment) { |
| route.fragment.next(nextSnapshot.fragment); |
| } |
| if (!shallowEqual(currentSnapshot.params, nextSnapshot.params)) { |
| route.params.next(nextSnapshot.params); |
| } |
| if (!shallowEqualArrays(currentSnapshot.url, nextSnapshot.url)) { |
| route.url.next(nextSnapshot.url); |
| } |
| if (!shallowEqual(currentSnapshot.data, nextSnapshot.data)) { |
| route.data.next(nextSnapshot.data); |
| } |
| } |
| else { |
| route.snapshot = route._futureSnapshot; |
| // this is for resolved data |
| route.data.next(route._futureSnapshot.data); |
| } |
| } |
| function equalParamsAndUrlSegments(a, b) { |
| const equalUrlParams = shallowEqual(a.params, b.params) && equalSegments(a.url, b.url); |
| const parentsMismatch = !a.parent !== !b.parent; |
| return equalUrlParams && !parentsMismatch && |
| (!a.parent || equalParamsAndUrlSegments(a.parent, b.parent)); |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| function createRouterState(routeReuseStrategy, curr, prevState) { |
| const root = createNode(routeReuseStrategy, curr._root, prevState ? prevState._root : undefined); |
| return new RouterState(root, curr); |
| } |
| function createNode(routeReuseStrategy, curr, prevState) { |
| // reuse an activated route that is currently displayed on the screen |
| if (prevState && routeReuseStrategy.shouldReuseRoute(curr.value, prevState.value.snapshot)) { |
| const value = prevState.value; |
| value._futureSnapshot = curr.value; |
| const children = createOrReuseChildren(routeReuseStrategy, curr, prevState); |
| return new TreeNode(value, children); |
| } |
| else { |
| if (routeReuseStrategy.shouldAttach(curr.value)) { |
| // retrieve an activated route that is used to be displayed, but is not currently displayed |
| const detachedRouteHandle = routeReuseStrategy.retrieve(curr.value); |
| if (detachedRouteHandle !== null) { |
| const tree = detachedRouteHandle.route; |
| setFutureSnapshotsOfActivatedRoutes(curr, tree); |
| return tree; |
| } |
| } |
| const value = createActivatedRoute(curr.value); |
| const children = curr.children.map(c => createNode(routeReuseStrategy, c)); |
| return new TreeNode(value, children); |
| } |
| } |
| function setFutureSnapshotsOfActivatedRoutes(curr, result) { |
| if (curr.value.routeConfig !== result.value.routeConfig) { |
| throw new Error('Cannot reattach ActivatedRouteSnapshot created from a different route'); |
| } |
| if (curr.children.length !== result.children.length) { |
| throw new Error('Cannot reattach ActivatedRouteSnapshot with a different number of children'); |
| } |
| result.value._futureSnapshot = curr.value; |
| for (let i = 0; i < curr.children.length; ++i) { |
| setFutureSnapshotsOfActivatedRoutes(curr.children[i], result.children[i]); |
| } |
| } |
| function createOrReuseChildren(routeReuseStrategy, curr, prevState) { |
| return curr.children.map(child => { |
| for (const p of prevState.children) { |
| if (routeReuseStrategy.shouldReuseRoute(child.value, p.value.snapshot)) { |
| return createNode(routeReuseStrategy, child, p); |
| } |
| } |
| return createNode(routeReuseStrategy, child); |
| }); |
| } |
| function createActivatedRoute(c) { |
| 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); |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| function createUrlTree(route, urlTree, commands, queryParams, fragment) { |
| if (commands.length === 0) { |
| return tree(urlTree.root, urlTree.root, urlTree, queryParams, fragment); |
| } |
| const nav = computeNavigation(commands); |
| if (nav.toRoot()) { |
| return tree(urlTree.root, new UrlSegmentGroup([], {}), urlTree, queryParams, fragment); |
| } |
| const startingPosition = findStartingPosition(nav, urlTree, route); |
| const segmentGroup = startingPosition.processChildren ? |
| updateSegmentGroupChildren(startingPosition.segmentGroup, startingPosition.index, nav.commands) : |
| updateSegmentGroup(startingPosition.segmentGroup, startingPosition.index, nav.commands); |
| return tree(startingPosition.segmentGroup, segmentGroup, urlTree, queryParams, fragment); |
| } |
| function isMatrixParams(command) { |
| return typeof command === 'object' && command != null && !command.outlets && !command.segmentPath; |
| } |
| /** |
| * Determines if a given command has an `outlets` map. When we encounter a command |
| * with an outlets k/v map, we need to apply each outlet individually to the existing segment. |
| */ |
| function isCommandWithOutlets(command) { |
| return typeof command === 'object' && command != null && command.outlets; |
| } |
| function tree(oldSegmentGroup, newSegmentGroup, urlTree, queryParams, fragment) { |
| let qp = {}; |
| if (queryParams) { |
| forEach(queryParams, (value, name) => { |
| qp[name] = Array.isArray(value) ? value.map((v) => `${v}`) : `${value}`; |
| }); |
| } |
| if (urlTree.root === oldSegmentGroup) { |
| return new UrlTree(newSegmentGroup, qp, fragment); |
| } |
| return new UrlTree(replaceSegment(urlTree.root, oldSegmentGroup, newSegmentGroup), qp, fragment); |
| } |
| function replaceSegment(current, oldSegment, newSegment) { |
| const children = {}; |
| forEach(current.children, (c, outletName) => { |
| if (c === oldSegment) { |
| children[outletName] = newSegment; |
| } |
| else { |
| children[outletName] = replaceSegment(c, oldSegment, newSegment); |
| } |
| }); |
| return new UrlSegmentGroup(current.segments, children); |
| } |
| class Navigation { |
| constructor(isAbsolute, numberOfDoubleDots, commands) { |
| this.isAbsolute = isAbsolute; |
| this.numberOfDoubleDots = numberOfDoubleDots; |
| this.commands = commands; |
| if (isAbsolute && commands.length > 0 && isMatrixParams(commands[0])) { |
| throw new Error('Root segment cannot have matrix parameters'); |
| } |
| const cmdWithOutlet = commands.find(isCommandWithOutlets); |
| if (cmdWithOutlet && cmdWithOutlet !== last(commands)) { |
| throw new Error('{outlets:{}} has to be the last command'); |
| } |
| } |
| toRoot() { |
| return this.isAbsolute && this.commands.length === 1 && this.commands[0] == '/'; |
| } |
| } |
| /** Transforms commands to a normalized `Navigation` */ |
| function computeNavigation(commands) { |
| if ((typeof commands[0] === 'string') && commands.length === 1 && commands[0] === '/') { |
| return new Navigation(true, 0, commands); |
| } |
| let numberOfDoubleDots = 0; |
| let isAbsolute = false; |
| const res = commands.reduce((res, cmd, cmdIdx) => { |
| if (typeof cmd === 'object' && cmd != null) { |
| if (cmd.outlets) { |
| const outlets = {}; |
| forEach(cmd.outlets, (commands, name) => { |
| outlets[name] = typeof commands === 'string' ? commands.split('/') : commands; |
| }); |
| return [...res, { outlets }]; |
| } |
| if (cmd.segmentPath) { |
| return [...res, cmd.segmentPath]; |
| } |
| } |
| if (!(typeof cmd === 'string')) { |
| return [...res, cmd]; |
| } |
| if (cmdIdx === 0) { |
| cmd.split('/').forEach((urlPart, partIndex) => { |
| if (partIndex == 0 && urlPart === '.') { |
| // skip './a' |
| } |
| else if (partIndex == 0 && urlPart === '') { // '/a' |
| isAbsolute = true; |
| } |
| else if (urlPart === '..') { // '../a' |
| numberOfDoubleDots++; |
| } |
| else if (urlPart != '') { |
| res.push(urlPart); |
| } |
| }); |
| return res; |
| } |
| return [...res, cmd]; |
| }, []); |
| return new Navigation(isAbsolute, numberOfDoubleDots, res); |
| } |
| class Position { |
| constructor(segmentGroup, processChildren, index) { |
| this.segmentGroup = segmentGroup; |
| this.processChildren = processChildren; |
| this.index = index; |
| } |
| } |
| function findStartingPosition(nav, tree, route) { |
| if (nav.isAbsolute) { |
| return new Position(tree.root, true, 0); |
| } |
| if (route.snapshot._lastPathIndex === -1) { |
| const segmentGroup = route.snapshot._urlSegment; |
| // Pathless ActivatedRoute has _lastPathIndex === -1 but should not process children |
| // see issue #26224, #13011, #35687 |
| // However, if the ActivatedRoute is the root we should process children like above. |
| const processChildren = segmentGroup === tree.root; |
| return new Position(segmentGroup, processChildren, 0); |
| } |
| const modifier = isMatrixParams(nav.commands[0]) ? 0 : 1; |
| const index = route.snapshot._lastPathIndex + modifier; |
| return createPositionApplyingDoubleDots(route.snapshot._urlSegment, index, nav.numberOfDoubleDots); |
| } |
| function createPositionApplyingDoubleDots(group, index, numberOfDoubleDots) { |
| let g = group; |
| let ci = index; |
| let dd = numberOfDoubleDots; |
| while (dd > ci) { |
| dd -= ci; |
| g = g.parent; |
| if (!g) { |
| throw new Error('Invalid number of \'../\''); |
| } |
| ci = g.segments.length; |
| } |
| return new Position(g, false, ci - dd); |
| } |
| function getOutlets(commands) { |
| if (isCommandWithOutlets(commands[0])) { |
| return commands[0].outlets; |
| } |
| return { [PRIMARY_OUTLET]: commands }; |
| } |
| function updateSegmentGroup(segmentGroup, startIndex, commands) { |
| if (!segmentGroup) { |
| segmentGroup = new UrlSegmentGroup([], {}); |
| } |
| if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) { |
| return updateSegmentGroupChildren(segmentGroup, startIndex, commands); |
| } |
| const m = prefixedWith(segmentGroup, startIndex, commands); |
| const slicedCommands = commands.slice(m.commandIndex); |
| if (m.match && m.pathIndex < segmentGroup.segments.length) { |
| const g = new UrlSegmentGroup(segmentGroup.segments.slice(0, m.pathIndex), {}); |
| g.children[PRIMARY_OUTLET] = |
| new UrlSegmentGroup(segmentGroup.segments.slice(m.pathIndex), segmentGroup.children); |
| return updateSegmentGroupChildren(g, 0, slicedCommands); |
| } |
| else if (m.match && slicedCommands.length === 0) { |
| return new UrlSegmentGroup(segmentGroup.segments, {}); |
| } |
| else if (m.match && !segmentGroup.hasChildren()) { |
| return createNewSegmentGroup(segmentGroup, startIndex, commands); |
| } |
| else if (m.match) { |
| return updateSegmentGroupChildren(segmentGroup, 0, slicedCommands); |
| } |
| else { |
| return createNewSegmentGroup(segmentGroup, startIndex, commands); |
| } |
| } |
| function updateSegmentGroupChildren(segmentGroup, startIndex, commands) { |
| if (commands.length === 0) { |
| return new UrlSegmentGroup(segmentGroup.segments, {}); |
| } |
| else { |
| const outlets = getOutlets(commands); |
| const children = {}; |
| forEach(outlets, (commands, outlet) => { |
| if (typeof commands === 'string') { |
| commands = [commands]; |
| } |
| if (commands !== null) { |
| children[outlet] = updateSegmentGroup(segmentGroup.children[outlet], startIndex, commands); |
| } |
| }); |
| forEach(segmentGroup.children, (child, childOutlet) => { |
| if (outlets[childOutlet] === undefined) { |
| children[childOutlet] = child; |
| } |
| }); |
| return new UrlSegmentGroup(segmentGroup.segments, children); |
| } |
| } |
| function prefixedWith(segmentGroup, startIndex, commands) { |
| let currentCommandIndex = 0; |
| let currentPathIndex = startIndex; |
| const noMatch = { match: false, pathIndex: 0, commandIndex: 0 }; |
| while (currentPathIndex < segmentGroup.segments.length) { |
| if (currentCommandIndex >= commands.length) |
| return noMatch; |
| const path = segmentGroup.segments[currentPathIndex]; |
| const command = commands[currentCommandIndex]; |
| // Do not try to consume command as part of the prefixing if it has outlets because it can |
| // contain outlets other than the one being processed. Consuming the outlets command would |
| // result in other outlets being ignored. |
| if (isCommandWithOutlets(command)) { |
| break; |
| } |
| const curr = `${command}`; |
| const next = currentCommandIndex < commands.length - 1 ? commands[currentCommandIndex + 1] : null; |
| if (currentPathIndex > 0 && curr === undefined) |
| break; |
| if (curr && next && (typeof next === 'object') && next.outlets === undefined) { |
| if (!compare(curr, next, path)) |
| return noMatch; |
| currentCommandIndex += 2; |
| } |
| else { |
| if (!compare(curr, {}, path)) |
| return noMatch; |
| currentCommandIndex++; |
| } |
| currentPathIndex++; |
| } |
| return { match: true, pathIndex: currentPathIndex, commandIndex: currentCommandIndex }; |
| } |
| function createNewSegmentGroup(segmentGroup, startIndex, commands) { |
| const paths = segmentGroup.segments.slice(0, startIndex); |
| let i = 0; |
| while (i < commands.length) { |
| const command = commands[i]; |
| if (isCommandWithOutlets(command)) { |
| const children = createNewSegmentChildren(command.outlets); |
| return new UrlSegmentGroup(paths, children); |
| } |
| // if we start with an object literal, we need to reuse the path part from the segment |
| if (i === 0 && isMatrixParams(commands[0])) { |
| const p = segmentGroup.segments[startIndex]; |
| paths.push(new UrlSegment(p.path, stringify(commands[0]))); |
| i++; |
| continue; |
| } |
| const curr = isCommandWithOutlets(command) ? command.outlets[PRIMARY_OUTLET] : `${command}`; |
| const next = (i < commands.length - 1) ? commands[i + 1] : null; |
| if (curr && next && isMatrixParams(next)) { |
| paths.push(new UrlSegment(curr, stringify(next))); |
| i += 2; |
| } |
| else { |
| paths.push(new UrlSegment(curr, {})); |
| i++; |
| } |
| } |
| return new UrlSegmentGroup(paths, {}); |
| } |
| function createNewSegmentChildren(outlets) { |
| const children = {}; |
| forEach(outlets, (commands, outlet) => { |
| if (typeof commands === 'string') { |
| commands = [commands]; |
| } |
| if (commands !== null) { |
| children[outlet] = createNewSegmentGroup(new UrlSegmentGroup([], {}), 0, commands); |
| } |
| }); |
| return children; |
| } |
| function stringify(params) { |
| const res = {}; |
| forEach(params, (v, k) => res[k] = `${v}`); |
| return res; |
| } |
| function compare(path, params, segment) { |
| return path == segment.path && shallowEqual(params, segment.parameters); |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| const activateRoutes = (rootContexts, routeReuseStrategy, forwardEvent) => map(t => { |
| new ActivateRoutes(routeReuseStrategy, t.targetRouterState, t.currentRouterState, forwardEvent) |
| .activate(rootContexts); |
| return t; |
| }); |
| class ActivateRoutes { |
| constructor(routeReuseStrategy, futureState, currState, forwardEvent) { |
| this.routeReuseStrategy = routeReuseStrategy; |
| this.futureState = futureState; |
| this.currState = currState; |
| this.forwardEvent = forwardEvent; |
| } |
| activate(parentContexts) { |
| const futureRoot = this.futureState._root; |
| const currRoot = this.currState ? this.currState._root : null; |
| this.deactivateChildRoutes(futureRoot, currRoot, parentContexts); |
| advanceActivatedRoute(this.futureState.root); |
| this.activateChildRoutes(futureRoot, currRoot, parentContexts); |
| } |
| // De-activate the child route that are not re-used for the future state |
| deactivateChildRoutes(futureNode, currNode, contexts) { |
| const children = nodeChildrenAsMap(currNode); |
| // Recurse on the routes active in the future state to de-activate deeper children |
| futureNode.children.forEach(futureChild => { |
| const childOutletName = futureChild.value.outlet; |
| this.deactivateRoutes(futureChild, children[childOutletName], contexts); |
| delete children[childOutletName]; |
| }); |
| // De-activate the routes that will not be re-used |
| forEach(children, (v, childName) => { |
| this.deactivateRouteAndItsChildren(v, contexts); |
| }); |
| } |
| deactivateRoutes(futureNode, currNode, parentContext) { |
| const future = futureNode.value; |
| const curr = currNode ? currNode.value : null; |
| if (future === curr) { |
| // Reusing the node, check to see if the children need to be de-activated |
| if (future.component) { |
| // If we have a normal route, we need to go through an outlet. |
| const context = parentContext.getContext(future.outlet); |
| if (context) { |
| this.deactivateChildRoutes(futureNode, currNode, context.children); |
| } |
| } |
| else { |
| // if we have a componentless route, we recurse but keep the same outlet map. |
| this.deactivateChildRoutes(futureNode, currNode, parentContext); |
| } |
| } |
| else { |
| if (curr) { |
| // Deactivate the current route which will not be re-used |
| this.deactivateRouteAndItsChildren(currNode, parentContext); |
| } |
| } |
| } |
| deactivateRouteAndItsChildren(route, parentContexts) { |
| if (this.routeReuseStrategy.shouldDetach(route.value.snapshot)) { |
| this.detachAndStoreRouteSubtree(route, parentContexts); |
| } |
| else { |
| this.deactivateRouteAndOutlet(route, parentContexts); |
| } |
| } |
| detachAndStoreRouteSubtree(route, parentContexts) { |
| const context = parentContexts.getContext(route.value.outlet); |
| if (context && context.outlet) { |
| const componentRef = context.outlet.detach(); |
| const contexts = context.children.onOutletDeactivated(); |
| this.routeReuseStrategy.store(route.value.snapshot, { componentRef, route, contexts }); |
| } |
| } |
| deactivateRouteAndOutlet(route, parentContexts) { |
| const context = parentContexts.getContext(route.value.outlet); |
| // The context could be `null` if we are on a componentless route but there may still be |
| // children that need deactivating. |
| const contexts = context && route.value.component ? context.children : parentContexts; |
| const children = nodeChildrenAsMap(route); |
| for (const childOutlet of Object.keys(children)) { |
| this.deactivateRouteAndItsChildren(children[childOutlet], contexts); |
| } |
| if (context && context.outlet) { |
| // Destroy the component |
| context.outlet.deactivate(); |
| // Destroy the contexts for all the outlets that were in the component |
| context.children.onOutletDeactivated(); |
| // Clear the information about the attached component on the context but keep the reference to |
| // the outlet. |
| context.attachRef = null; |
| context.resolver = null; |
| context.route = null; |
| } |
| } |
| activateChildRoutes(futureNode, currNode, contexts) { |
| const children = nodeChildrenAsMap(currNode); |
| futureNode.children.forEach(c => { |
| this.activateRoutes(c, children[c.value.outlet], contexts); |
| this.forwardEvent(new ActivationEnd(c.value.snapshot)); |
| }); |
| if (futureNode.children.length) { |
| this.forwardEvent(new ChildActivationEnd(futureNode.value.snapshot)); |
| } |
| } |
| activateRoutes(futureNode, currNode, parentContexts) { |
| const future = futureNode.value; |
| const curr = currNode ? currNode.value : null; |
| advanceActivatedRoute(future); |
| // reusing the node |
| if (future === curr) { |
| if (future.component) { |
| // If we have a normal route, we need to go through an outlet. |
| const context = parentContexts.getOrCreateContext(future.outlet); |
| this.activateChildRoutes(futureNode, currNode, context.children); |
| } |
| else { |
| // if we have a componentless route, we recurse but keep the same outlet map. |
| this.activateChildRoutes(futureNode, currNode, parentContexts); |
| } |
| } |
| else { |
| if (future.component) { |
| // if we have a normal route, we need to place the component into the outlet and recurse. |
| const context = parentContexts.getOrCreateContext(future.outlet); |
| if (this.routeReuseStrategy.shouldAttach(future.snapshot)) { |
| const stored = this.routeReuseStrategy.retrieve(future.snapshot); |
| this.routeReuseStrategy.store(future.snapshot, null); |
| context.children.onOutletReAttached(stored.contexts); |
| context.attachRef = stored.componentRef; |
| context.route = stored.route.value; |
| if (context.outlet) { |
| // Attach right away when the outlet has already been instantiated |
| // Otherwise attach from `RouterOutlet.ngOnInit` when it is instantiated |
| context.outlet.attach(stored.componentRef, stored.route.value); |
| } |
| advanceActivatedRouteNodeAndItsChildren(stored.route); |
| } |
| else { |
| const config = parentLoadedConfig(future.snapshot); |
| const cmpFactoryResolver = config ? config.module.componentFactoryResolver : null; |
| context.attachRef = null; |
| context.route = future; |
| context.resolver = cmpFactoryResolver; |
| if (context.outlet) { |
| // Activate the outlet when it has already been instantiated |
| // Otherwise it will get activated from its `ngOnInit` when instantiated |
| context.outlet.activateWith(future, cmpFactoryResolver); |
| } |
| this.activateChildRoutes(futureNode, null, context.children); |
| } |
| } |
| else { |
| // if we have a componentless route, we recurse but keep the same outlet map. |
| this.activateChildRoutes(futureNode, null, parentContexts); |
| } |
| } |
| } |
| } |
| function advanceActivatedRouteNodeAndItsChildren(node) { |
| advanceActivatedRoute(node.value); |
| node.children.forEach(advanceActivatedRouteNodeAndItsChildren); |
| } |
| function parentLoadedConfig(snapshot) { |
| for (let s = snapshot.parent; s; s = s.parent) { |
| const route = s.routeConfig; |
| if (route && route._loadedConfig) |
| return route._loadedConfig; |
| if (route && route.component) |
| return null; |
| } |
| return null; |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| class LoadedRouterConfig { |
| constructor(routes, module) { |
| this.routes = routes; |
| this.module = module; |
| } |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * Simple function check, but generic so type inference will flow. Example: |
| * |
| * function product(a: number, b: number) { |
| * return a * b; |
| * } |
| * |
| * if (isFunction<product>(fn)) { |
| * return fn(1, 2); |
| * } else { |
| * throw "Must provide the `product` function"; |
| * } |
| */ |
| function isFunction(v) { |
| return typeof v === 'function'; |
| } |
| function isBoolean(v) { |
| return typeof v === 'boolean'; |
| } |
| function isUrlTree(v) { |
| return v instanceof UrlTree; |
| } |
| function isCanLoad(guard) { |
| return guard && isFunction(guard.canLoad); |
| } |
| function isCanActivate(guard) { |
| return guard && isFunction(guard.canActivate); |
| } |
| function isCanActivateChild(guard) { |
| return guard && isFunction(guard.canActivateChild); |
| } |
| function isCanDeactivate(guard) { |
| return guard && isFunction(guard.canDeactivate); |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| const INITIAL_VALUE = Symbol('INITIAL_VALUE'); |
| function prioritizedGuardValue() { |
| return switchMap(obs => { |
| return combineLatest(obs.map(o => o.pipe(take(1), startWith(INITIAL_VALUE)))) |
| .pipe(scan((acc, list) => { |
| let isPending = false; |
| return list.reduce((innerAcc, val, i) => { |
| if (innerAcc !== INITIAL_VALUE) |
| return innerAcc; |
| // Toggle pending flag if any values haven't been set yet |
| if (val === INITIAL_VALUE) |
| isPending = true; |
| // Any other return values are only valid if we haven't yet hit a pending |
| // call. This guarantees that in the case of a guard at the bottom of the |
| // tree that returns a redirect, we will wait for the higher priority |
| // guard at the top to finish before performing the redirect. |
| if (!isPending) { |
| // Early return when we hit a `false` value as that should always |
| // cancel navigation |
| if (val === false) |
| return val; |
| if (i === list.length - 1 || isUrlTree(val)) { |
| return val; |
| } |
| } |
| return innerAcc; |
| }, acc); |
| }, INITIAL_VALUE), filter(item => item !== INITIAL_VALUE), map(item => isUrlTree(item) ? item : item === true), // |
| take(1)); |
| }); |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * This component is used internally within the router to be a placeholder when an empty |
| * router-outlet is needed. For example, with a config such as: |
| * |
| * `{path: 'parent', outlet: 'nav', children: [...]}` |
| * |
| * In order to render, there needs to be a component on this config, which will default |
| * to this `EmptyOutletComponent`. |
| */ |
| class ɵEmptyOutletComponent { |
| } |
| ɵEmptyOutletComponent.decorators = [ |
| { type: Component, args: [{ template: `<router-outlet></router-outlet>` },] } |
| ]; |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| function validateConfig(config, parentPath = '') { |
| // forEach doesn't iterate undefined values |
| for (let i = 0; i < config.length; i++) { |
| const route = config[i]; |
| const fullPath = getFullPath(parentPath, route); |
| validateNode(route, fullPath); |
| } |
| } |
| function validateNode(route, fullPath) { |
| if (typeof ngDevMode === 'undefined' || ngDevMode) { |
| if (!route) { |
| throw new Error(` |
| Invalid configuration of route '${fullPath}': Encountered undefined route. |
| The reason might be an extra comma. |
| |
| Example: |
| const routes: Routes = [ |
| { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, |
| { path: 'dashboard', component: DashboardComponent },, << two commas |
| { path: 'detail/:id', component: HeroDetailComponent } |
| ]; |
| `); |
| } |
| if (Array.isArray(route)) { |
| throw new Error(`Invalid configuration of route '${fullPath}': Array cannot be specified`); |
| } |
| if (!route.component && !route.children && !route.loadChildren && |
| (route.outlet && route.outlet !== PRIMARY_OUTLET)) { |
| throw new Error(`Invalid configuration of route '${fullPath}': a componentless route without children or loadChildren cannot have a named outlet set`); |
| } |
| if (route.redirectTo && route.children) { |
| throw new Error(`Invalid configuration of route '${fullPath}': redirectTo and children cannot be used together`); |
| } |
| if (route.redirectTo && route.loadChildren) { |
| throw new Error(`Invalid configuration of route '${fullPath}': redirectTo and loadChildren cannot be used together`); |
| } |
| if (route.children && route.loadChildren) { |
| throw new Error(`Invalid configuration of route '${fullPath}': children and loadChildren cannot be used together`); |
| } |
| if (route.redirectTo && route.component) { |
| throw new Error(`Invalid configuration of route '${fullPath}': redirectTo and component cannot be used together`); |
| } |
| if (route.redirectTo && route.canActivate) { |
| throw new Error(`Invalid configuration of route '${fullPath}': redirectTo and canActivate cannot be used together. Redirects happen before activation ` + |
| `so canActivate will never be executed.`); |
| } |
| if (route.path && route.matcher) { |
| throw new Error(`Invalid configuration of route '${fullPath}': path and matcher cannot be used together`); |
| } |
| if (route.redirectTo === void 0 && !route.component && !route.children && !route.loadChildren) { |
| throw new Error(`Invalid configuration of route '${fullPath}'. One of the following must be provided: component, redirectTo, children or loadChildren`); |
| } |
| if (route.path === void 0 && route.matcher === void 0) { |
| throw new Error(`Invalid configuration of route '${fullPath}': routes must have either a path or a matcher specified`); |
| } |
| if (typeof route.path === 'string' && route.path.charAt(0) === '/') { |
| throw new Error(`Invalid configuration of route '${fullPath}': path cannot start with a slash`); |
| } |
| if (route.path === '' && route.redirectTo !== void 0 && route.pathMatch === void 0) { |
| const exp = `The default value of 'pathMatch' is 'prefix', but often the intent is to use 'full'.`; |
| throw new Error(`Invalid configuration of route '{path: "${fullPath}", redirectTo: "${route.redirectTo}"}': please provide 'pathMatch'. ${exp}`); |
| } |
| if (route.pathMatch !== void 0 && route.pathMatch !== 'full' && route.pathMatch !== 'prefix') { |
| throw new Error(`Invalid configuration of route '${fullPath}': pathMatch can only be set to 'prefix' or 'full'`); |
| } |
| } |
| if (route.children) { |
| validateConfig(route.children, fullPath); |
| } |
| } |
| function getFullPath(parentPath, currentRoute) { |
| if (!currentRoute) { |
| return parentPath; |
| } |
| if (!parentPath && !currentRoute.path) { |
| return ''; |
| } |
| else if (parentPath && !currentRoute.path) { |
| return `${parentPath}/`; |
| } |
| else if (!parentPath && currentRoute.path) { |
| return currentRoute.path; |
| } |
| else { |
| return `${parentPath}/${currentRoute.path}`; |
| } |
| } |
| /** |
| * Makes a copy of the config and adds any default required properties. |
| */ |
| function standardizeConfig(r) { |
| const children = r.children && r.children.map(standardizeConfig); |
| const c = children ? Object.assign(Object.assign({}, r), { children }) : Object.assign({}, r); |
| if (!c.component && (children || c.loadChildren) && (c.outlet && c.outlet !== PRIMARY_OUTLET)) { |
| c.component = ɵEmptyOutletComponent; |
| } |
| return c; |
| } |
| /** Returns the `route.outlet` or PRIMARY_OUTLET if none exists. */ |
| function getOutlet(route) { |
| return route.outlet || PRIMARY_OUTLET; |
| } |
| /** |
| * Sorts the `routes` such that the ones with an outlet matching `outletName` come first. |
| * The order of the configs is otherwise preserved. |
| */ |
| function sortByMatchingOutlets(routes, outletName) { |
| const sortedConfig = routes.filter(r => getOutlet(r) === outletName); |
| sortedConfig.push(...routes.filter(r => getOutlet(r) !== outletName)); |
| return sortedConfig; |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| const noMatch = { |
| matched: false, |
| consumedSegments: [], |
| lastChild: 0, |
| parameters: {}, |
| positionalParamSegments: {} |
| }; |
| function match(segmentGroup, route, segments) { |
| var _a; |
| if (route.path === '') { |
| if (route.pathMatch === 'full' && (segmentGroup.hasChildren() || segments.length > 0)) { |
| return Object.assign({}, noMatch); |
| } |
| return { |
| matched: true, |
| consumedSegments: [], |
| lastChild: 0, |
| parameters: {}, |
| positionalParamSegments: {} |
| }; |
| } |
| const matcher = route.matcher || defaultUrlMatcher; |
| const res = matcher(segments, segmentGroup, route); |
| if (!res) |
| return Object.assign({}, noMatch); |
| const posParams = {}; |
| forEach(res.posParams, (v, k) => { |
| posParams[k] = v.path; |
| }); |
| const parameters = res.consumed.length > 0 ? Object.assign(Object.assign({}, posParams), res.consumed[res.consumed.length - 1].parameters) : |
| posParams; |
| return { |
| matched: true, |
| consumedSegments: res.consumed, |
| lastChild: res.consumed.length, |
| // TODO(atscott): investigate combining parameters and positionalParamSegments |
| parameters, |
| positionalParamSegments: (_a = res.posParams) !== null && _a !== void 0 ? _a : {} |
| }; |
| } |
| function split(segmentGroup, consumedSegments, slicedSegments, config, relativeLinkResolution = 'corrected') { |
| if (slicedSegments.length > 0 && |
| containsEmptyPathMatchesWithNamedOutlets(segmentGroup, slicedSegments, config)) { |
| const s = new UrlSegmentGroup(consumedSegments, createChildrenForEmptyPaths(segmentGroup, consumedSegments, config, new UrlSegmentGroup(slicedSegments, segmentGroup.children))); |
| s._sourceSegment = segmentGroup; |
| s._segmentIndexShift = consumedSegments.length; |
| return { segmentGroup: s, slicedSegments: [] }; |
| } |
| if (slicedSegments.length === 0 && |
| containsEmptyPathMatches(segmentGroup, slicedSegments, config)) { |
| const s = new UrlSegmentGroup(segmentGroup.segments, addEmptyPathsToChildrenIfNeeded(segmentGroup, consumedSegments, slicedSegments, config, segmentGroup.children, relativeLinkResolution)); |
| s._sourceSegment = segmentGroup; |
| s._segmentIndexShift = consumedSegments.length; |
| return { segmentGroup: s, slicedSegments }; |
| } |
| const s = new UrlSegmentGroup(segmentGroup.segments, segmentGroup.children); |
| s._sourceSegment = segmentGroup; |
| s._segmentIndexShift = consumedSegments.length; |
| return { segmentGroup: s, slicedSegments }; |
| } |
| function addEmptyPathsToChildrenIfNeeded(segmentGroup, consumedSegments, slicedSegments, routes, children, relativeLinkResolution) { |
| const res = {}; |
| for (const r of routes) { |
| if (emptyPathMatch(segmentGroup, slicedSegments, r) && !children[getOutlet(r)]) { |
| const s = new UrlSegmentGroup([], {}); |
| s._sourceSegment = segmentGroup; |
| if (relativeLinkResolution === 'legacy') { |
| s._segmentIndexShift = segmentGroup.segments.length; |
| } |
| else { |
| s._segmentIndexShift = consumedSegments.length; |
| } |
| res[getOutlet(r)] = s; |
| } |
| } |
| return Object.assign(Object.assign({}, children), res); |
| } |
| function createChildrenForEmptyPaths(segmentGroup, consumedSegments, routes, primarySegment) { |
| const res = {}; |
| res[PRIMARY_OUTLET] = primarySegment; |
| primarySegment._sourceSegment = segmentGroup; |
| primarySegment._segmentIndexShift = consumedSegments.length; |
| for (const r of routes) { |
| if (r.path === '' && getOutlet(r) !== PRIMARY_OUTLET) { |
| const s = new UrlSegmentGroup([], {}); |
| s._sourceSegment = segmentGroup; |
| s._segmentIndexShift = consumedSegments.length; |
| res[getOutlet(r)] = s; |
| } |
| } |
| return res; |
| } |
| function containsEmptyPathMatchesWithNamedOutlets(segmentGroup, slicedSegments, routes) { |
| return routes.some(r => emptyPathMatch(segmentGroup, slicedSegments, r) && getOutlet(r) !== PRIMARY_OUTLET); |
| } |
| function containsEmptyPathMatches(segmentGroup, slicedSegments, routes) { |
| return routes.some(r => emptyPathMatch(segmentGroup, slicedSegments, r)); |
| } |
| function emptyPathMatch(segmentGroup, slicedSegments, r) { |
| if ((segmentGroup.hasChildren() || slicedSegments.length > 0) && r.pathMatch === 'full') { |
| return false; |
| } |
| return r.path === ''; |
| } |
| /** |
| * Determines if `route` is a path match for the `rawSegment`, `segments`, and `outlet` without |
| * verifying that its children are a full match for the remainder of the `rawSegment` children as |
| * well. |
| */ |
| function isImmediateMatch(route, rawSegment, segments, outlet) { |
| // We allow matches to empty paths when the outlets differ so we can match a url like `/(b:b)` to |
| // a config like |
| // * `{path: '', children: [{path: 'b', outlet: 'b'}]}` |
| // or even |
| // * `{path: '', outlet: 'a', children: [{path: 'b', outlet: 'b'}]` |
| // |
| // The exception here is when the segment outlet is for the primary outlet. This would |
| // result in a match inside the named outlet because all children there are written as primary |
| // outlets. So we need to prevent child named outlet matches in a url like `/b` in a config like |
| // * `{path: '', outlet: 'x' children: [{path: 'b'}]}` |
| // This should only match if the url is `/(x:b)`. |
| if (getOutlet(route) !== outlet && |
| (outlet === PRIMARY_OUTLET || !emptyPathMatch(rawSegment, segments, route))) { |
| return false; |
| } |
| if (route.path === '**') { |
| return true; |
| } |
| return match(rawSegment, route, segments).matched; |
| } |
| function noLeftoversInUrl(segmentGroup, segments, outlet) { |
| return segments.length === 0 && !segmentGroup.children[outlet]; |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| class NoMatch { |
| constructor(segmentGroup) { |
| this.segmentGroup = segmentGroup || null; |
| } |
| } |
| class AbsoluteRedirect { |
| constructor(urlTree) { |
| this.urlTree = urlTree; |
| } |
| } |
| function noMatch$1(segmentGroup) { |
| return new Observable((obs) => obs.error(new NoMatch(segmentGroup))); |
| } |
| function absoluteRedirect(newTree) { |
| return new Observable((obs) => obs.error(new AbsoluteRedirect(newTree))); |
| } |
| function namedOutletsRedirect(redirectTo) { |
| return new Observable((obs) => obs.error(new Error(`Only absolute redirects can have named outlets. redirectTo: '${redirectTo}'`))); |
| } |
| function canLoadFails(route) { |
| return new Observable((obs) => obs.error(navigationCancelingError(`Cannot load children because the guard of the route "path: '${route.path}'" returned false`))); |
| } |
| /** |
| * Returns the `UrlTree` with the redirection applied. |
| * |
| * Lazy modules are loaded along the way. |
| */ |
| function applyRedirects(moduleInjector, configLoader, urlSerializer, urlTree, config) { |
| return new ApplyRedirects(moduleInjector, configLoader, urlSerializer, urlTree, config).apply(); |
| } |
| class ApplyRedirects { |
| constructor(moduleInjector, configLoader, urlSerializer, urlTree, config) { |
| this.configLoader = configLoader; |
| this.urlSerializer = urlSerializer; |
| this.urlTree = urlTree; |
| this.config = config; |
| this.allowRedirects = true; |
| this.ngModule = moduleInjector.get(NgModuleRef); |
| } |
| apply() { |
| const splitGroup = split(this.urlTree.root, [], [], this.config).segmentGroup; |
| // TODO(atscott): creating a new segment removes the _sourceSegment _segmentIndexShift, which is |
| // only necessary to prevent failures in tests which assert exact object matches. The `split` is |
| // now shared between `applyRedirects` and `recognize` but only the `recognize` step needs these |
| // properties. Before the implementations were merged, the `applyRedirects` would not assign |
| // them. We should be able to remove this logic as a "breaking change" but should do some more |
| // investigation into the failures first. |
| const rootSegmentGroup = new UrlSegmentGroup(splitGroup.segments, splitGroup.children); |
| const expanded$ = this.expandSegmentGroup(this.ngModule, this.config, rootSegmentGroup, PRIMARY_OUTLET); |
| const urlTrees$ = expanded$.pipe(map((rootSegmentGroup) => { |
| return this.createUrlTree(squashSegmentGroup(rootSegmentGroup), this.urlTree.queryParams, this.urlTree.fragment); |
| })); |
| return urlTrees$.pipe(catchError((e) => { |
| if (e instanceof AbsoluteRedirect) { |
| // after an absolute redirect we do not apply any more redirects! |
| this.allowRedirects = false; |
| // we need to run matching, so we can fetch all lazy-loaded modules |
| return this.match(e.urlTree); |
| } |
| if (e instanceof NoMatch) { |
| throw this.noMatchError(e); |
| } |
| throw e; |
| })); |
| } |
| match(tree) { |
| const expanded$ = this.expandSegmentGroup(this.ngModule, this.config, tree.root, PRIMARY_OUTLET); |
| const mapped$ = expanded$.pipe(map((rootSegmentGroup) => { |
| return this.createUrlTree(squashSegmentGroup(rootSegmentGroup), tree.queryParams, tree.fragment); |
| })); |
| return mapped$.pipe(catchError((e) => { |
| if (e instanceof NoMatch) { |
| throw this.noMatchError(e); |
| } |
| throw e; |
| })); |
| } |
| noMatchError(e) { |
| return new Error(`Cannot match any routes. URL Segment: '${e.segmentGroup}'`); |
| } |
| createUrlTree(rootCandidate, queryParams, fragment) { |
| const root = rootCandidate.segments.length > 0 ? |
| new UrlSegmentGroup([], { [PRIMARY_OUTLET]: rootCandidate }) : |
| rootCandidate; |
| return new UrlTree(root, queryParams, fragment); |
| } |
| expandSegmentGroup(ngModule, routes, segmentGroup, outlet) { |
| if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) { |
| return this.expandChildren(ngModule, routes, segmentGroup) |
| .pipe(map((children) => new UrlSegmentGroup([], children))); |
| } |
| return this.expandSegment(ngModule, segmentGroup, routes, segmentGroup.segments, outlet, true); |
| } |
| // Recursively expand segment groups for all the child outlets |
| expandChildren(ngModule, routes, segmentGroup) { |
| // Expand outlets one at a time, starting with the primary outlet. We need to do it this way |
| // because an absolute redirect from the primary outlet takes precedence. |
| const childOutlets = []; |
| for (const child of Object.keys(segmentGroup.children)) { |
| if (child === 'primary') { |
| childOutlets.unshift(child); |
| } |
| else { |
| childOutlets.push(child); |
| } |
| } |
| return from(childOutlets) |
| .pipe(concatMap(childOutlet => { |
| const child = segmentGroup.children[childOutlet]; |
| // Sort the routes so routes with outlets that match the segment appear |
| // first, followed by routes for other outlets, which might match if they have an |
| // empty path. |
| const sortedRoutes = sortByMatchingOutlets(routes, childOutlet); |
| return this.expandSegmentGroup(ngModule, sortedRoutes, child, childOutlet) |
| .pipe(map(s => ({ segment: s, outlet: childOutlet }))); |
| }), scan((children, expandedChild) => { |
| children[expandedChild.outlet] = expandedChild.segment; |
| return children; |
| }, {}), last$1()); |
| } |
| expandSegment(ngModule, segmentGroup, routes, segments, outlet, allowRedirects) { |
| return from(routes).pipe(concatMap((r) => { |
| const expanded$ = this.expandSegmentAgainstRoute(ngModule, segmentGroup, routes, r, segments, outlet, allowRedirects); |
| return expanded$.pipe(catchError((e) => { |
| if (e instanceof NoMatch) { |
| return of(null); |
| } |
| throw e; |
| })); |
| }), first((s) => !!s), catchError((e, _) => { |
| if (e instanceof EmptyError || e.name === 'EmptyError') { |
| if (noLeftoversInUrl(segmentGroup, segments, outlet)) { |
| return of(new UrlSegmentGroup([], {})); |
| } |
| throw new NoMatch(segmentGroup); |
| } |
| throw e; |
| })); |
| } |
| expandSegmentAgainstRoute(ngModule, segmentGroup, routes, route, paths, outlet, allowRedirects) { |
| if (!isImmediateMatch(route, segmentGroup, paths, outlet)) { |
| return noMatch$1(segmentGroup); |
| } |
| if (route.redirectTo === undefined) { |
| return this.matchSegmentAgainstRoute(ngModule, segmentGroup, route, paths, outlet); |
| } |
| if (allowRedirects && this.allowRedirects) { |
| return this.expandSegmentAgainstRouteUsingRedirect(ngModule, segmentGroup, routes, route, paths, outlet); |
| } |
| return noMatch$1(segmentGroup); |
| } |
| expandSegmentAgainstRouteUsingRedirect(ngModule, segmentGroup, routes, route, segments, outlet) { |
| if (route.path === '**') { |
| return this.expandWildCardWithParamsAgainstRouteUsingRedirect(ngModule, routes, route, outlet); |
| } |
| return this.expandRegularSegmentAgainstRouteUsingRedirect(ngModule, segmentGroup, routes, route, segments, outlet); |
| } |
| expandWildCardWithParamsAgainstRouteUsingRedirect(ngModule, routes, route, outlet) { |
| const newTree = this.applyRedirectCommands([], route.redirectTo, {}); |
| if (route.redirectTo.startsWith('/')) { |
| return absoluteRedirect(newTree); |
| } |
| return this.lineralizeSegments(route, newTree).pipe(mergeMap((newSegments) => { |
| const group = new UrlSegmentGroup(newSegments, {}); |
| return this.expandSegment(ngModule, group, routes, newSegments, outlet, false); |
| })); |
| } |
| expandRegularSegmentAgainstRouteUsingRedirect(ngModule, segmentGroup, routes, route, segments, outlet) { |
| const { matched, consumedSegments, lastChild, positionalParamSegments } = match(segmentGroup, route, segments); |
| if (!matched) |
| return noMatch$1(segmentGroup); |
| const newTree = this.applyRedirectCommands(consumedSegments, route.redirectTo, positionalParamSegments); |
| if (route.redirectTo.startsWith('/')) { |
| return absoluteRedirect(newTree); |
| } |
| return this.lineralizeSegments(route, newTree).pipe(mergeMap((newSegments) => { |
| return this.expandSegment(ngModule, segmentGroup, routes, newSegments.concat(segments.slice(lastChild)), outlet, false); |
| })); |
| } |
| matchSegmentAgainstRoute(ngModule, rawSegmentGroup, route, segments, outlet) { |
| if (route.path === '**') { |
| if (route.loadChildren) { |
| const loaded$ = route._loadedConfig ? of(route._loadedConfig) : |
| this.configLoader.load(ngModule.injector, route); |
| return loaded$.pipe(map((cfg) => { |
| route._loadedConfig = cfg; |
| return new UrlSegmentGroup(segments, {}); |
| })); |
| } |
| return of(new UrlSegmentGroup(segments, {})); |
| } |
| const { matched, consumedSegments, lastChild } = match(rawSegmentGroup, route, segments); |
| if (!matched) |
| return noMatch$1(rawSegmentGroup); |
| const rawSlicedSegments = segments.slice(lastChild); |
| const childConfig$ = this.getChildConfig(ngModule, route, segments); |
| return childConfig$.pipe(mergeMap((routerConfig) => { |
| const childModule = routerConfig.module; |
| const childConfig = routerConfig.routes; |
| const { segmentGroup: splitSegmentGroup, slicedSegments } = split(rawSegmentGroup, consumedSegments, rawSlicedSegments, childConfig); |
| // See comment on the other call to `split` about why this is necessary. |
| const segmentGroup = new UrlSegmentGroup(splitSegmentGroup.segments, splitSegmentGroup.children); |
| if (slicedSegments.length === 0 && segmentGroup.hasChildren()) { |
| const expanded$ = this.expandChildren(childModule, childConfig, segmentGroup); |
| return expanded$.pipe(map((children) => new UrlSegmentGroup(consumedSegments, children))); |
| } |
| if (childConfig.length === 0 && slicedSegments.length === 0) { |
| return of(new UrlSegmentGroup(consumedSegments, {})); |
| } |
| const matchedOnOutlet = getOutlet(route) === outlet; |
| const expanded$ = this.expandSegment(childModule, segmentGroup, childConfig, slicedSegments, matchedOnOutlet ? PRIMARY_OUTLET : outlet, true); |
| return expanded$.pipe(map((cs) => new UrlSegmentGroup(consumedSegments.concat(cs.segments), cs.children))); |
| })); |
| } |
| getChildConfig(ngModule, route, segments) { |
| if (route.children) { |
| // The children belong to the same module |
| return of(new LoadedRouterConfig(route.children, ngModule)); |
| } |
| if (route.loadChildren) { |
| // lazy children belong to the loaded module |
| if (route._loadedConfig !== undefined) { |
| return of(route._loadedConfig); |
| } |
| return this.runCanLoadGuards(ngModule.injector, route, segments) |
| .pipe(mergeMap((shouldLoadResult) => { |
| if (shouldLoadResult) { |
| return this.configLoader.load(ngModule.injector, route) |
| .pipe(map((cfg) => { |
| route._loadedConfig = cfg; |
| return cfg; |
| })); |
| } |
| return canLoadFails(route); |
| })); |
| } |
| return of(new LoadedRouterConfig([], ngModule)); |
| } |
| runCanLoadGuards(moduleInjector, route, segments) { |
| const canLoad = route.canLoad; |
| if (!canLoad || canLoad.length === 0) |
| return of(true); |
| const canLoadObservables = canLoad.map((injectionToken) => { |
| const guard = moduleInjector.get(injectionToken); |
| let guardVal; |
| if (isCanLoad(guard)) { |
| guardVal = guard.canLoad(route, segments); |
| } |
| else if (isFunction(guard)) { |
| guardVal = guard(route, segments); |
| } |
| else { |
| throw new Error('Invalid CanLoad guard'); |
| } |
| return wrapIntoObservable(guardVal); |
| }); |
| return of(canLoadObservables) |
| .pipe(prioritizedGuardValue(), tap((result) => { |
| if (!isUrlTree(result)) |
| return; |
| const error = navigationCancelingError(`Redirecting to "${this.urlSerializer.serialize(result)}"`); |
| error.url = result; |
| throw error; |
| }), map(result => result === true)); |
| } |
| lineralizeSegments(route, urlTree) { |
| let res = []; |
| let c = urlTree.root; |
| while (true) { |
| res = res.concat(c.segments); |
| if (c.numberOfChildren === 0) { |
| return of(res); |
| } |
| if (c.numberOfChildren > 1 || !c.children[PRIMARY_OUTLET]) { |
| return namedOutletsRedirect(route.redirectTo); |
| } |
| c = c.children[PRIMARY_OUTLET]; |
| } |
| } |
| applyRedirectCommands(segments, redirectTo, posParams) { |
| return this.applyRedirectCreatreUrlTree(redirectTo, this.urlSerializer.parse(redirectTo), segments, posParams); |
| } |
| applyRedirectCreatreUrlTree(redirectTo, urlTree, segments, posParams) { |
| const newRoot = this.createSegmentGroup(redirectTo, urlTree.root, segments, posParams); |
| return new UrlTree(newRoot, this.createQueryParams(urlTree.queryParams, this.urlTree.queryParams), urlTree.fragment); |
| } |
| createQueryParams(redirectToParams, actualParams) { |
| const res = {}; |
| forEach(redirectToParams, (v, k) => { |
| const copySourceValue = typeof v === 'string' && v.startsWith(':'); |
| if (copySourceValue) { |
| const sourceName = v.substring(1); |
| res[k] = actualParams[sourceName]; |
| } |
| else { |
| res[k] = v; |
| } |
| }); |
| return res; |
| } |
| createSegmentGroup(redirectTo, group, segments, posParams) { |
| const updatedSegments = this.createSegments(redirectTo, group.segments, segments, posParams); |
| let children = {}; |
| forEach(group.children, (child, name) => { |
| children[name] = this.createSegmentGroup(redirectTo, child, segments, posParams); |
| }); |
| return new UrlSegmentGroup(updatedSegments, children); |
| } |
| createSegments(redirectTo, redirectToSegments, actualSegments, posParams) { |
| return redirectToSegments.map(s => s.path.startsWith(':') ? this.findPosParam(redirectTo, s, posParams) : |
| this.findOrReturn(s, actualSegments)); |
| } |
| findPosParam(redirectTo, redirectToUrlSegment, posParams) { |
| const pos = posParams[redirectToUrlSegment.path.substring(1)]; |
| if (!pos) |
| throw new Error(`Cannot redirect to '${redirectTo}'. Cannot find '${redirectToUrlSegment.path}'.`); |
| return pos; |
| } |
| findOrReturn(redirectToUrlSegment, actualSegments) { |
| let idx = 0; |
| for (const s of actualSegments) { |
| if (s.path === redirectToUrlSegment.path) { |
| actualSegments.splice(idx); |
| return s; |
| } |
| idx++; |
| } |
| return redirectToUrlSegment; |
| } |
| } |
| /** |
| * When possible, merges the primary outlet child into the parent `UrlSegmentGroup`. |
| * |
| * When a segment group has only one child which is a primary outlet, merges that child into the |
| * parent. That is, the child segment group's segments are merged into the `s` and the child's |
| * children become the children of `s`. Think of this like a 'squash', merging the child segment |
| * group into the parent. |
| */ |
| function mergeTrivialChildren(s) { |
| if (s.numberOfChildren === 1 && s.children[PRIMARY_OUTLET]) { |
| const c = s.children[PRIMARY_OUTLET]; |
| return new UrlSegmentGroup(s.segments.concat(c.segments), c.children); |
| } |
| return s; |
| } |
| /** |
| * Recursively merges primary segment children into their parents and also drops empty children |
| * (those which have no segments and no children themselves). The latter prevents serializing a |
| * group into something like `/a(aux:)`, where `aux` is an empty child segment. |
| */ |
| function squashSegmentGroup(segmentGroup) { |
| const newChildren = {}; |
| for (const childOutlet of Object.keys(segmentGroup.children)) { |
| const child = segmentGroup.children[childOutlet]; |
| const childCandidate = squashSegmentGroup(child); |
| // don't add empty children |
| if (childCandidate.segments.length > 0 || childCandidate.hasChildren()) { |
| newChildren[childOutlet] = childCandidate; |
| } |
| } |
| const s = new UrlSegmentGroup(segmentGroup.segments, newChildren); |
| return mergeTrivialChildren(s); |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| function applyRedirects$1(moduleInjector, configLoader, urlSerializer, config) { |
| return switchMap(t => applyRedirects(moduleInjector, configLoader, urlSerializer, t.extractedUrl, config) |
| .pipe(map(urlAfterRedirects => (Object.assign(Object.assign({}, t), { urlAfterRedirects }))))); |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| class CanActivate { |
| constructor(path) { |
| this.path = path; |
| this.route = this.path[this.path.length - 1]; |
| } |
| } |
| class CanDeactivate { |
| constructor(component, route) { |
| this.component = component; |
| this.route = route; |
| } |
| } |
| function getAllRouteGuards(future, curr, parentContexts) { |
| const futureRoot = future._root; |
| const currRoot = curr ? curr._root : null; |
| return getChildRouteGuards(futureRoot, currRoot, parentContexts, [futureRoot.value]); |
| } |
| function getCanActivateChild(p) { |
| const canActivateChild = p.routeConfig ? p.routeConfig.canActivateChild : null; |
| if (!canActivateChild || canActivateChild.length === 0) |
| return null; |
| return { node: p, guards: canActivateChild }; |
| } |
| function getToken(token, snapshot, moduleInjector) { |
| const config = getClosestLoadedConfig(snapshot); |
| const injector = config ? config.module.injector : moduleInjector; |
| return injector.get(token); |
| } |
| function getClosestLoadedConfig(snapshot) { |
| if (!snapshot) |
| return null; |
| for (let s = snapshot.parent; s; s = s.parent) { |
| const route = s.routeConfig; |
| if (route && route._loadedConfig) |
| return route._loadedConfig; |
| } |
| return null; |
| } |
| function getChildRouteGuards(futureNode, currNode, contexts, futurePath, checks = { |
| canDeactivateChecks: [], |
| canActivateChecks: [] |
| }) { |
| const prevChildren = nodeChildrenAsMap(currNode); |
| // Process the children of the future route |
| futureNode.children.forEach(c => { |
| getRouteGuards(c, prevChildren[c.value.outlet], contexts, futurePath.concat([c.value]), checks); |
| delete prevChildren[c.value.outlet]; |
| }); |
| // Process any children left from the current route (not active for the future route) |
| forEach(prevChildren, (v, k) => deactivateRouteAndItsChildren(v, contexts.getContext(k), checks)); |
| return checks; |
| } |
| function getRouteGuards(futureNode, currNode, parentContexts, futurePath, checks = { |
| canDeactivateChecks: [], |
| canActivateChecks: [] |
| }) { |
| const future = futureNode.value; |
| const curr = currNode ? currNode.value : null; |
| const context = parentContexts ? parentContexts.getContext(futureNode.value.outlet) : null; |
| // reusing the node |
| if (curr && future.routeConfig === curr.routeConfig) { |
| const shouldRun = shouldRunGuardsAndResolvers(curr, future, future.routeConfig.runGuardsAndResolvers); |
| if (shouldRun) { |
| checks.canActivateChecks.push(new CanActivate(futurePath)); |
| } |
| else { |
| // we need to set the data |
| future.data = curr.data; |
| future._resolvedData = curr._resolvedData; |
| } |
| // If we have a component, we need to go through an outlet. |
| if (future.component) { |
| getChildRouteGuards(futureNode, currNode, context ? context.children : null, futurePath, checks); |
| // if we have a componentless route, we recurse but keep the same outlet map. |
| } |
| else { |
| getChildRouteGuards(futureNode, currNode, parentContexts, futurePath, checks); |
| } |
| if (shouldRun && context && context.outlet && context.outlet.isActivated) { |
| checks.canDeactivateChecks.push(new CanDeactivate(context.outlet.component, curr)); |
| } |
| } |
| else { |
| if (curr) { |
| deactivateRouteAndItsChildren(currNode, context, checks); |
| } |
| checks.canActivateChecks.push(new CanActivate(futurePath)); |
| // If we have a component, we need to go through an outlet. |
| if (future.component) { |
| getChildRouteGuards(futureNode, null, context ? context.children : null, futurePath, checks); |
| // if we have a componentless route, we recurse but keep the same outlet map. |
| } |
| else { |
| getChildRouteGuards(futureNode, null, parentContexts, futurePath, checks); |
| } |
| } |
| return checks; |
| } |
| function shouldRunGuardsAndResolvers(curr, future, mode) { |
| if (typeof mode === 'function') { |
| return mode(curr, future); |
| } |
| switch (mode) { |
| case 'pathParamsChange': |
| return !equalPath(curr.url, future.url); |
| case 'pathParamsOrQueryParamsChange': |
| return !equalPath(curr.url, future.url) || |
| !shallowEqual(curr.queryParams, future.queryParams); |
| case 'always': |
| return true; |
| case 'paramsOrQueryParamsChange': |
| return !equalParamsAndUrlSegments(curr, future) || |
| !shallowEqual(curr.queryParams, future.queryParams); |
| case 'paramsChange': |
| default: |
| return !equalParamsAndUrlSegments(curr, future); |
| } |
| } |
| function deactivateRouteAndItsChildren(route, context, checks) { |
| const children = nodeChildrenAsMap(route); |
| const r = route.value; |
| forEach(children, (node, childName) => { |
| if (!r.component) { |
| deactivateRouteAndItsChildren(node, context, checks); |
| } |
| else if (context) { |
| deactivateRouteAndItsChildren(node, context.children.getContext(childName), checks); |
| } |
| else { |
| deactivateRouteAndItsChildren(node, null, checks); |
| } |
| }); |
| if (!r.component) { |
| checks.canDeactivateChecks.push(new CanDeactivate(null, r)); |
| } |
| else if (context && context.outlet && context.outlet.isActivated) { |
| checks.canDeactivateChecks.push(new CanDeactivate(context.outlet.component, r)); |
| } |
| else { |
| checks.canDeactivateChecks.push(new CanDeactivate(null, r)); |
| } |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| function checkGuards(moduleInjector, forwardEvent) { |
| return mergeMap(t => { |
| const { targetSnapshot, currentSnapshot, guards: { canActivateChecks, canDeactivateChecks } } = t; |
| if (canDeactivateChecks.length === 0 && canActivateChecks.length === 0) { |
| return of(Object.assign(Object.assign({}, t), { guardsResult: true })); |
| } |
| return runCanDeactivateChecks(canDeactivateChecks, targetSnapshot, currentSnapshot, moduleInjector) |
| .pipe(mergeMap(canDeactivate => { |
| return canDeactivate && isBoolean(canDeactivate) ? |
| runCanActivateChecks(targetSnapshot, canActivateChecks, moduleInjector, forwardEvent) : |
| of(canDeactivate); |
| }), map(guardsResult => (Object.assign(Object.assign({}, t), { guardsResult })))); |
| }); |
| } |
| function runCanDeactivateChecks(checks, futureRSS, currRSS, moduleInjector) { |
| return from(checks).pipe(mergeMap(check => runCanDeactivate(check.component, check.route, currRSS, futureRSS, moduleInjector)), first(result => { |
| return result !== true; |
| }, true)); |
| } |
| function runCanActivateChecks(futureSnapshot, checks, moduleInjector, forwardEvent) { |
| return from(checks).pipe(concatMap((check) => { |
| return concat(fireChildActivationStart(check.route.parent, forwardEvent), fireActivationStart(check.route, forwardEvent), runCanActivateChild(futureSnapshot, check.path, moduleInjector), runCanActivate(futureSnapshot, check.route, moduleInjector)); |
| }), first(result => { |
| return result !== true; |
| }, true)); |
| } |
| /** |
| * This should fire off `ActivationStart` events for each route being activated at this |
| * level. |
| * In other words, if you're activating `a` and `b` below, `path` will contain the |
| * `ActivatedRouteSnapshot`s for both and we will fire `ActivationStart` for both. Always |
| * return |
| * `true` so checks continue to run. |
| */ |
| function fireActivationStart(snapshot, forwardEvent) { |
| if (snapshot !== null && forwardEvent) { |
| forwardEvent(new ActivationStart(snapshot)); |
| } |
| return of(true); |
| } |
| /** |
| * This should fire off `ChildActivationStart` events for each route being activated at this |
| * level. |
| * In other words, if you're activating `a` and `b` below, `path` will contain the |
| * `ActivatedRouteSnapshot`s for both and we will fire `ChildActivationStart` for both. Always |
| * return |
| * `true` so checks continue to run. |
| */ |
| function fireChildActivationStart(snapshot, forwardEvent) { |
| if (snapshot !== null && forwardEvent) { |
| forwardEvent(new ChildActivationStart(snapshot)); |
| } |
| return of(true); |
| } |
| function runCanActivate(futureRSS, futureARS, moduleInjector) { |
| const canActivate = futureARS.routeConfig ? futureARS.routeConfig.canActivate : null; |
| if (!canActivate || canActivate.length === 0) |
| return of(true); |
| const canActivateObservables = canActivate.map((c) => { |
| return defer(() => { |
| const guard = getToken(c, futureARS, moduleInjector); |
| let observable; |
| if (isCanActivate(guard)) { |
| observable = wrapIntoObservable(guard.canActivate(futureARS, futureRSS)); |
| } |
| else if (isFunction(guard)) { |
| observable = wrapIntoObservable(guard(futureARS, futureRSS)); |
| } |
| else { |
| throw new Error('Invalid CanActivate guard'); |
| } |
| return observable.pipe(first()); |
| }); |
| }); |
| return of(canActivateObservables).pipe(prioritizedGuardValue()); |
| } |
| function runCanActivateChild(futureRSS, path, moduleInjector) { |
| const futureARS = path[path.length - 1]; |
| const canActivateChildGuards = path.slice(0, path.length - 1) |
| .reverse() |
| .map(p => getCanActivateChild(p)) |
| .filter(_ => _ !== null); |
| const canActivateChildGuardsMapped = canActivateChildGuards.map((d) => { |
| return defer(() => { |
| const guardsMapped = d.guards.map((c) => { |
| const guard = getToken(c, d.node, moduleInjector); |
| let observable; |
| if (isCanActivateChild(guard)) { |
| observable = wrapIntoObservable(guard.canActivateChild(futureARS, futureRSS)); |
| } |
| else if (isFunction(guard)) { |
| observable = wrapIntoObservable(guard(futureARS, futureRSS)); |
| } |
| else { |
| throw new Error('Invalid CanActivateChild guard'); |
| } |
| return observable.pipe(first()); |
| }); |
| return of(guardsMapped).pipe(prioritizedGuardValue()); |
| }); |
| }); |
| return of(canActivateChildGuardsMapped).pipe(prioritizedGuardValue()); |
| } |
| function runCanDeactivate(component, currARS, currRSS, futureRSS, moduleInjector) { |
| const canDeactivate = currARS && currARS.routeConfig ? currARS.routeConfig.canDeactivate : null; |
| if (!canDeactivate || canDeactivate.length === 0) |
| return of(true); |
| const canDeactivateObservables = canDeactivate.map((c) => { |
| const guard = getToken(c, currARS, moduleInjector); |
| let observable; |
| if (isCanDeactivate(guard)) { |
| observable = wrapIntoObservable(guard.canDeactivate(component, currARS, currRSS, futureRSS)); |
| } |
| else if (isFunction(guard)) { |
| observable = wrapIntoObservable(guard(component, currARS, currRSS, futureRSS)); |
| } |
| else { |
| throw new Error('Invalid CanDeactivate guard'); |
| } |
| return observable.pipe(first()); |
| }); |
| return of(canDeactivateObservables).pipe(prioritizedGuardValue()); |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| class NoMatch$1 { |
| } |
| function newObservableError(e) { |
| // TODO(atscott): This pattern is used throughout the router code and can be `throwError` instead. |
| return new Observable((obs) => obs.error(e)); |
| } |
| function recognize(rootComponentType, config, urlTree, url, paramsInheritanceStrategy = 'emptyOnly', relativeLinkResolution = 'legacy') { |
| try { |
| const result = new Recognizer(rootComponentType, config, urlTree, url, paramsInheritanceStrategy, relativeLinkResolution) |
| .recognize(); |
| if (result === null) { |
| return newObservableError(new NoMatch$1()); |
| } |
| else { |
| return of(result); |
| } |
| } |
| catch (e) { |
| // Catch the potential error from recognize due to duplicate outlet matches and return as an |
| // `Observable` error instead. |
| return newObservableError(e); |
| } |
| } |
| class Recognizer { |
| constructor(rootComponentType, config, urlTree, url, paramsInheritanceStrategy, relativeLinkResolution) { |
| this.rootComponentType = rootComponentType; |
| this.config = config; |
| this.urlTree = urlTree; |
| this.url = url; |
| this.paramsInheritanceStrategy = paramsInheritanceStrategy; |
| this.relativeLinkResolution = relativeLinkResolution; |
| } |
| recognize() { |
| const rootSegmentGroup = split(this.urlTree.root, [], [], this.config.filter(c => c.redirectTo === undefined), this.relativeLinkResolution) |
| .segmentGroup; |
| const children = this.processSegmentGroup(this.config, rootSegmentGroup, PRIMARY_OUTLET); |
| if (children === null) { |
| return null; |
| } |
| // Use Object.freeze to prevent readers of the Router state from modifying it outside of a |
| // navigation, resulting in the router being out of sync with the browser. |
| 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, {}); |
| const rootNode = new TreeNode(root, children); |
| const routeState = new RouterStateSnapshot(this.url, rootNode); |
| this.inheritParamsAndData(routeState._root); |
| return routeState; |
| } |
| inheritParamsAndData(routeNode) { |
| const route = routeNode.value; |
| const i = inheritedParamsDataResolve(route, this.paramsInheritanceStrategy); |
| route.params = Object.freeze(i.params); |
| route.data = Object.freeze(i.data); |
| routeNode.children.forEach(n => this.inheritParamsAndData(n)); |
| } |
| processSegmentGroup(config, segmentGroup, outlet) { |
| if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) { |
| return this.processChildren(config, segmentGroup); |
| } |
| return this.processSegment(config, segmentGroup, segmentGroup.segments, outlet); |
| } |
| /** |
| * Matches every child outlet in the `segmentGroup` to a `Route` in the config. Returns `null` if |
| * we cannot find a match for _any_ of the children. |
| * |
| * @param config - The `Routes` to match against |
| * @param segmentGroup - The `UrlSegmentGroup` whose children need to be matched against the |
| * config. |
| */ |
| processChildren(config, segmentGroup) { |
| const children = []; |
| for (const childOutlet of Object.keys(segmentGroup.children)) { |
| const child = segmentGroup.children[childOutlet]; |
| // Sort the config so that routes with outlets that match the one being activated appear |
| // first, followed by routes for other outlets, which might match if they have an empty path. |
| const sortedConfig = sortByMatchingOutlets(config, childOutlet); |
| const outletChildren = this.processSegmentGroup(sortedConfig, child, childOutlet); |
| if (outletChildren === null) { |
| // Configs must match all segment children so because we did not find a match for this |
| // outlet, return `null`. |
| return null; |
| } |
| children.push(...outletChildren); |
| } |
| // Because we may have matched two outlets to the same empty path segment, we can have multiple |
| // activated results for the same outlet. We should merge the children of these results so the |
| // final return value is only one `TreeNode` per outlet. |
| const mergedChildren = mergeEmptyPathMatches(children); |
| if (typeof ngDevMode === 'undefined' || ngDevMode) { |
| // This should really never happen - we are only taking the first match for each outlet and |
| // merge the empty path matches. |
| checkOutletNameUniqueness(mergedChildren); |
| } |
| sortActivatedRouteSnapshots(mergedChildren); |
| return mergedChildren; |
| } |
| processSegment(config, segmentGroup, segments, outlet) { |
| for (const r of config) { |
| const children = this.processSegmentAgainstRoute(r, segmentGroup, segments, outlet); |
| if (children !== null) { |
| return children; |
| } |
| } |
| if (noLeftoversInUrl(segmentGroup, segments, outlet)) { |
| return []; |
| } |
| return null; |
| } |
| processSegmentAgainstRoute(route, rawSegment, segments, outlet) { |
| if (route.redirectTo || !isImmediateMatch(route, rawSegment, segments, outlet)) |
| return null; |
| let snapshot; |
| let consumedSegments = []; |
| let rawSlicedSegments = []; |
| if (route.path === '**') { |
| const params = segments.length > 0 ? last(segments).parameters : {}; |
| 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)); |
| } |
| else { |
| const result = match(rawSegment, route, segments); |
| if (!result.matched) { |
| return null; |
| } |
| consumedSegments = result.consumedSegments; |
| rawSlicedSegments = segments.slice(result.lastChild); |
| 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)); |
| } |
| const childConfig = getChildConfig(route); |
| const { segmentGroup, slicedSegments } = split(rawSegment, consumedSegments, rawSlicedSegments, |
| // Filter out routes with redirectTo because we are trying to create activated route |
| // snapshots and don't handle redirects here. That should have been done in |
| // `applyRedirects`. |
| childConfig.filter(c => c.redirectTo === undefined), this.relativeLinkResolution); |
| if (slicedSegments.length === 0 && segmentGroup.hasChildren()) { |
| const children = this.processChildren(childConfig, segmentGroup); |
| if (children === null) { |
| return null; |
| } |
| return [new TreeNode(snapshot, children)]; |
| } |
| if (childConfig.length === 0 && slicedSegments.length === 0) { |
| return [new TreeNode(snapshot, [])]; |
| } |
| const matchedOnOutlet = getOutlet(route) === outlet; |
| // If we matched a config due to empty path match on a different outlet, we need to continue |
| // passing the current outlet for the segment rather than switch to PRIMARY. |
| // Note that we switch to primary when we have a match because outlet configs look like this: |
| // {path: 'a', outlet: 'a', children: [ |
| // {path: 'b', component: B}, |
| // {path: 'c', component: C}, |
| // ]} |
| // Notice that the children of the named outlet are configured with the primary outlet |
| const children = this.processSegment(childConfig, segmentGroup, slicedSegments, matchedOnOutlet ? PRIMARY_OUTLET : outlet); |
| if (children === null) { |
| return null; |
| } |
| return [new TreeNode(snapshot, children)]; |
| } |
| } |
| function sortActivatedRouteSnapshots(nodes) { |
| nodes.sort((a, b) => { |
| if (a.value.outlet === PRIMARY_OUTLET) |
| return -1; |
| if (b.value.outlet === PRIMARY_OUTLET) |
| return 1; |
| return a.value.outlet.localeCompare(b.value.outlet); |
| }); |
| } |
| function getChildConfig(route) { |
| if (route.children) { |
| return route.children; |
| } |
| if (route.loadChildren) { |
| return route._loadedConfig.routes; |
| } |
| return []; |
| } |
| function hasEmptyPathConfig(node) { |
| const config = node.value.routeConfig; |
| return config && config.path === '' && config.redirectTo === undefined; |
| } |
| /** |
| * Finds `TreeNode`s with matching empty path route configs and merges them into `TreeNode` with the |
| * children from each duplicate. This is necessary because different outlets can match a single |
| * empty path route config and the results need to then be merged. |
| */ |
| function mergeEmptyPathMatches(nodes) { |
| const result = []; |
| // The set of nodes which contain children that were merged from two duplicate empty path nodes. |
| const mergedNodes = new Set(); |
| for (const node of nodes) { |
| if (!hasEmptyPathConfig(node)) { |
| result.push(node); |
| continue; |
| } |
| const duplicateEmptyPathNode = result.find(resultNode => node.value.routeConfig === resultNode.value.routeConfig); |
| if (duplicateEmptyPathNode !== undefined) { |
| duplicateEmptyPathNode.children.push(...node.children); |
| mergedNodes.add(duplicateEmptyPathNode); |
| } |
| else { |
| result.push(node); |
| } |
| } |
| // For each node which has children from multiple sources, we need to recompute a new `TreeNode` |
| // by also merging those children. This is necessary when there are multiple empty path configs in |
| // a row. Put another way: whenever we combine children of two nodes, we need to also check if any |
| // of those children can be combined into a single node as well. |
| for (const mergedNode of mergedNodes) { |
| const mergedChildren = mergeEmptyPathMatches(mergedNode.children); |
| result.push(new TreeNode(mergedNode.value, mergedChildren)); |
| } |
| return result.filter(n => !mergedNodes.has(n)); |
| } |
| function checkOutletNameUniqueness(nodes) { |
| const names = {}; |
| nodes.forEach(n => { |
| const routeWithSameOutletName = names[n.value.outlet]; |
| if (routeWithSameOutletName) { |
| const p = routeWithSameOutletName.url.map(s => s.toString()).join('/'); |
| const c = n.value.url.map(s => s.toString()).join('/'); |
| throw new Error(`Two segments cannot have the same outlet name: '${p}' and '${c}'.`); |
| } |
| names[n.value.outlet] = n.value; |
| }); |
| } |
| function getSourceSegmentGroup(segmentGroup) { |
| let s = segmentGroup; |
| while (s._sourceSegment) { |
| s = s._sourceSegment; |
| } |
| return s; |
| } |
| function getPathIndexShift(segmentGroup) { |
| let s = segmentGroup; |
| let res = (s._segmentIndexShift ? s._segmentIndexShift : 0); |
| while (s._sourceSegment) { |
| s = s._sourceSegment; |
| res += (s._segmentIndexShift ? s._segmentIndexShift : 0); |
| } |
| return res - 1; |
| } |
| function getData(route) { |
| return route.data || {}; |
| } |
| function getResolve(route) { |
| return route.resolve || {}; |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| function recognize$1(rootComponentType, config, serializer, paramsInheritanceStrategy, relativeLinkResolution) { |
| return mergeMap(t => recognize(rootComponentType, config, t.urlAfterRedirects, serializer(t.urlAfterRedirects), paramsInheritanceStrategy, relativeLinkResolution) |
| .pipe(map(targetSnapshot => (Object.assign(Object.assign({}, t), { targetSnapshot }))))); |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| function resolveData(paramsInheritanceStrategy, moduleInjector) { |
| return mergeMap(t => { |
| const { targetSnapshot, guards: { canActivateChecks } } = t; |
| if (!canActivateChecks.length) { |
| return of(t); |
| } |
| let canActivateChecksResolved = 0; |
| return from(canActivateChecks) |
| .pipe(concatMap(check => runResolve(check.route, targetSnapshot, paramsInheritanceStrategy, moduleInjector)), tap(() => canActivateChecksResolved++), takeLast(1), mergeMap(_ => canActivateChecksResolved === canActivateChecks.length ? of(t) : EMPTY)); |
| }); |
| } |
| function runResolve(futureARS, futureRSS, paramsInheritanceStrategy, moduleInjector) { |
| const resolve = futureARS._resolve; |
| return resolveNode(resolve, futureARS, futureRSS, moduleInjector) |
| .pipe(map((resolvedData) => { |
| futureARS._resolvedData = resolvedData; |
| futureARS.data = Object.assign(Object.assign({}, futureARS.data), inheritedParamsDataResolve(futureARS, paramsInheritanceStrategy).resolve); |
| return null; |
| })); |
| } |
| function resolveNode(resolve, futureARS, futureRSS, moduleInjector) { |
| const keys = Object.keys(resolve); |
| if (keys.length === 0) { |
| return of({}); |
| } |
| const data = {}; |
| return from(keys).pipe(mergeMap((key) => getResolver(resolve[key], futureARS, futureRSS, moduleInjector) |
| .pipe(tap((value) => { |
| data[key] = value; |
| }))), takeLast(1), mergeMap(() => { |
| // Ensure all resolvers returned values, otherwise don't emit any "next" and just complete |
| // the chain which will cancel navigation |
| if (Object.keys(data).length === keys.length) { |
| return of(data); |
| } |
| return EMPTY; |
| })); |
| } |
| function getResolver(injectionToken, futureARS, futureRSS, moduleInjector) { |
| const resolver = getToken(injectionToken, futureARS, moduleInjector); |
| return resolver.resolve ? wrapIntoObservable(resolver.resolve(futureARS, futureRSS)) : |
| wrapIntoObservable(resolver(futureARS, futureRSS)); |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * Perform a side effect through a switchMap for every emission on the source Observable, |
| * but return an Observable that is identical to the source. It's essentially the same as |
| * the `tap` operator, but if the side effectful `next` function returns an ObservableInput, |
| * it will wait before continuing with the original value. |
| */ |
| function switchTap(next) { |
| return switchMap(v => { |
| const nextResult = next(v); |
| if (nextResult) { |
| return from(nextResult).pipe(map(() => v)); |
| } |
| return of(v); |
| }); |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * @description |
| * |
| * Provides a way to customize when activated routes get reused. |
| * |
| * @publicApi |
| */ |
| class RouteReuseStrategy { |
| } |
| /** |
| * @description |
| * |
| * This base route reuse strategy only reuses routes when the matched router configs are |
| * identical. This prevents components from being destroyed and recreated |
| * when just the fragment or query parameters change |
| * (that is, the existing component is _reused_). |
| * |
| * This strategy does not store any routes for later reuse. |
| * |
| * Angular uses this strategy by default. |
| * |
| * |
| * It can be used as a base class for custom route reuse strategies, i.e. you can create your own |
| * class that extends the `BaseRouteReuseStrategy` one. |
| * @publicApi |
| */ |
| class BaseRouteReuseStrategy { |
| /** |
| * Whether the given route should detach for later reuse. |
| * Always returns false for `BaseRouteReuseStrategy`. |
| * */ |
| shouldDetach(route) { |
| return false; |
| } |
| /** |
| * A no-op; the route is never stored since this strategy never detaches routes for later re-use. |
| */ |
| store(route, detachedTree) { } |
| /** Returns `false`, meaning the route (and its subtree) is never reattached */ |
| shouldAttach(route) { |
| return false; |
| } |
| /** Returns `null` because this strategy does not store routes for later re-use. */ |
| retrieve(route) { |
| return null; |
| } |
| /** |
| * Determines if a route should be reused. |
| * This strategy returns `true` when the future route config and current route config are |
| * identical. |
| */ |
| shouldReuseRoute(future, curr) { |
| return future.routeConfig === curr.routeConfig; |
| } |
| } |
| class DefaultRouteReuseStrategy extends BaseRouteReuseStrategy { |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * The [DI token](guide/glossary/#di-token) for a router configuration. |
| * @see `ROUTES` |
| * @publicApi |
| */ |
| const ROUTES = new InjectionToken('ROUTES'); |
| class RouterConfigLoader { |
| constructor(loader, compiler, onLoadStartListener, onLoadEndListener) { |
| this.loader = loader; |
| this.compiler = compiler; |
| this.onLoadStartListener = onLoadStartListener; |
| this.onLoadEndListener = onLoadEndListener; |
| } |
| load(parentInjector, route) { |
| if (route._loader$) { |
| return route._loader$; |
| } |
| if (this.onLoadStartListener) { |
| this.onLoadStartListener(route); |
| } |
| const moduleFactory$ = this.loadModuleFactory(route.loadChildren); |
| const loadRunner = moduleFactory$.pipe(map((factory) => { |
| if (this.onLoadEndListener) { |
| this.onLoadEndListener(route); |
| } |
| const module = factory.create(parentInjector); |
| // When loading a module that doesn't provide `RouterModule.forChild()` preloader |
| // will get stuck in an infinite loop. The child module's Injector will look to |
| // its parent `Injector` when it doesn't find any ROUTES so it will return routes |
| // for it's parent module instead. |
| return new LoadedRouterConfig(flatten(module.injector.get(ROUTES, undefined, InjectFlags.Self | InjectFlags.Optional)) |
| .map(standardizeConfig), module); |
| }), catchError((err) => { |
| route._loader$ = undefined; |
| throw err; |
| })); |
| // Use custom ConnectableObservable as share in runners pipe increasing the bundle size too much |
| route._loader$ = new ConnectableObservable(loadRunner, () => new Subject()) |
| .pipe(refCount()); |
| return route._loader$; |
| } |
| loadModuleFactory(loadChildren) { |
| if (typeof loadChildren === 'string') { |
| return from(this.loader.load(loadChildren)); |
| } |
| else { |
| return wrapIntoObservable(loadChildren()).pipe(mergeMap((t) => { |
| if (t instanceof NgModuleFactory) { |
| return of(t); |
| } |
| else { |
| return from(this.compiler.compileModuleAsync(t)); |
| } |
| })); |
| } |
| } |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * Store contextual information about a `RouterOutlet` |
| * |
| * @publicApi |
| */ |
| class OutletContext { |
| constructor() { |
| this.outlet = null; |
| this.route = null; |
| this.resolver = null; |
| this.children = new ChildrenOutletContexts(); |
| this.attachRef = null; |
| } |
| } |
| /** |
| * Store contextual information about the children (= nested) `RouterOutlet` |
| * |
| * @publicApi |
| */ |
| class ChildrenOutletContexts { |
| constructor() { |
| // contexts for child outlets, by name. |
| this.contexts = new Map(); |
| } |
| /** Called when a `RouterOutlet` directive is instantiated */ |
| onChildOutletCreated(childName, outlet) { |
| const context = this.getOrCreateContext(childName); |
| context.outlet = outlet; |
| this.contexts.set(childName, context); |
| } |
| /** |
| * Called when a `RouterOutlet` directive is destroyed. |
| * We need to keep the context as the outlet could be destroyed inside a NgIf and might be |
| * re-created later. |
| */ |
| onChildOutletDestroyed(childName) { |
| const context = this.getContext(childName); |
| if (context) { |
| context.outlet = null; |
| } |
| } |
| /** |
| * Called when the corresponding route is deactivated during navigation. |
| * Because the component get destroyed, all children outlet are destroyed. |
| */ |
| onOutletDeactivated() { |
| const contexts = this.contexts; |
| this.contexts = new Map(); |
| return contexts; |
| } |
| onOutletReAttached(contexts) { |
| this.contexts = contexts; |
| } |
| getOrCreateContext(childName) { |
| let context = this.getContext(childName); |
| if (!context) { |
| context = new OutletContext(); |
| this.contexts.set(childName, context); |
| } |
| return context; |
| } |
| getContext(childName) { |
| return this.contexts.get(childName) || null; |
| } |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * @description |
| * |
| * Provides a way to migrate AngularJS applications to Angular. |
| * |
| * @publicApi |
| */ |
| class UrlHandlingStrategy { |
| } |
| /** |
| * @publicApi |
| */ |
| class DefaultUrlHandlingStrategy { |
| shouldProcessUrl(url) { |
| return true; |
| } |
| extract(url) { |
| return url; |
| } |
| merge(newUrlPart, wholeUrl) { |
| return newUrlPart; |
| } |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| function defaultErrorHandler(error) { |
| throw error; |
| } |
| function defaultMalformedUriErrorHandler(error, urlSerializer, url) { |
| return urlSerializer.parse('/'); |
| } |
| /** |
| * @internal |
| */ |
| function defaultRouterHook(snapshot, runExtras) { |
| return of(null); |
| } |
| /** |
| * @description |
| * |
| * A service that provides navigation among views and URL manipulation capabilities. |
| * |
| * @see `Route`. |
| * @see [Routing and Navigation Guide](guide/router). |
| * |
| * @ngModule RouterModule |
| * |
| * @publicApi |
| */ |
| class Router { |
| /** |
| * Creates the router service. |
| */ |
| // TODO: vsavkin make internal after the final is out. |
| constructor(rootComponentType, urlSerializer, rootContexts, location, injector, loader, compiler, config) { |
| this.rootComponentType = rootComponentType; |
| this.urlSerializer = urlSerializer; |
| this.rootContexts = rootContexts; |
| this.location = location; |
| this.config = config; |
| this.lastSuccessfulNavigation = null; |
| this.currentNavigation = null; |
| this.disposed = false; |
| /** |
| * Tracks the previously seen location change from the location subscription so we can compare |
| * the two latest to see if they are duplicates. See setUpLocationChangeListener. |
| */ |
| this.lastLocationChangeInfo = null; |
| this.navigationId = 0; |
| this.isNgZoneEnabled = false; |
| /** |
| * An event stream for routing events in this NgModule. |
| */ |
| this.events = new Subject(); |
| /** |
| * A handler for navigation errors in this NgModule. |
| */ |
| this.errorHandler = defaultErrorHandler; |
| /** |
| * A handler for errors thrown by `Router.parseUrl(url)` |
| * when `url` contains an invalid character. |
| * The most common case is a `%` sign |
| * that's not encoded and is not part of a percent encoded sequence. |
| */ |
| this.malformedUriErrorHandler = defaultMalformedUriErrorHandler; |
| /** |
| * True if at least one navigation event has occurred, |
| * false otherwise. |
| */ |
| this.navigated = false; |
| this.lastSuccessfulId = -1; |
| /** |
| * Hooks that enable you to pause navigation, |
| * either before or after the preactivation phase. |
| * Used by `RouterModule`. |
| * |
| * @internal |
| */ |
| this.hooks = { beforePreactivation: defaultRouterHook, afterPreactivation: defaultRouterHook }; |
| /** |
| * A strategy for extracting and merging URLs. |
| * Used for AngularJS to Angular migrations. |
| */ |
| this.urlHandlingStrategy = new DefaultUrlHandlingStrategy(); |
| /** |
| * A strategy for re-using routes. |
| */ |
| this.routeReuseStrategy = new DefaultRouteReuseStrategy(); |
| /** |
| * How to handle a navigation request to the current URL. One of: |
| * - `'ignore'` : The router ignores the request. |
| * - `'reload'` : The router reloads the URL. Use to implement a "refresh" feature. |
| */ |
| this.onSameUrlNavigation = 'ignore'; |
| /** |
| * How to merge parameters, data, and resolved data from parent to child |
| * routes. One of: |
| * |
| * - `'emptyOnly'` : Inherit parent parameters, data, and resolved data |
| * for path-less or component-less routes. |
| * - `'always'` : Inherit parent parameters, data, and resolved data |
| * for all child routes. |
| */ |
| this.paramsInheritanceStrategy = 'emptyOnly'; |
| /** |
| * Determines when the router updates the browser URL. |
| * By default (`"deferred"`), updates the browser URL after navigation has finished. |
| * Set to `'eager'` to update the browser URL at the beginning of navigation. |
| * You can choose to update early so that, if navigation fails, |
| * you can show an error message with the URL that failed. |
| */ |
| this.urlUpdateStrategy = 'deferred'; |
| /** |
| * Enables a bug fix that corrects relative link resolution in components with empty paths. |
| * @see `RouterModule` |
| */ |
| this.relativeLinkResolution = 'corrected'; |
| const onLoadStart = (r) => this.triggerEvent(new RouteConfigLoadStart(r)); |
| const onLoadEnd = (r) => this.triggerEvent(new RouteConfigLoadEnd(r)); |
| this.ngModule = injector.get(NgModuleRef); |
| this.console = injector.get(ɵConsole); |
| const ngZone = injector.get(NgZone); |
| this.isNgZoneEnabled = ngZone instanceof NgZone && NgZone.isInAngularZone(); |
| this.resetConfig(config); |
| this.currentUrlTree = createEmptyUrlTree(); |
| this.rawUrlTree = this.currentUrlTree; |
| this.browserUrlTree = this.currentUrlTree; |
| this.configLoader = new RouterConfigLoader(loader, compiler, onLoadStart, onLoadEnd); |
| this.routerState = createEmptyState(this.currentUrlTree, this.rootComponentType); |
| this.transitions = new BehaviorSubject({ |
| id: 0, |
| currentUrlTree: this.currentUrlTree, |
| currentRawUrl: this.currentUrlTree, |
| extractedUrl: this.urlHandlingStrategy.extract(this.currentUrlTree), |
| urlAfterRedirects: this.urlHandlingStrategy.extract(this.currentUrlTree), |
| rawUrl: this.currentUrlTree, |
| extras: {}, |
| resolve: null, |
| reject: null, |
| promise: Promise.resolve(true), |
| source: 'imperative', |
| restoredState: null, |
| currentSnapshot: this.routerState.snapshot, |
| targetSnapshot: null, |
| currentRouterState: this.routerState, |
| targetRouterState: null, |
| guards: { canActivateChecks: [], canDeactivateChecks: [] }, |
| guardsResult: null, |
| }); |
| this.navigations = this.setupNavigations(this.transitions); |
| this.processNavigations(); |
| } |
| setupNavigations(transitions) { |
| const eventsSubject = this.events; |
| return transitions.pipe(filter(t => t.id !== 0), |
| // Extract URL |
| map(t => (Object.assign(Object.assign({}, t), { extractedUrl: this.urlHandlingStrategy.extract(t.rawUrl) }))), |
| // Using switchMap so we cancel executing navigations when a new one comes in |
| switchMap(t => { |
| let completed = false; |
| let errored = false; |
| return of(t).pipe( |
| // Store the Navigation object |
| tap(t => { |
| this.currentNavigation = { |
| id: t.id, |
| initialUrl: t.currentRawUrl, |
| extractedUrl: t.extractedUrl, |
| trigger: t.source, |
| extras: t.extras, |
| previousNavigation: this.lastSuccessfulNavigation ? Object.assign(Object.assign({}, this.lastSuccessfulNavigation), { previousNavigation: null }) : |
| null |
| }; |
| }), switchMap(t => { |
| const urlTransition = !this.navigated || |
| t.extractedUrl.toString() !== this.browserUrlTree.toString(); |
| const processCurrentUrl = (this.onSameUrlNavigation === 'reload' ? true : urlTransition) && |
| this.urlHandlingStrategy.shouldProcessUrl(t.rawUrl); |
| if (processCurrentUrl) { |
| return of(t).pipe( |
| // Fire NavigationStart event |
| switchMap(t => { |
| const transition = this.transitions.getValue(); |
| eventsSubject.next(new NavigationStart(t.id, this.serializeUrl(t.extractedUrl), t.source, t.restoredState)); |
| if (transition !== this.transitions.getValue()) { |
| return EMPTY; |
| } |
| // This delay is required to match old behavior that forced |
| // navigation to always be async |
| return Promise.resolve(t); |
| }), |
| // ApplyRedirects |
| applyRedirects$1(this.ngModule.injector, this.configLoader, this.urlSerializer, this.config), |
| // Update the currentNavigation |
| tap(t => { |
| this.currentNavigation = Object.assign(Object.assign({}, this.currentNavigation), { finalUrl: t.urlAfterRedirects }); |
| }), |
| // Recognize |
| recognize$1(this.rootComponentType, this.config, (url) => this.serializeUrl(url), this.paramsInheritanceStrategy, this.relativeLinkResolution), |
| // Update URL if in `eager` update mode |
| tap(t => { |
| if (this.urlUpdateStrategy === 'eager') { |
| if (!t.extras.skipLocationChange) { |
| this.setBrowserUrl(t.urlAfterRedirects, !!t.extras.replaceUrl, t.id, t.extras.state); |
| } |
| this.browserUrlTree = t.urlAfterRedirects; |
| } |
| // Fire RoutesRecognized |
| const routesRecognized = new RoutesRecognized(t.id, this.serializeUrl(t.extractedUrl), this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot); |
| eventsSubject.next(routesRecognized); |
| })); |
| } |
| else { |
| const processPreviousUrl = urlTransition && this.rawUrlTree && |
| this.urlHandlingStrategy.shouldProcessUrl(this.rawUrlTree); |
| /* When the current URL shouldn't be processed, but the previous one was, |
| * we handle this "error condition" by navigating to the previously |
| * successful URL, but leaving the URL intact.*/ |
| if (processPreviousUrl) { |
| const { id, extractedUrl, source, restoredState, extras } = t; |
| const navStart = new NavigationStart(id, this.serializeUrl(extractedUrl), source, restoredState); |
| eventsSubject.next(navStart); |
| const targetSnapshot = createEmptyState(extractedUrl, this.rootComponentType).snapshot; |
| return of(Object.assign(Object.assign({}, t), { targetSnapshot, urlAfterRedirects: extractedUrl, extras: Object.assign(Object.assign({}, extras), { skipLocationChange: false, replaceUrl: false }) })); |
| } |
| else { |
| /* When neither the current or previous URL can be processed, do nothing |
| * other than update router's internal reference to the current "settled" |
| * URL. This way the next navigation will be coming from the current URL |
| * in the browser. |
| */ |
| this.rawUrlTree = t.rawUrl; |
| this.browserUrlTree = t.urlAfterRedirects; |
| t.resolve(null); |
| return EMPTY; |
| } |
| } |
| }), |
| // Before Preactivation |
| switchTap(t => { |
| const { targetSnapshot, id: navigationId, extractedUrl: appliedUrlTree, rawUrl: rawUrlTree, extras: { skipLocationChange, replaceUrl } } = t; |
| return this.hooks.beforePreactivation(targetSnapshot, { |
| navigationId, |
| appliedUrlTree, |
| rawUrlTree, |
| skipLocationChange: !!skipLocationChange, |
| replaceUrl: !!replaceUrl, |
| }); |
| }), |
| // --- GUARDS --- |
| tap(t => { |
| const guardsStart = new GuardsCheckStart(t.id, this.serializeUrl(t.extractedUrl), this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot); |
| this.triggerEvent(guardsStart); |
| }), 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 => { |
| if (isUrlTree(t.guardsResult)) { |
| const error = navigationCancelingError(`Redirecting to "${this.serializeUrl(t.guardsResult)}"`); |
| error.url = t.guardsResult; |
| throw error; |
| } |
| const guardsEnd = new GuardsCheckEnd(t.id, this.serializeUrl(t.extractedUrl), this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot, !!t.guardsResult); |
| this.triggerEvent(guardsEnd); |
| }), filter(t => { |
| if (!t.guardsResult) { |
| this.resetUrlToCurrentUrlTree(); |
| const navCancel = new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), ''); |
| eventsSubject.next(navCancel); |
| t.resolve(false); |
| return false; |
| } |
| return true; |
| }), |
| // --- RESOLVE --- |
| switchTap(t => { |
| if (t.guards.canActivateChecks.length) { |
| return of(t).pipe(tap(t => { |
| const resolveStart = new ResolveStart(t.id, this.serializeUrl(t.extractedUrl), this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot); |
| this.triggerEvent(resolveStart); |
| }), switchMap(t => { |
| let dataResolved = false; |
| return of(t).pipe(resolveData(this.paramsInheritanceStrategy, this.ngModule.injector), tap({ |
| next: () => dataResolved = true, |
| complete: () => { |
| if (!dataResolved) { |
| const navCancel = new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), `At least one route resolver didn't emit any value.`); |
| eventsSubject.next(navCancel); |
| t.resolve(false); |
| } |
| } |
| })); |
| }), tap(t => { |
| const resolveEnd = new ResolveEnd(t.id, this.serializeUrl(t.extractedUrl), this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot); |
| this.triggerEvent(resolveEnd); |
| })); |
| } |
| return undefined; |
| }), |
| // --- AFTER PREACTIVATION --- |
| switchTap((t) => { |
| const { targetSnapshot, id: navigationId, extractedUrl: appliedUrlTree, rawUrl: rawUrlTree, extras: { skipLocationChange, replaceUrl } } = t; |
| return this.hooks.afterPreactivation(targetSnapshot, { |
| navigationId, |
| appliedUrlTree, |
| rawUrlTree, |
| skipLocationChange: !!skipLocationChange, |
| replaceUrl: !!replaceUrl, |
| }); |
| }), map((t) => { |
| const targetRouterState = createRouterState(this.routeReuseStrategy, t.targetSnapshot, t.currentRouterState); |
| return (Object.assign(Object.assign({}, t), { targetRouterState })); |
| }), |
| /* Once here, we are about to activate syncronously. The assumption is this |
| will succeed, and user code may read from the Router service. Therefore |
| before activation, we need to update router properties storing the current |
| URL and the RouterState, as well as updated the browser URL. All this should |
| happen *before* activating. */ |
| tap((t) => { |
| this.currentUrlTree = t.urlAfterRedirects; |
| this.rawUrlTree = |
| this.urlHandlingStrategy.merge(this.currentUrlTree, t.rawUrl); |
| this.routerState = t.targetRouterState; |
| if (this.urlUpdateStrategy === 'deferred') { |
| if (!t.extras.skipLocationChange) { |
| this.setBrowserUrl(this.rawUrlTree, !!t.extras.replaceUrl, t.id, t.extras.state); |
| } |
| this.browserUrlTree = t.urlAfterRedirects; |
| } |
| }), activateRoutes(this.rootContexts, this.routeReuseStrategy, (evt) => this.triggerEvent(evt)), tap({ |
| next() { |
| completed = true; |
| }, |
| complete() { |
| completed = true; |
| } |
| }), finalize(() => { |
| /* When the navigation stream finishes either through error or success, we |
| * set the `completed` or `errored` flag. However, there are some situations |
| * where we could get here without either of those being set. For instance, a |
| * redirect during NavigationStart. Therefore, this is a catch-all to make |
| * sure the NavigationCancel |
| * event is fired when a navigation gets cancelled but not caught by other |
| * means. */ |
| if (!completed && !errored) { |
| // Must reset to current URL tree here to ensure history.state is set. On a |
| // fresh page load, if a new navigation comes in before a successful |
| // navigation completes, there will be nothing in |
| // history.state.navigationId. This can cause sync problems with AngularJS |
| // sync code which looks for a value here in order to determine whether or |
| // not to handle a given popstate event or to leave it to the Angular |
| // router. |
| this.resetUrlToCurrentUrlTree(); |
| const navCancel = new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), `Navigation ID ${t.id} is not equal to the current navigation id ${this.navigationId}`); |
| eventsSubject.next(navCancel); |
| t.resolve(false); |
| } |
| // currentNavigation should always be reset to null here. If navigation was |
| // successful, lastSuccessfulTransition will have already been set. Therefore |
| // we can safely set currentNavigation to null here. |
| this.currentNavigation = null; |
| }), catchError((e) => { |
| errored = true; |
| /* This error type is issued during Redirect, and is handled as a |
| * cancellation rather than an error. */ |
| if (isNavigationCancelingError(e)) { |
| const redirecting = isUrlTree(e.url); |
| if (!redirecting) { |
| // Set property only if we're not redirecting. If we landed on a page and |
| // redirect to `/` route, the new navigation is going to see the `/` |
| // isn't a change from the default currentUrlTree and won't navigate. |
| // This is only applicable with initial navigation, so setting |
| // `navigated` only when not redirecting resolves this scenario. |
| this.navigated = true; |
| this.resetStateAndUrl(t.currentRouterState, t.currentUrlTree, t.rawUrl); |
| } |
| const navCancel = new NavigationCancel(t.id, this.serializeUrl(t.extractedUrl), e.message); |
| eventsSubject.next(navCancel); |
| // When redirecting, we need to delay resolving the navigation |
| // promise and push it to the redirect navigation |
| if (!redirecting) { |
| t.resolve(false); |
| } |
| else { |
| // setTimeout is required so this navigation finishes with |
| // the return EMPTY below. If it isn't allowed to finish |
| // processing, there can be multiple navigations to the same |
| // URL. |
| setTimeout(() => { |
| const mergedTree = this.urlHandlingStrategy.merge(e.url, this.rawUrlTree); |
| const extras = { |
| skipLocationChange: t.extras.skipLocationChange, |
| replaceUrl: this.urlUpdateStrategy === 'eager' |
| }; |
| this.scheduleNavigation(mergedTree, 'imperative', null, extras, { resolve: t.resolve, reject: t.reject, promise: t.promise }); |
| }, 0); |
| } |
| /* All other errors should reset to the router's internal URL reference to |
| * the pre-error state. */ |
| } |
| else { |
| this.resetStateAndUrl(t.currentRouterState, t.currentUrlTree, t.rawUrl); |
| const navError = new NavigationError(t.id, this.serializeUrl(t.extractedUrl), e); |
| eventsSubject.next(navError); |
| try { |
| t.resolve(this.errorHandler(e)); |
| } |
| catch (ee) { |
| t.reject(ee); |
| } |
| } |
| return EMPTY; |
| })); |
| // TODO(jasonaden): remove cast once g3 is on updated TypeScript |
| })); |
| } |
| /** |
| * @internal |
| * TODO: this should be removed once the constructor of the router made internal |
| */ |
| resetRootComponentType(rootComponentType) { |
| this.rootComponentType = rootComponentType; |
| // TODO: vsavkin router 4.0 should make the root component set to null |
| // this will simplify the lifecycle of the router. |
| this.routerState.root.component = this.rootComponentType; |
| } |
| getTransition() { |
| const transition = this.transitions.value; |
| // This value needs to be set. Other values such as extractedUrl are set on initial navigation |
| // but the urlAfterRedirects may not get set if we aren't processing the new URL *and* not |
| // processing the previous URL. |
| transition.urlAfterRedirects = this.browserUrlTree; |
| return transition; |
| } |
| setTransition(t) { |
| this.transitions.next(Object.assign(Object.assign({}, this.getTransition()), t)); |
| } |
| /** |
| * Sets up the location change listener and performs the initial navigation. |
| */ |
| initialNavigation() { |
| this.setUpLocationChangeListener(); |
| if (this.navigationId === 0) { |
| this.navigateByUrl(this.location.path(true), { replaceUrl: true }); |
| } |
| } |
| /** |
| * Sets up the location change listener. This listener detects navigations triggered from outside |
| * the Router (the browser back/forward buttons, for example) and schedules a corresponding Router |
| * navigation so that the correct events, guards, etc. are triggered. |
| */ |
| setUpLocationChangeListener() { |
| // Don't need to use Zone.wrap any more, because zone.js |
| // already patch onPopState, so location change callback will |
| // run into ngZone |
| if (!this.locationSubscription) { |
| this.locationSubscription = this.location.subscribe(event => { |
| const currentChange = this.extractLocationChangeInfoFromEvent(event); |
| if (this.shouldScheduleNavigation(this.lastLocationChangeInfo, currentChange)) { |
| // The `setTimeout` was added in #12160 and is likely to support Angular/AngularJS |
| // hybrid apps. |
| setTimeout(() => { |
| const { source, state, urlTree } = currentChange; |
| const extras = { replaceUrl: true }; |
| if (state) { |
| const stateCopy = Object.assign({}, state); |
| delete stateCopy.navigationId; |
| if (Object.keys(stateCopy).length !== 0) { |
| extras.state = stateCopy; |
| } |
| } |
| this.scheduleNavigation(urlTree, source, state, extras); |
| }, 0); |
| } |
| this.lastLocationChangeInfo = currentChange; |
| }); |
| } |
| } |
| /** Extracts router-related information from a `PopStateEvent`. */ |
| extractLocationChangeInfoFromEvent(change) { |
| var _a; |
| return { |
| source: change['type'] === 'popstate' ? 'popstate' : 'hashchange', |
| urlTree: this.parseUrl(change['url']), |
| // Navigations coming from Angular router have a navigationId state |
| // property. When this exists, restore the state. |
| state: ((_a = change.state) === null || _a === void 0 ? void 0 : _a.navigationId) ? change.state : null, |
| transitionId: this.getTransition().id |
| }; |
| } |
| /** |
| * Determines whether two events triggered by the Location subscription are due to the same |
| * navigation. The location subscription can fire two events (popstate and hashchange) for a |
| * single navigation. The second one should be ignored, that is, we should not schedule another |
| * navigation in the Router. |
| */ |
| shouldScheduleNavigation(previous, current) { |
| if (!previous) |
| return true; |
| const sameDestination = current.urlTree.toString() === previous.urlTree.toString(); |
| const eventsOccurredAtSameTime = current.transitionId === previous.transitionId; |
| if (!eventsOccurredAtSameTime || !sameDestination) { |
| return true; |
| } |
| if ((current.source === 'hashchange' && previous.source === 'popstate') || |
| (current.source === 'popstate' && previous.source === 'hashchange')) { |
| return false; |
| } |
| return true; |
| } |
| /** The current URL. */ |
| get url() { |
| return this.serializeUrl(this.currentUrlTree); |
| } |
| /** |
| * Returns the current `Navigation` object when the router is navigating, |
| * and `null` when idle. |
| */ |
| getCurrentNavigation() { |
| return this.currentNavigation; |
| } |
| /** @internal */ |
| triggerEvent(event) { |
| this.events.next(event); |
| } |
| /** |
| * Resets the route configuration used for navigation and generating links. |
| * |
| * @param config The route array for the new configuration. |
| * |
| * @usageNotes |
| * |
| * ``` |
| * router.resetConfig([ |
| * { path: 'team/:id', component: TeamCmp, children: [ |
| * { path: 'simple', component: SimpleCmp }, |
| * { path: 'user/:name', component: UserCmp } |
| * ]} |
| * ]); |
| * ``` |
| */ |
| resetConfig(config) { |
| validateConfig(config); |
| this.config = config.map(standardizeConfig); |
| this.navigated = false; |
| this.lastSuccessfulId = -1; |
| } |
| /** @nodoc */ |
| ngOnDestroy() { |
| this.dispose(); |
| } |
| /** Disposes of the router. */ |
| dispose() { |
| this.transitions.complete(); |
| if (this.locationSubscription) { |
| this.locationSubscription.unsubscribe(); |
| this.locationSubscription = undefined; |
| } |
| this.disposed = true; |
| } |
| /** |
| * Appends URL segments to the current URL tree to create a new URL tree. |
| * |
| * @param commands An array of URL fragments with which to construct the new URL tree. |
| * If the path is static, can be the literal URL string. For a dynamic path, pass an array of path |
| * segments, followed by the parameters for each segment. |
| * The fragments are applied to the current URL tree or the one provided in the `relativeTo` |
| * property of the options object, if supplied. |
| * @param navigationExtras Options that control the navigation strategy. |
| * @returns The new URL tree. |
| * |
| * @usageNotes |
| * |
| * ``` |
| * // create /team/33/user/11 |
| * router.createUrlTree(['/team', 33, 'user', 11]); |
| * |
| * // create /team/33;expand=true/user/11 |
| * router.createUrlTree(['/team', 33, {expand: true}, 'user', 11]); |
| * |
| * // you can collapse static segments like this (this works only with the first passed-in value): |
| * router.createUrlTree(['/team/33/user', userId]); |
| * |
| * // If the first segment can contain slashes, and you do not want the router to split it, |
| * // you can do the following: |
| * router.createUrlTree([{segmentPath: '/one/two'}]); |
| * |
| * // create /team/33/(user/11//right:chat) |
| * router.createUrlTree(['/team', 33, {outlets: {primary: 'user/11', right: 'chat'}}]); |
| * |
| * // remove the right secondary node |
| * router.createUrlTree(['/team', 33, {outlets: {primary: 'user/11', right: null}}]); |
| * |
| * // assuming the current url is `/team/33/user/11` and the route points to `user/11` |
| * |
| * // navigate to /team/33/user/11/details |
| * router.createUrlTree(['details'], {relativeTo: route}); |
| * |
| * // navigate to /team/33/user/22 |
| * router.createUrlTree(['../22'], {relativeTo: route}); |
| * |
| * // navigate to /team/44/user/22 |
| * router.createUrlTree(['../../team/44/user/22'], {relativeTo: route}); |
| * |
| * Note that a value of `null` or `undefined` for `relativeTo` indicates that the |
| * tree should be created relative to the root. |
| * ``` |
| */ |
| createUrlTree(commands, navigationExtras = {}) { |
| const { relativeTo, queryParams, fragment, queryParamsHandling, preserveFragment } = navigationExtras; |
| const a = relativeTo || this.routerState.root; |
| const f = preserveFragment ? this.currentUrlTree.fragment : fragment; |
| let q = null; |
| switch (queryParamsHandling) { |
| case 'merge': |
| q = Object.assign(Object.assign({}, this.currentUrlTree.queryParams), queryParams); |
| break; |
| case 'preserve': |
| q = this.currentUrlTree.queryParams; |
| break; |
| default: |
| q = queryParams || null; |
| } |
| if (q !== null) { |
| q = this.removeEmptyProps(q); |
| } |
| return createUrlTree(a, this.currentUrlTree, commands, q, f); |
| } |
| /** |
| * Navigates to a view using an absolute route path. |
| * |
| * @param url An absolute path for a defined route. The function does not apply any delta to the |
| * current URL. |
| * @param extras An object containing properties that modify the navigation strategy. |
| * |
| * @returns A Promise that resolves to 'true' when navigation succeeds, |
| * to 'false' when navigation fails, or is rejected on error. |
| * |
| * @usageNotes |
| * |
| * The following calls request navigation to an absolute path. |
| * |
| * ``` |
| * router.navigateByUrl("/team/33/user/11"); |
| * |
| * // Navigate without updating the URL |
| * router.navigateByUrl("/team/33/user/11", { skipLocationChange: true }); |
| * ``` |
| * |
| * @see [Routing and Navigation guide](guide/router) |
| * |
| */ |
| navigateByUrl(url, extras = { |
| skipLocationChange: false |
| }) { |
| if (typeof ngDevMode === 'undefined' || |
| ngDevMode && this.isNgZoneEnabled && !NgZone.isInAngularZone()) { |
| this.console.warn(`Navigation triggered outside Angular zone, did you forget to call 'ngZone.run()'?`); |
| } |
| const urlTree = isUrlTree(url) ? url : this.parseUrl(url); |
| const mergedTree = this.urlHandlingStrategy.merge(urlTree, this.rawUrlTree); |
| return this.scheduleNavigation(mergedTree, 'imperative', null, extras); |
| } |
| /** |
| * Navigate based on the provided array of commands and a starting point. |
| * If no starting route is provided, the navigation is absolute. |
| * |
| * @param commands An array of URL fragments with which to construct the target URL. |
| * If the path is static, can be the literal URL string. For a dynamic path, pass an array of path |
| * segments, followed by the parameters for each segment. |
| * The fragments are applied to the current URL or the one provided in the `relativeTo` property |
| * of the options object, if supplied. |
| * @param extras An options object that determines how the URL should be constructed or |
| * interpreted. |
| * |
| * @returns A Promise that resolves to `true` when navigation succeeds, to `false` when navigation |
| * fails, |
| * or is rejected on error. |
| * |
| * @usageNotes |
| * |
| * The following calls request navigation to a dynamic route path relative to the current URL. |
| * |
| * ``` |
| * router.navigate(['team', 33, 'user', 11], {relativeTo: route}); |
| * |
| * // Navigate without updating the URL, overriding the default behavior |
| * router.navigate(['team', 33, 'user', 11], {relativeTo: route, skipLocationChange: true}); |
| * ``` |
| * |
| * @see [Routing and Navigation guide](guide/router) |
| * |
| */ |
| navigate(commands, extras = { skipLocationChange: false }) { |
| validateCommands(commands); |
| return this.navigateByUrl(this.createUrlTree(commands, extras), extras); |
| } |
| /** Serializes a `UrlTree` into a string */ |
| serializeUrl(url) { |
| return this.urlSerializer.serialize(url); |
| } |
| /** Parses a string into a `UrlTree` */ |
| parseUrl(url) { |
| let urlTree; |
| try { |
| urlTree = this.urlSerializer.parse(url); |
| } |
| catch (e) { |
| urlTree = this.malformedUriErrorHandler(e, this.urlSerializer, url); |
| } |
| return urlTree; |
| } |
| /** Returns whether the url is activated */ |
| isActive(url, exact) { |
| if (isUrlTree(url)) { |
| return containsTree(this.currentUrlTree, url, exact); |
| } |
| const urlTree = this.parseUrl(url); |
| return containsTree(this.currentUrlTree, urlTree, exact); |
| } |
| removeEmptyProps(params) { |
| return Object.keys(params).reduce((result, key) => { |
| const value = params[key]; |
| if (value !== null && value !== undefined) { |
| result[key] = value; |
| } |
| return result; |
| }, {}); |
| } |
| processNavigations() { |
| this.navigations.subscribe(t => { |
| this.navigated = true; |
| this.lastSuccessfulId = t.id; |
| this.events |
| .next(new NavigationEnd(t.id, this.serializeUrl(t.extractedUrl), this.serializeUrl(this.currentUrlTree))); |
| this.lastSuccessfulNavigation = this.currentNavigation; |
| t.resolve(true); |
| }, e => { |
| this.console.warn(`Unhandled Navigation Error: `); |
| }); |
| } |
| scheduleNavigation(rawUrl, source, restoredState, extras, priorPromise) { |
| if (this.disposed) { |
| return Promise.resolve(false); |
| } |
| // * Imperative navigations (router.navigate) might trigger additional navigations to the same |
| // URL via a popstate event and the locationChangeListener. We should skip these duplicate |
| // navs. Duplicates may also be triggered by attempts to sync AngularJS and Angular router |
| // states. |
| // * Imperative navigations can be cancelled by router guards, meaning the URL won't change. If |
| // the user follows that with a navigation using the back/forward button or manual URL change, |
| // the destination may be the same as the previous imperative attempt. We should not skip |
| // these navigations because it's a separate case from the one above -- it's not a duplicate |
| // navigation. |
| const lastNavigation = this.getTransition(); |
| // We don't want to skip duplicate successful navs if they're imperative because |
| // onSameUrlNavigation could be 'reload' (so the duplicate is intended). |
| const browserNavPrecededByRouterNav = source !== 'imperative' && (lastNavigation === null || lastNavigation === void 0 ? void 0 : lastNavigation.source) === 'imperative'; |
| const lastNavigationSucceeded = this.lastSuccessfulId === lastNavigation.id; |
| // If the last navigation succeeded or is in flight, we can use the rawUrl as the comparison. |
| // However, if it failed, we should compare to the final result (urlAfterRedirects). |
| const lastNavigationUrl = (lastNavigationSucceeded || this.currentNavigation) ? |
| lastNavigation.rawUrl : |
| lastNavigation.urlAfterRedirects; |
| const duplicateNav = lastNavigationUrl.toString() === rawUrl.toString(); |
| if (browserNavPrecededByRouterNav && duplicateNav) { |
| return Promise.resolve(true); // return value is not used |
| } |
| let resolve; |
| let reject; |
| let promise; |
| if (priorPromise) { |
| resolve = priorPromise.resolve; |
| reject = priorPromise.reject; |
| promise = priorPromise.promise; |
| } |
| else { |
| promise = new Promise((res, rej) => { |
| resolve = res; |
| reject = rej; |
| }); |
| } |
| const id = ++this.navigationId; |
| this.setTransition({ |
| id, |
| source, |
| restoredState, |
| currentUrlTree: this.currentUrlTree, |
| currentRawUrl: this.rawUrlTree, |
| rawUrl, |
| extras, |
| resolve, |
| reject, |
| promise, |
| currentSnapshot: this.routerState.snapshot, |
| currentRouterState: this.routerState |
| }); |
| // Make sure that the error is propagated even though `processNavigations` catch |
| // handler does not rethrow |
| return promise.catch((e) => { |
| return Promise.reject(e); |
| }); |
| } |
| setBrowserUrl(url, replaceUrl, id, state) { |
| const path = this.urlSerializer.serialize(url); |
| state = state || {}; |
| if (this.location.isCurrentPathEqualTo(path) || replaceUrl) { |
| // TODO(jasonaden): Remove first `navigationId` and rely on `ng` namespace. |
| this.location.replaceState(path, '', Object.assign(Object.assign({}, state), { navigationId: id })); |
| } |
| else { |
| this.location.go(path, '', Object.assign(Object.assign({}, state), { navigationId: id })); |
| } |
| } |
| resetStateAndUrl(storedState, storedUrl, rawUrl) { |
| this.routerState = storedState; |
| this.currentUrlTree = storedUrl; |
| this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, rawUrl); |
| this.resetUrlToCurrentUrlTree(); |
| } |
| resetUrlToCurrentUrlTree() { |
| this.location.replaceState(this.urlSerializer.serialize(this.rawUrlTree), '', { navigationId: this.lastSuccessfulId }); |
| } |
| } |
| Router.decorators = [ |
| { type: Injectable } |
| ]; |
| Router.ctorParameters = () => [ |
| { type: Type }, |
| { type: UrlSerializer }, |
| { type: ChildrenOutletContexts }, |
| { type: Location }, |
| { type: Injector }, |
| { type: NgModuleFactoryLoader }, |
| { type: Compiler }, |
| { type: undefined } |
| ]; |
| function validateCommands(commands) { |
| for (let i = 0; i < commands.length; i++) { |
| const cmd = commands[i]; |
| if (cmd == null) { |
| throw new Error(`The requested path contains ${cmd} segment at index ${i}`); |
| } |
| } |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * @description |
| * |
| * When applied to an element in a template, makes that element a link |
| * that initiates navigation to a route. Navigation opens one or more routed components |
| * in one or more `<router-outlet>` locations on the page. |
| * |
| * Given a route configuration `[{ path: 'user/:name', component: UserCmp }]`, |
| * the following creates a static link to the route: |
| * `<a routerLink="/user/bob">link to user component</a>` |
| * |
| * You can use dynamic values to generate the link. |
| * For a dynamic link, pass an array of path segments, |
| * followed by the params for each segment. |
| * For example, `['/team', teamId, 'user', userName, {details: true}]` |
| * generates a link to `/team/11/user/bob;details=true`. |
| * |
| * Multiple static segments can be merged into one term and combined with dynamic segements. |
| * For example, `['/team/11/user', userName, {details: true}]` |
| * |
| * The input that you provide to the link is treated as a delta to the current URL. |
| * For instance, suppose the current URL is `/user/(box//aux:team)`. |
| * The link `<a [routerLink]="['/user/jim']">Jim</a>` creates the URL |
| * `/user/(jim//aux:team)`. |
| * See {@link Router#createUrlTree createUrlTree} for more information. |
| * |
| * @usageNotes |
| * |
| * You can use absolute or relative paths in a link, set query parameters, |
| * control how parameters are handled, and keep a history of navigation states. |
| * |
| * ### Relative link paths |
| * |
| * The first segment name can be prepended with `/`, `./`, or `../`. |
| * * If the first segment begins with `/`, the router looks up the route from the root of the |
| * app. |
| * * If the first segment begins with `./`, or doesn't begin with a slash, the router |
| * looks in the children of the current activated route. |
| * * If the first segment begins with `../`, the router goes up one level in the route tree. |
| * |
| * ### Setting and handling query params and fragments |
| * |
| * The following link adds a query parameter and a fragment to the generated URL: |
| * |
| * ``` |
| * <a [routerLink]="['/user/bob']" [queryParams]="{debug: true}" fragment="education"> |
| * link to user component |
| * </a> |
| * ``` |
| * By default, the directive constructs the new URL using the given query parameters. |
| * The example generates the link: `/user/bob?debug=true#education`. |
| * |
| * You can instruct the directive to handle query parameters differently |
| * by specifying the `queryParamsHandling` option in the link. |
| * Allowed values are: |
| * |
| * - `'merge'`: Merge the given `queryParams` into the current query params. |
| * - `'preserve'`: Preserve the current query params. |
| * |
| * For example: |
| * |
| * ``` |
| * <a [routerLink]="['/user/bob']" [queryParams]="{debug: true}" queryParamsHandling="merge"> |
| * link to user component |
| * </a> |
| * ``` |
| * |
| * See {@link UrlCreationOptions.queryParamsHandling UrlCreationOptions#queryParamsHandling}. |
| * |
| * ### Preserving navigation history |
| * |
| * You can provide a `state` value to be persisted to the browser's |
| * [`History.state` property](https://developer.mozilla.org/en-US/docs/Web/API/History#Properties). |
| * For example: |
| * |
| * ``` |
| * <a [routerLink]="['/user/bob']" [state]="{tracingId: 123}"> |
| * link to user component |
| * </a> |
| * ``` |
| * |
| * Use {@link Router.getCurrentNavigation() Router#getCurrentNavigation} to retrieve a saved |
| * navigation-state value. For example, to capture the `tracingId` during the `NavigationStart` |
| * event: |
| * |
| * ``` |
| * // Get NavigationStart events |
| * router.events.pipe(filter(e => e instanceof NavigationStart)).subscribe(e => { |
| * const navigation = router.getCurrentNavigation(); |
| * tracingService.trace({id: navigation.extras.state.tracingId}); |
| * }); |
| * ``` |
| * |
| * @ngModule RouterModule |
| * |
| * @publicApi |
| */ |
| class RouterLink { |
| constructor(router, route, tabIndex, renderer, el) { |
| this.router = router; |
| this.route = route; |
| this.commands = []; |
| /** @internal */ |
| this.onChanges = new Subject(); |
| if (tabIndex == null) { |
| renderer.setAttribute(el.nativeElement, 'tabindex', '0'); |
| } |
| } |
| /** @nodoc */ |
| ngOnChanges(changes) { |
| // This is subscribed to by `RouterLinkActive` so that it knows to update when there are changes |
| // to the RouterLinks it's tracking. |
| this.onChanges.next(this); |
| } |
| /** |
| * Commands to pass to {@link Router#createUrlTree Router#createUrlTree}. |
| * - **array**: commands to pass to {@link Router#createUrlTree Router#createUrlTree}. |
| * - **string**: shorthand for array of commands with just the string, i.e. `['/route']` |
| * - **null|undefined**: shorthand for an empty array of commands, i.e. `[]` |
| * @see {@link Router#createUrlTree Router#createUrlTree} |
| */ |
| set routerLink(commands) { |
| if (commands != null) { |
| this.commands = Array.isArray(commands) ? commands : [commands]; |
| } |
| else { |
| this.commands = []; |
| } |
| } |
| /** @nodoc */ |
| onClick() { |
| const extras = { |
| skipLocationChange: attrBoolValue(this.skipLocationChange), |
| replaceUrl: attrBoolValue(this.replaceUrl), |
| state: this.state, |
| }; |
| this.router.navigateByUrl(this.urlTree, extras); |
| return true; |
| } |
| get urlTree() { |
| return this.router.createUrlTree(this.commands, { |
| // If the `relativeTo` input is not defined, we want to use `this.route` by default. |
| // Otherwise, we should use the value provided by the user in the input. |
| relativeTo: this.relativeTo !== undefined ? this.relativeTo : this.route, |
| queryParams: this.queryParams, |
| fragment: this.fragment, |
| queryParamsHandling: this.queryParamsHandling, |
| preserveFragment: attrBoolValue(this.preserveFragment), |
| }); |
| } |
| } |
| RouterLink.decorators = [ |
| { type: Directive, args: [{ selector: ':not(a):not(area)[routerLink]' },] } |
| ]; |
| RouterLink.ctorParameters = () => [ |
| { type: Router }, |
| { type: ActivatedRoute }, |
| { type: String, decorators: [{ type: Attribute, args: ['tabindex',] }] }, |
| { type: Renderer2 }, |
| { type: ElementRef } |
| ]; |
| RouterLink.propDecorators = { |
| queryParams: [{ type: Input }], |
| fragment: [{ type: Input }], |
| queryParamsHandling: [{ type: Input }], |
| preserveFragment: [{ type: Input }], |
| skipLocationChange: [{ type: Input }], |
| replaceUrl: [{ type: Input }], |
| state: [{ type: Input }], |
| relativeTo: [{ type: Input }], |
| routerLink: [{ type: Input }], |
| onClick: [{ type: HostListener, args: ['click',] }] |
| }; |
| /** |
| * @description |
| * |
| * Lets you link to specific routes in your app. |
| * |
| * See `RouterLink` for more information. |
| * |
| * @ngModule RouterModule |
| * |
| * @publicApi |
| */ |
| class RouterLinkWithHref { |
| constructor(router, route, locationStrategy) { |
| this.router = router; |
| this.route = route; |
| this.locationStrategy = locationStrategy; |
| this.commands = []; |
| /** @internal */ |
| this.onChanges = new Subject(); |
| this.subscription = router.events.subscribe((s) => { |
| if (s instanceof NavigationEnd) { |
| this.updateTargetUrlAndHref(); |
| } |
| }); |
| } |
| /** |
| * Commands to pass to {@link Router#createUrlTree Router#createUrlTree}. |
| * - **array**: commands to pass to {@link Router#createUrlTree Router#createUrlTree}. |
| * - **string**: shorthand for array of commands with just the string, i.e. `['/route']` |
| * - **null|undefined**: shorthand for an empty array of commands, i.e. `[]` |
| * @see {@link Router#createUrlTree Router#createUrlTree} |
| */ |
| set routerLink(commands) { |
| if (commands != null) { |
| this.commands = Array.isArray(commands) ? commands : [commands]; |
| } |
| else { |
| this.commands = []; |
| } |
| } |
| /** @nodoc */ |
| ngOnChanges(changes) { |
| this.updateTargetUrlAndHref(); |
| this.onChanges.next(this); |
| } |
| /** @nodoc */ |
| ngOnDestroy() { |
| this.subscription.unsubscribe(); |
| } |
| /** @nodoc */ |
| onClick(button, ctrlKey, shiftKey, altKey, metaKey) { |
| if (button !== 0 || ctrlKey || shiftKey || altKey || metaKey) { |
| return true; |
| } |
| if (typeof this.target === 'string' && this.target != '_self') { |
| return true; |
| } |
| const extras = { |
| skipLocationChange: attrBoolValue(this.skipLocationChange), |
| replaceUrl: attrBoolValue(this.replaceUrl), |
| state: this.state |
| }; |
| this.router.navigateByUrl(this.urlTree, extras); |
| return false; |
| } |
| updateTargetUrlAndHref() { |
| this.href = this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.urlTree)); |
| } |
| get urlTree() { |
| return this.router.createUrlTree(this.commands, { |
| // If the `relativeTo` input is not defined, we want to use `this.route` by default. |
| // Otherwise, we should use the value provided by the user in the input. |
| relativeTo: this.relativeTo !== undefined ? this.relativeTo : this.route, |
| queryParams: this.queryParams, |
| fragment: this.fragment, |
| queryParamsHandling: this.queryParamsHandling, |
| preserveFragment: attrBoolValue(this.preserveFragment), |
| }); |
| } |
| } |
| RouterLinkWithHref.decorators = [ |
| { type: Directive, args: [{ selector: 'a[routerLink],area[routerLink]' },] } |
| ]; |
| RouterLinkWithHref.ctorParameters = () => [ |
| { type: Router }, |
| { type: ActivatedRoute }, |
| { type: LocationStrategy } |
| ]; |
| RouterLinkWithHref.propDecorators = { |
| target: [{ type: HostBinding, args: ['attr.target',] }, { type: Input }], |
| queryParams: [{ type: Input }], |
| fragment: [{ type: Input }], |
| queryParamsHandling: [{ type: Input }], |
| preserveFragment: [{ type: Input }], |
| skipLocationChange: [{ type: Input }], |
| replaceUrl: [{ type: Input }], |
| state: [{ type: Input }], |
| relativeTo: [{ type: Input }], |
| href: [{ type: HostBinding }], |
| routerLink: [{ type: Input }], |
| onClick: [{ type: HostListener, args: ['click', |
| ['$event.button', '$event.ctrlKey', '$event.shiftKey', '$event.altKey', '$event.metaKey'],] }] |
| }; |
| function attrBoolValue(s) { |
| return s === '' || !!s; |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * |
| * @description |
| * |
| * Tracks whether the linked route of an element is currently active, and allows you |
| * to specify one or more CSS classes to add to the element when the linked route |
| * is active. |
| * |
| * Use this directive to create a visual distinction for elements associated with an active route. |
| * For example, the following code highlights the word "Bob" when the router |
| * activates the associated route: |
| * |
| * ``` |
| * <a routerLink="/user/bob" routerLinkActive="active-link">Bob</a> |
| * ``` |
| * |
| * Whenever the URL is either '/user' or '/user/bob', the "active-link" class is |
| * added to the anchor tag. If the URL changes, the class is removed. |
| * |
| * You can set more than one class using a space-separated string or an array. |
| * For example: |
| * |
| * ``` |
| * <a routerLink="/user/bob" routerLinkActive="class1 class2">Bob</a> |
| * <a routerLink="/user/bob" [routerLinkActive]="['class1', 'class2']">Bob</a> |
| * ``` |
| * |
| * To add the classes only when the URL matches the link exactly, add the option `exact: true`: |
| * |
| * ``` |
| * <a routerLink="/user/bob" routerLinkActive="active-link" [routerLinkActiveOptions]="{exact: |
| * true}">Bob</a> |
| * ``` |
| * |
| * To directly check the `isActive` status of the link, assign the `RouterLinkActive` |
| * instance to a template variable. |
| * For example, the following checks the status without assigning any CSS classes: |
| * |
| * ``` |
| * <a routerLink="/user/bob" routerLinkActive #rla="routerLinkActive"> |
| * Bob {{ rla.isActive ? '(already open)' : ''}} |
| * </a> |
| * ``` |
| * |
| * You can apply the `RouterLinkActive` directive to an ancestor of linked elements. |
| * For example, the following sets the active-link class on the `<div>` parent tag |
| * when the URL is either '/user/jim' or '/user/bob'. |
| * |
| * ``` |
| * <div routerLinkActive="active-link" [routerLinkActiveOptions]="{exact: true}"> |
| * <a routerLink="/user/jim">Jim</a> |
| * <a routerLink="/user/bob">Bob</a> |
| * </div> |
| * ``` |
| * |
| * @ngModule RouterModule |
| * |
| * @publicApi |
| */ |
| class RouterLinkActive { |
| constructor(router, element, renderer, cdr, link, linkWithHref) { |
| this.router = router; |
| this.element = element; |
| this.renderer = renderer; |
| this.cdr = cdr; |
| this.link = link; |
| this.linkWithHref = linkWithHref; |
| this.classes = []; |
| this.isActive = false; |
| this.routerLinkActiveOptions = { exact: false }; |
| this.routerEventsSubscription = router.events.subscribe((s) => { |
| if (s instanceof NavigationEnd) { |
| this.update(); |
| } |
| }); |
| } |
| /** @nodoc */ |
| ngAfterContentInit() { |
| // `of(null)` is used to force subscribe body to execute once immediately (like `startWith`). |
| of(this.links.changes, this.linksWithHrefs.changes, of(null)).pipe(mergeAll()).subscribe(_ => { |
| this.update(); |
| this.subscribeToEachLinkOnChanges(); |
| }); |
| } |
| subscribeToEachLinkOnChanges() { |
| var _a; |
| (_a = this.linkInputChangesSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe(); |
| const allLinkChanges = [...this.links.toArray(), ...this.linksWithHrefs.toArray(), this.link, this.linkWithHref] |
| .filter((link) => !!link) |
| .map(link => link.onChanges); |
| this.linkInputChangesSubscription = from(allLinkChanges).pipe(mergeAll()).subscribe(link => { |
| if (this.isActive !== this.isLinkActive(this.router)(link)) { |
| this.update(); |
| } |
| }); |
| } |
| set routerLinkActive(data) { |
| const classes = Array.isArray(data) ? data : data.split(' '); |
| this.classes = classes.filter(c => !!c); |
| } |
| /** @nodoc */ |
| ngOnChanges(changes) { |
| this.update(); |
| } |
| /** @nodoc */ |
| ngOnDestroy() { |
| var _a; |
| this.routerEventsSubscription.unsubscribe(); |
| (_a = this.linkInputChangesSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe(); |
| } |
| update() { |
| if (!this.links || !this.linksWithHrefs || !this.router.navigated) |
| return; |
| Promise.resolve().then(() => { |
| const hasActiveLinks = this.hasActiveLinks(); |
| if (this.isActive !== hasActiveLinks) { |
| this.isActive = hasActiveLinks; |
| this.cdr.markForCheck(); |
| this.classes.forEach((c) => { |
| if (hasActiveLinks) { |
| this.renderer.addClass(this.element.nativeElement, c); |
| } |
| else { |
| this.renderer.removeClass(this.element.nativeElement, c); |
| } |
| }); |
| } |
| }); |
| } |
| isLinkActive(router) { |
| return (link) => router.isActive(link.urlTree, this.routerLinkActiveOptions.exact); |
| } |
| hasActiveLinks() { |
| const isActiveCheckFn = this.isLinkActive(this.router); |
| return this.link && isActiveCheckFn(this.link) || |
| this.linkWithHref && isActiveCheckFn(this.linkWithHref) || |
| this.links.some(isActiveCheckFn) || this.linksWithHrefs.some(isActiveCheckFn); |
| } |
| } |
| RouterLinkActive.decorators = [ |
| { type: Directive, args: [{ |
| selector: '[routerLinkActive]', |
| exportAs: 'routerLinkActive', |
| },] } |
| ]; |
| RouterLinkActive.ctorParameters = () => [ |
| { type: Router }, |
| { type: ElementRef }, |
| { type: Renderer2 }, |
| { type: ChangeDetectorRef }, |
| { type: RouterLink, decorators: [{ type: Optional }] }, |
| { type: RouterLinkWithHref, decorators: [{ type: Optional }] } |
| ]; |
| RouterLinkActive.propDecorators = { |
| links: [{ type: ContentChildren, args: [RouterLink, { descendants: true },] }], |
| linksWithHrefs: [{ type: ContentChildren, args: [RouterLinkWithHref, { descendants: true },] }], |
| routerLinkActiveOptions: [{ type: Input }], |
| routerLinkActive: [{ type: Input }] |
| }; |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * @description |
| * |
| * Acts as a placeholder that Angular dynamically fills based on the current router state. |
| * |
| * Each outlet can have a unique name, determined by the optional `name` attribute. |
| * The name cannot be set or changed dynamically. If not set, default value is "primary". |
| * |
| * ``` |
| * <router-outlet></router-outlet> |
| * <router-outlet name='left'></router-outlet> |
| * <router-outlet name='right'></router-outlet> |
| * ``` |
| * |
| * Named outlets can be the targets of secondary routes. |
| * The `Route` object for a secondary route has an `outlet` property to identify the target outlet: |
| * |
| * `{path: <base-path>, component: <component>, outlet: <target_outlet_name>}` |
| * |
| * Using named outlets and secondary routes, you can target multiple outlets in |
| * the same `RouterLink` directive. |
| * |
| * The router keeps track of separate branches in a navigation tree for each named outlet and |
| * generates a representation of that tree in the URL. |
| * The URL for a secondary route uses the following syntax to specify both the primary and secondary |
| * routes at the same time: |
| * |
| * `http://base-path/primary-route-path(outlet-name:route-path)` |
| * |
| * A router outlet emits an activate event when a new component is instantiated, |
| * and a deactivate event when a component is destroyed. |
| * |
| * ``` |
| * <router-outlet |
| * (activate)='onActivate($event)' |
| * (deactivate)='onDeactivate($event)'></router-outlet> |
| * ``` |
| * |
| * @see [Routing tutorial](guide/router-tutorial-toh#named-outlets "Example of a named |
| * outlet and secondary route configuration"). |
| * @see `RouterLink` |
| * @see `Route` |
| * @ngModule RouterModule |
| * |
| * @publicApi |
| */ |
| class RouterOutlet { |
| constructor(parentContexts, location, resolver, name, changeDetector) { |
| this.parentContexts = parentContexts; |
| this.location = location; |
| this.resolver = resolver; |
| this.changeDetector = changeDetector; |
| this.activated = null; |
| this._activatedRoute = null; |
| this.activateEvents = new EventEmitter(); |
| this.deactivateEvents = new EventEmitter(); |
| this.name = name || PRIMARY_OUTLET; |
| parentContexts.onChildOutletCreated(this.name, this); |
| } |
| /** @nodoc */ |
| ngOnDestroy() { |
| this.parentContexts.onChildOutletDestroyed(this.name); |
| } |
| /** @nodoc */ |
| ngOnInit() { |
| if (!this.activated) { |
| // If the outlet was not instantiated at the time the route got activated we need to populate |
| // the outlet when it is initialized (ie inside a NgIf) |
| const context = this.parentContexts.getContext(this.name); |
| if (context && context.route) { |
| if (context.attachRef) { |
| // `attachRef` is populated when there is an existing component to mount |
| this.attach(context.attachRef, context.route); |
| } |
| else { |
| // otherwise the component defined in the configuration is created |
| this.activateWith(context.route, context.resolver || null); |
| } |
| } |
| } |
| } |
| get isActivated() { |
| return !!this.activated; |
| } |
| get component() { |
| if (!this.activated) |
| throw new Error('Outlet is not activated'); |
| return this.activated.instance; |
| } |
| get activatedRoute() { |
| if (!this.activated) |
| throw new Error('Outlet is not activated'); |
| return this._activatedRoute; |
| } |
| get activatedRouteData() { |
| if (this._activatedRoute) { |
| return this._activatedRoute.snapshot.data; |
| } |
| return {}; |
| } |
| /** |
| * Called when the `RouteReuseStrategy` instructs to detach the subtree |
| */ |
| detach() { |
| if (!this.activated) |
| throw new Error('Outlet is not activated'); |
| this.location.detach(); |
| const cmp = this.activated; |
| this.activated = null; |
| this._activatedRoute = null; |
| return cmp; |
| } |
| /** |
| * Called when the `RouteReuseStrategy` instructs to re-attach a previously detached subtree |
| */ |
| attach(ref, activatedRoute) { |
| this.activated = ref; |
| this._activatedRoute = activatedRoute; |
| this.location.insert(ref.hostView); |
| } |
| deactivate() { |
| if (this.activated) { |
| const c = this.component; |
| this.activated.destroy(); |
| this.activated = null; |
| this._activatedRoute = null; |
| this.deactivateEvents.emit(c); |
| } |
| } |
| activateWith(activatedRoute, resolver) { |
| if (this.isActivated) { |
| throw new Error('Cannot activate an already activated outlet'); |
| } |
| this._activatedRoute = activatedRoute; |
| const snapshot = activatedRoute._futureSnapshot; |
| const component = snapshot.routeConfig.component; |
| resolver = resolver || this.resolver; |
| const factory = resolver.resolveComponentFactory(component); |
| const childContexts = this.parentContexts.getOrCreateContext(this.name).children; |
| const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector); |
| this.activated = this.location.createComponent(factory, this.location.length, injector); |
| // Calling `markForCheck` to make sure we will run the change detection when the |
| // `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component. |
| this.changeDetector.markForCheck(); |
| this.activateEvents.emit(this.activated.instance); |
| } |
| } |
| RouterOutlet.decorators = [ |
| { type: Directive, args: [{ selector: 'router-outlet', exportAs: 'outlet' },] } |
| ]; |
| RouterOutlet.ctorParameters = () => [ |
| { type: ChildrenOutletContexts }, |
| { type: ViewContainerRef }, |
| { type: ComponentFactoryResolver }, |
| { type: String, decorators: [{ type: Attribute, args: ['name',] }] }, |
| { type: ChangeDetectorRef } |
| ]; |
| RouterOutlet.propDecorators = { |
| activateEvents: [{ type: Output, args: ['activate',] }], |
| deactivateEvents: [{ type: Output, args: ['deactivate',] }] |
| }; |
| class OutletInjector { |
| constructor(route, childContexts, parent) { |
| this.route = route; |
| this.childContexts = childContexts; |
| this.parent = parent; |
| } |
| get(token, notFoundValue) { |
| if (token === ActivatedRoute) { |
| return this.route; |
| } |
| if (token === ChildrenOutletContexts) { |
| return this.childContexts; |
| } |
| return this.parent.get(token, notFoundValue); |
| } |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * @description |
| * |
| * Provides a preloading strategy. |
| * |
| * @publicApi |
| */ |
| class PreloadingStrategy { |
| } |
| /** |
| * @description |
| * |
| * Provides a preloading strategy that preloads all modules as quickly as possible. |
| * |
| * ``` |
| * RouterModule.forRoot(ROUTES, {preloadingStrategy: PreloadAllModules}) |
| * ``` |
| * |
| * @publicApi |
| */ |
| class PreloadAllModules { |
| preload(route, fn) { |
| return fn().pipe(catchError(() => of(null))); |
| } |
| } |
| /** |
| * @description |
| * |
| * Provides a preloading strategy that does not preload any modules. |
| * |
| * This strategy is enabled by default. |
| * |
| * @publicApi |
| */ |
| class NoPreloading { |
| preload(route, fn) { |
| return of(null); |
| } |
| } |
| /** |
| * The preloader optimistically loads all router configurations to |
| * make navigations into lazily-loaded sections of the application faster. |
| * |
| * The preloader runs in the background. When the router bootstraps, the preloader |
| * starts listening to all navigation events. After every such event, the preloader |
| * will check if any configurations can be loaded lazily. |
| * |
| * If a route is protected by `canLoad` guards, the preloaded will not load it. |
| * |
| * @publicApi |
| */ |
| class RouterPreloader { |
| constructor(router, moduleLoader, compiler, injector, preloadingStrategy) { |
| this.router = router; |
| this.injector = injector; |
| this.preloadingStrategy = preloadingStrategy; |
| const onStartLoad = (r) => router.triggerEvent(new RouteConfigLoadStart(r)); |
| const onEndLoad = (r) => router.triggerEvent(new RouteConfigLoadEnd(r)); |
| this.loader = new RouterConfigLoader(moduleLoader, compiler, onStartLoad, onEndLoad); |
| } |
| setUpPreloading() { |
| this.subscription = |
| this.router.events |
| .pipe(filter((e) => e instanceof NavigationEnd), concatMap(() => this.preload())) |
| .subscribe(() => { }); |
| } |
| preload() { |
| const ngModule = this.injector.get(NgModuleRef); |
| return this.processRoutes(ngModule, this.router.config); |
| } |
| /** @nodoc */ |
| ngOnDestroy() { |
| if (this.subscription) { |
| this.subscription.unsubscribe(); |
| } |
| } |
| processRoutes(ngModule, routes) { |
| const res = []; |
| for (const route of routes) { |
| // we already have the config loaded, just recurse |
| if (route.loadChildren && !route.canLoad && route._loadedConfig) { |
| const childConfig = route._loadedConfig; |
| res.push(this.processRoutes(childConfig.module, childConfig.routes)); |
| // no config loaded, fetch the config |
| } |
| else if (route.loadChildren && !route.canLoad) { |
| res.push(this.preloadConfig(ngModule, route)); |
| // recurse into children |
| } |
| else if (route.children) { |
| res.push(this.processRoutes(ngModule, route.children)); |
| } |
| } |
| return from(res).pipe(mergeAll(), map((_) => void 0)); |
| } |
| preloadConfig(ngModule, route) { |
| return this.preloadingStrategy.preload(route, () => { |
| const loaded$ = route._loadedConfig ? of(route._loadedConfig) : |
| this.loader.load(ngModule.injector, route); |
| return loaded$.pipe(mergeMap((config) => { |
| route._loadedConfig = config; |
| return this.processRoutes(config.module, config.routes); |
| })); |
| }); |
| } |
| } |
| RouterPreloader.decorators = [ |
| { type: Injectable } |
| ]; |
| RouterPreloader.ctorParameters = () => [ |
| { type: Router }, |
| { type: NgModuleFactoryLoader }, |
| { type: Compiler }, |
| { type: Injector }, |
| { type: PreloadingStrategy } |
| ]; |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| class RouterScroller { |
| constructor(router, |
| /** @docsNotRequired */ viewportScroller, options = {}) { |
| this.router = router; |
| this.viewportScroller = viewportScroller; |
| this.options = options; |
| this.lastId = 0; |
| this.lastSource = 'imperative'; |
| this.restoredId = 0; |
| this.store = {}; |
| // Default both options to 'disabled' |
| options.scrollPositionRestoration = options.scrollPositionRestoration || 'disabled'; |
| options.anchorScrolling = options.anchorScrolling || 'disabled'; |
| } |
| init() { |
| // we want to disable the automatic scrolling because having two places |
| // responsible for scrolling results race conditions, especially given |
| // that browser don't implement this behavior consistently |
| if (this.options.scrollPositionRestoration !== 'disabled') { |
| this.viewportScroller.setHistoryScrollRestoration('manual'); |
| } |
| this.routerEventsSubscription = this.createScrollEvents(); |
| this.scrollEventsSubscription = this.consumeScrollEvents(); |
| } |
| createScrollEvents() { |
| return this.router.events.subscribe(e => { |
| if (e instanceof NavigationStart) { |
| // store the scroll position of the current stable navigations. |
| this.store[this.lastId] = this.viewportScroller.getScrollPosition(); |
| this.lastSource = e.navigationTrigger; |
| this.restoredId = e.restoredState ? e.restoredState.navigationId : 0; |
| } |
| else if (e instanceof NavigationEnd) { |
| this.lastId = e.id; |
| this.scheduleScrollEvent(e, this.router.parseUrl(e.urlAfterRedirects).fragment); |
| } |
| }); |
| } |
| consumeScrollEvents() { |
| return this.router.events.subscribe(e => { |
| if (!(e instanceof Scroll)) |
| return; |
| // a popstate event. The pop state event will always ignore anchor scrolling. |
| if (e.position) { |
| if (this.options.scrollPositionRestoration === 'top') { |
| this.viewportScroller.scrollToPosition([0, 0]); |
| } |
| else if (this.options.scrollPositionRestoration === 'enabled') { |
| this.viewportScroller.scrollToPosition(e.position); |
| } |
| // imperative navigation "forward" |
| } |
| else { |
| if (e.anchor && this.options.anchorScrolling === 'enabled') { |
| this.viewportScroller.scrollToAnchor(e.anchor); |
| } |
| else if (this.options.scrollPositionRestoration !== 'disabled') { |
| this.viewportScroller.scrollToPosition([0, 0]); |
| } |
| } |
| }); |
| } |
| scheduleScrollEvent(routerEvent, anchor) { |
| this.router.triggerEvent(new Scroll(routerEvent, this.lastSource === 'popstate' ? this.store[this.restoredId] : null, anchor)); |
| } |
| /** @nodoc */ |
| ngOnDestroy() { |
| if (this.routerEventsSubscription) { |
| this.routerEventsSubscription.unsubscribe(); |
| } |
| if (this.scrollEventsSubscription) { |
| this.scrollEventsSubscription.unsubscribe(); |
| } |
| } |
| } |
| RouterScroller.decorators = [ |
| { type: Injectable } |
| ]; |
| RouterScroller.ctorParameters = () => [ |
| { type: Router }, |
| { type: ViewportScroller }, |
| { type: undefined } |
| ]; |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * The directives defined in the `RouterModule`. |
| */ |
| const ROUTER_DIRECTIVES = [RouterOutlet, RouterLink, RouterLinkWithHref, RouterLinkActive, ɵEmptyOutletComponent]; |
| /** |
| * A [DI token](guide/glossary/#di-token) for the router service. |
| * |
| * @publicApi |
| */ |
| const ROUTER_CONFIGURATION = new InjectionToken('ROUTER_CONFIGURATION'); |
| /** |
| * @docsNotRequired |
| */ |
| const ROUTER_FORROOT_GUARD = new InjectionToken('ROUTER_FORROOT_GUARD'); |
| const ɵ0 = { enableTracing: false }; |
| const ROUTER_PROVIDERS = [ |
| Location, |
| { provide: UrlSerializer, useClass: DefaultUrlSerializer }, |
| { |
| provide: Router, |
| useFactory: setupRouter, |
| deps: [ |
| UrlSerializer, ChildrenOutletContexts, Location, Injector, NgModuleFactoryLoader, Compiler, |
| ROUTES, ROUTER_CONFIGURATION, [UrlHandlingStrategy, new Optional()], |
| [RouteReuseStrategy, new Optional()] |
| ] |
| }, |
| ChildrenOutletContexts, |
| { provide: ActivatedRoute, useFactory: rootRoute, deps: [Router] }, |
| { provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader }, |
| RouterPreloader, |
| NoPreloading, |
| PreloadAllModules, |
| { provide: ROUTER_CONFIGURATION, useValue: ɵ0 }, |
| ]; |
| function routerNgProbeToken() { |
| return new NgProbeToken('Router', Router); |
| } |
| /** |
| * @description |
| * |
| * Adds directives and providers for in-app navigation among views defined in an application. |
| * Use the Angular `Router` service to declaratively specify application states and manage state |
| * transitions. |
| * |
| * You can import this NgModule multiple times, once for each lazy-loaded bundle. |
| * However, only one `Router` service can be active. |
| * To ensure this, there are two ways to register routes when importing this module: |
| * |
| * * The `forRoot()` method creates an `NgModule` that contains all the directives, the given |
| * routes, and the `Router` service itself. |
| * * The `forChild()` method creates an `NgModule` that contains all the directives and the given |
| * routes, but does not include the `Router` service. |
| * |
| * @see [Routing and Navigation guide](guide/router) for an |
| * overview of how the `Router` service should be used. |
| * |
| * @publicApi |
| */ |
| class RouterModule { |
| // Note: We are injecting the Router so it gets created eagerly... |
| constructor(guard, router) { } |
| /** |
| * Creates and configures a module with all the router providers and directives. |
| * Optionally sets up an application listener to perform an initial navigation. |
| * |
| * When registering the NgModule at the root, import as follows: |
| * |
| * ``` |
| * @NgModule({ |
| * imports: [RouterModule.forRoot(ROUTES)] |
| * }) |
| * class MyNgModule {} |
| * ``` |
| * |
| * @param routes An array of `Route` objects that define the navigation paths for the application. |
| * @param config An `ExtraOptions` configuration object that controls how navigation is performed. |
| * @return The new `NgModule`. |
| * |
| */ |
| static forRoot(routes, config) { |
| return { |
| ngModule: RouterModule, |
| providers: [ |
| ROUTER_PROVIDERS, |
| provideRoutes(routes), |
| { |
| provide: ROUTER_FORROOT_GUARD, |
| useFactory: provideForRootGuard, |
| deps: [[Router, new Optional(), new SkipSelf()]] |
| }, |
| { provide: ROUTER_CONFIGURATION, useValue: config ? config : {} }, |
| { |
| provide: LocationStrategy, |
| useFactory: provideLocationStrategy, |
| deps: [PlatformLocation, [new Inject(APP_BASE_HREF), new Optional()], ROUTER_CONFIGURATION] |
| }, |
| { |
| provide: RouterScroller, |
| useFactory: createRouterScroller, |
| deps: [Router, ViewportScroller, ROUTER_CONFIGURATION] |
| }, |
| { |
| provide: PreloadingStrategy, |
| useExisting: config && config.preloadingStrategy ? config.preloadingStrategy : |
| NoPreloading |
| }, |
| { provide: NgProbeToken, multi: true, useFactory: routerNgProbeToken }, |
| provideRouterInitializer(), |
| ], |
| }; |
| } |
| /** |
| * Creates a module with all the router directives and a provider registering routes, |
| * without creating a new Router service. |
| * When registering for submodules and lazy-loaded submodules, create the NgModule as follows: |
| * |
| * ``` |
| * @NgModule({ |
| * imports: [RouterModule.forChild(ROUTES)] |
| * }) |
| * class MyNgModule {} |
| * ``` |
| * |
| * @param routes An array of `Route` objects that define the navigation paths for the submodule. |
| * @return The new NgModule. |
| * |
| */ |
| static forChild(routes) { |
| return { ngModule: RouterModule, providers: [provideRoutes(routes)] }; |
| } |
| } |
| RouterModule.decorators = [ |
| { type: NgModule, args: [{ |
| declarations: ROUTER_DIRECTIVES, |
| exports: ROUTER_DIRECTIVES, |
| entryComponents: [ɵEmptyOutletComponent] |
| },] } |
| ]; |
| RouterModule.ctorParameters = () => [ |
| { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [ROUTER_FORROOT_GUARD,] }] }, |
| { type: Router, decorators: [{ type: Optional }] } |
| ]; |
| function createRouterScroller(router, viewportScroller, config) { |
| if (config.scrollOffset) { |
| viewportScroller.setOffset(config.scrollOffset); |
| } |
| return new RouterScroller(router, viewportScroller, config); |
| } |
| function provideLocationStrategy(platformLocationStrategy, baseHref, options = {}) { |
| return options.useHash ? new HashLocationStrategy(platformLocationStrategy, baseHref) : |
| new PathLocationStrategy(platformLocationStrategy, baseHref); |
| } |
| function provideForRootGuard(router) { |
| if ((typeof ngDevMode === 'undefined' || ngDevMode) && router) { |
| throw new Error(`RouterModule.forRoot() called twice. Lazy loaded modules should use RouterModule.forChild() instead.`); |
| } |
| return 'guarded'; |
| } |
| /** |
| * Registers a [DI provider](guide/glossary#provider) for a set of routes. |
| * @param routes The route configuration to provide. |
| * |
| * @usageNotes |
| * |
| * ``` |
| * @NgModule({ |
| * imports: [RouterModule.forChild(ROUTES)], |
| * providers: [provideRoutes(EXTRA_ROUTES)] |
| * }) |
| * class MyNgModule {} |
| * ``` |
| * |
| * @publicApi |
| */ |
| function provideRoutes(routes) { |
| return [ |
| { provide: ANALYZE_FOR_ENTRY_COMPONENTS, multi: true, useValue: routes }, |
| { provide: ROUTES, multi: true, useValue: routes }, |
| ]; |
| } |
| function setupRouter(urlSerializer, contexts, location, injector, loader, compiler, config, opts = {}, urlHandlingStrategy, routeReuseStrategy) { |
| const router = new Router(null, urlSerializer, contexts, location, injector, loader, compiler, flatten(config)); |
| if (urlHandlingStrategy) { |
| router.urlHandlingStrategy = urlHandlingStrategy; |
| } |
| if (routeReuseStrategy) { |
| router.routeReuseStrategy = routeReuseStrategy; |
| } |
| assignExtraOptionsToRouter(opts, router); |
| if (opts.enableTracing) { |
| const dom = ɵgetDOM(); |
| router.events.subscribe((e) => { |
| dom.logGroup(`Router Event: ${e.constructor.name}`); |
| dom.log(e.toString()); |
| dom.log(e); |
| dom.logGroupEnd(); |
| }); |
| } |
| return router; |
| } |
| function assignExtraOptionsToRouter(opts, router) { |
| if (opts.errorHandler) { |
| router.errorHandler = opts.errorHandler; |
| } |
| if (opts.malformedUriErrorHandler) { |
| router.malformedUriErrorHandler = opts.malformedUriErrorHandler; |
| } |
| if (opts.onSameUrlNavigation) { |
| router.onSameUrlNavigation = opts.onSameUrlNavigation; |
| } |
| if (opts.paramsInheritanceStrategy) { |
| router.paramsInheritanceStrategy = opts.paramsInheritanceStrategy; |
| } |
| if (opts.relativeLinkResolution) { |
| router.relativeLinkResolution = opts.relativeLinkResolution; |
| } |
| if (opts.urlUpdateStrategy) { |
| router.urlUpdateStrategy = opts.urlUpdateStrategy; |
| } |
| } |
| function rootRoute(router) { |
| return router.routerState.root; |
| } |
| /** |
| * Router initialization requires two steps: |
| * |
| * First, we start the navigation in a `APP_INITIALIZER` to block the bootstrap if |
| * a resolver or a guard executes asynchronously. |
| * |
| * Next, we actually run activation in a `BOOTSTRAP_LISTENER`, using the |
| * `afterPreactivation` hook provided by the router. |
| * The router navigation starts, reaches the point when preactivation is done, and then |
| * pauses. It waits for the hook to be resolved. We then resolve it only in a bootstrap listener. |
| */ |
| class RouterInitializer { |
| constructor(injector) { |
| this.injector = injector; |
| this.initNavigation = false; |
| this.resultOfPreactivationDone = new Subject(); |
| } |
| appInitializer() { |
| const p = this.injector.get(LOCATION_INITIALIZED, Promise.resolve(null)); |
| return p.then(() => { |
| let resolve = null; |
| const res = new Promise(r => resolve = r); |
| const router = this.injector.get(Router); |
| const opts = this.injector.get(ROUTER_CONFIGURATION); |
| if (opts.initialNavigation === 'disabled') { |
| router.setUpLocationChangeListener(); |
| resolve(true); |
| } |
| else if ( |
| // TODO: enabled is deprecated as of v11, can be removed in v13 |
| opts.initialNavigation === 'enabled' || opts.initialNavigation === 'enabledBlocking') { |
| router.hooks.afterPreactivation = () => { |
| // only the initial navigation should be delayed |
| if (!this.initNavigation) { |
| this.initNavigation = true; |
| resolve(true); |
| return this.resultOfPreactivationDone; |
| // subsequent navigations should not be delayed |
| } |
| else { |
| return of(null); |
| } |
| }; |
| router.initialNavigation(); |
| } |
| else { |
| resolve(true); |
| } |
| return res; |
| }); |
| } |
| bootstrapListener(bootstrappedComponentRef) { |
| const opts = this.injector.get(ROUTER_CONFIGURATION); |
| const preloader = this.injector.get(RouterPreloader); |
| const routerScroller = this.injector.get(RouterScroller); |
| const router = this.injector.get(Router); |
| const ref = this.injector.get(ApplicationRef); |
| if (bootstrappedComponentRef !== ref.components[0]) { |
| return; |
| } |
| // Default case |
| if (opts.initialNavigation === 'enabledNonBlocking' || opts.initialNavigation === undefined) { |
| router.initialNavigation(); |
| } |
| preloader.setUpPreloading(); |
| routerScroller.init(); |
| router.resetRootComponentType(ref.componentTypes[0]); |
| this.resultOfPreactivationDone.next(null); |
| this.resultOfPreactivationDone.complete(); |
| } |
| } |
| RouterInitializer.decorators = [ |
| { type: Injectable } |
| ]; |
| RouterInitializer.ctorParameters = () => [ |
| { type: Injector } |
| ]; |
| function getAppInitializer(r) { |
| return r.appInitializer.bind(r); |
| } |
| function getBootstrapListener(r) { |
| return r.bootstrapListener.bind(r); |
| } |
| /** |
| * A [DI token](guide/glossary/#di-token) for the router initializer that |
| * is called after the app is bootstrapped. |
| * |
| * @publicApi |
| */ |
| const ROUTER_INITIALIZER = new InjectionToken('Router Initializer'); |
| function provideRouterInitializer() { |
| return [ |
| RouterInitializer, |
| { |
| provide: APP_INITIALIZER, |
| multi: true, |
| useFactory: getAppInitializer, |
| deps: [RouterInitializer] |
| }, |
| { provide: ROUTER_INITIALIZER, useFactory: getBootstrapListener, deps: [RouterInitializer] }, |
| { provide: APP_BOOTSTRAP_LISTENER, multi: true, useExisting: ROUTER_INITIALIZER }, |
| ]; |
| } |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * @publicApi |
| */ |
| const VERSION = new Version('11.2.14'); |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| // This file only reexports content of the `src` folder. Keep it that way. |
| |
| /** |
| * @license |
| * Copyright Google LLC All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| |
| /** |
| * Generated bundle index. Do not edit. |
| */ |
| |
| export { 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 }; |
| //# sourceMappingURL=router.js.map |