@angular/router
Version:
Angular - the routing library
1,851 lines (1,841 loc) • 225 kB
JavaScript
/**
* @license Angular v7.0.1
* (c) 2010-2018 Google, Inc. https://angular.io/
* License: MIT
*/
import { Component, ɵisObservable, ɵisPromise, NgModuleRef, InjectionToken, NgModuleFactory, NgZone, isDevMode, ɵConsole, Attribute, Directive, ElementRef, HostBinding, HostListener, Input, Renderer2, ChangeDetectorRef, ContentChildren, ComponentFactoryResolver, EventEmitter, Output, ViewContainerRef, Compiler, Injectable, Injector, NgModuleFactoryLoader, ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, Inject, NgModule, NgProbeToken, Optional, SkipSelf, SystemJsNgModuleLoader, Version } from '@angular/core';
import { from, of, BehaviorSubject, EmptyError, Observable, EMPTY, Subject } from 'rxjs';
import { concatAll, every, last, map, mergeAll, catchError, first, mergeMap, switchMap, concatMap, reduce, filter, finalize, tap } from 'rxjs/operators';
import { LocationStrategy, APP_BASE_HREF, HashLocationStrategy, LOCATION_INITIALIZED, Location, PathLocationStrategy, PlatformLocation, ViewportScroller } from '@angular/common';
import { ɵgetDOM } from '@angular/platform-browser';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,uselessCode} checked by tsc
*/
/**
* \@description
*
* Base for events the Router goes through, as opposed to events tied to a specific
* Route. `RouterEvent`s will only be fired one time for any given navigation.
*
* Example:
*
* ```
* class MyService {
* constructor(public router: Router, logger: Logger) {
* router.events.filter(e => e instanceof RouterEvent).subscribe(e => {
* logger.log(e.id, e.url);
* });
* }
* }
* ```
*
* \@publicApi
*/
class RouterEvent {
/**
* @param {?} id
* @param {?} url
*/
constructor(id, url) {
this.id = id;
this.url = url;
}
}
/**
* \@description
*
* Represents an event triggered when a navigation starts.
*
* \@publicApi
*/
class NavigationStart extends RouterEvent {
/**
* @param {?} id
* @param {?} url
* @param {?=} navigationTrigger
* @param {?=} restoredState
*/
constructor(/** @docsNotRequired */
id, /** @docsNotRequired */
url, /** @docsNotRequired */
navigationTrigger = 'imperative', /** @docsNotRequired */
restoredState = null) {
super(id, url);
this.navigationTrigger = navigationTrigger;
this.restoredState = restoredState;
}
/**
* \@docsNotRequired
* @return {?}
*/
toString() { return `NavigationStart(id: ${this.id}, url: '${this.url}')`; }
}
/**
* \@description
*
* Represents an event triggered when a navigation ends successfully.
*
* \@publicApi
*/
class NavigationEnd extends RouterEvent {
/**
* @param {?} id
* @param {?} url
* @param {?} urlAfterRedirects
*/
constructor(/** @docsNotRequired */
id, /** @docsNotRequired */
url, urlAfterRedirects) {
super(id, url);
this.urlAfterRedirects = urlAfterRedirects;
}
/**
* \@docsNotRequired
* @return {?}
*/
toString() {
return `NavigationEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}')`;
}
}
/**
* \@description
*
* Represents an event triggered when a navigation is canceled.
*
* \@publicApi
*/
class NavigationCancel extends RouterEvent {
/**
* @param {?} id
* @param {?} url
* @param {?} reason
*/
constructor(/** @docsNotRequired */
id, /** @docsNotRequired */
url, reason) {
super(id, url);
this.reason = reason;
}
/**
* \@docsNotRequired
* @return {?}
*/
toString() { return `NavigationCancel(id: ${this.id}, url: '${this.url}')`; }
}
/**
* \@description
*
* Represents an event triggered when a navigation fails due to an unexpected error.
*
* \@publicApi
*/
class NavigationError extends RouterEvent {
/**
* @param {?} id
* @param {?} url
* @param {?} error
*/
constructor(/** @docsNotRequired */
id, /** @docsNotRequired */
url, error) {
super(id, url);
this.error = error;
}
/**
* \@docsNotRequired
* @return {?}
*/
toString() {
return `NavigationError(id: ${this.id}, url: '${this.url}', error: ${this.error})`;
}
}
/**
* \@description
*
* Represents an event triggered when routes are recognized.
*
* \@publicApi
*/
class RoutesRecognized extends RouterEvent {
/**
* @param {?} id
* @param {?} url
* @param {?} urlAfterRedirects
* @param {?} state
*/
constructor(/** @docsNotRequired */
id, /** @docsNotRequired */
url, urlAfterRedirects, state) {
super(id, url);
this.urlAfterRedirects = urlAfterRedirects;
this.state = state;
}
/**
* \@docsNotRequired
* @return {?}
*/
toString() {
return `RoutesRecognized(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
}
}
/**
* \@description
*
* Represents the start of the Guard phase of routing.
*
* \@publicApi
*/
class GuardsCheckStart extends RouterEvent {
/**
* @param {?} id
* @param {?} url
* @param {?} urlAfterRedirects
* @param {?} state
*/
constructor(/** @docsNotRequired */
id, /** @docsNotRequired */
url, urlAfterRedirects, state) {
super(id, url);
this.urlAfterRedirects = urlAfterRedirects;
this.state = state;
}
/**
* @return {?}
*/
toString() {
return `GuardsCheckStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
}
}
/**
* \@description
*
* Represents the end of the Guard phase of routing.
*
* \@publicApi
*/
class GuardsCheckEnd extends RouterEvent {
/**
* @param {?} id
* @param {?} url
* @param {?} urlAfterRedirects
* @param {?} state
* @param {?} shouldActivate
*/
constructor(/** @docsNotRequired */
id, /** @docsNotRequired */
url, urlAfterRedirects, state, shouldActivate) {
super(id, url);
this.urlAfterRedirects = urlAfterRedirects;
this.state = state;
this.shouldActivate = shouldActivate;
}
/**
* @return {?}
*/
toString() {
return `GuardsCheckEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state}, shouldActivate: ${this.shouldActivate})`;
}
}
/**
* \@description
*
* Represents the start of the Resolve phase of routing. The timing of this
* event may change, thus it's experimental. In the current iteration it will run
* in the "resolve" phase whether there's things to resolve or not. In the future this
* behavior may change to only run when there are things to be resolved.
*
* \@publicApi
*/
class ResolveStart extends RouterEvent {
/**
* @param {?} id
* @param {?} url
* @param {?} urlAfterRedirects
* @param {?} state
*/
constructor(/** @docsNotRequired */
id, /** @docsNotRequired */
url, urlAfterRedirects, state) {
super(id, url);
this.urlAfterRedirects = urlAfterRedirects;
this.state = state;
}
/**
* @return {?}
*/
toString() {
return `ResolveStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
}
}
/**
* \@description
*
* Represents the end of the Resolve phase of routing. See note on
* `ResolveStart` for use of this experimental API.
*
* \@publicApi
*/
class ResolveEnd extends RouterEvent {
/**
* @param {?} id
* @param {?} url
* @param {?} urlAfterRedirects
* @param {?} state
*/
constructor(/** @docsNotRequired */
id, /** @docsNotRequired */
url, urlAfterRedirects, state) {
super(id, url);
this.urlAfterRedirects = urlAfterRedirects;
this.state = state;
}
/**
* @return {?}
*/
toString() {
return `ResolveEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
}
}
/**
* \@description
*
* Represents an event triggered before lazy loading a route config.
*
* \@publicApi
*/
class RouteConfigLoadStart {
/**
* @param {?} route
*/
constructor(route) {
this.route = route;
}
/**
* @return {?}
*/
toString() { return `RouteConfigLoadStart(path: ${this.route.path})`; }
}
/**
* \@description
*
* Represents an event triggered when a route has been lazy loaded.
*
* \@publicApi
*/
class RouteConfigLoadEnd {
/**
* @param {?} route
*/
constructor(route) {
this.route = route;
}
/**
* @return {?}
*/
toString() { return `RouteConfigLoadEnd(path: ${this.route.path})`; }
}
/**
* \@description
*
* Represents the start of end of the Resolve phase of routing. See note on
* `ChildActivationEnd` for use of this experimental API.
*
* \@publicApi
*/
class ChildActivationStart {
/**
* @param {?} snapshot
*/
constructor(snapshot) {
this.snapshot = snapshot;
}
/**
* @return {?}
*/
toString() {
/** @type {?} */
const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
return `ChildActivationStart(path: '${path}')`;
}
}
/**
* \@description
*
* Represents the start of end of the Resolve phase of routing. See note on
* `ChildActivationStart` for use of this experimental API.
*
* \@publicApi
*/
class ChildActivationEnd {
/**
* @param {?} snapshot
*/
constructor(snapshot) {
this.snapshot = snapshot;
}
/**
* @return {?}
*/
toString() {
/** @type {?} */
const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
return `ChildActivationEnd(path: '${path}')`;
}
}
/**
* \@description
*
* Represents the start of end of the Resolve phase of routing. See note on
* `ActivationEnd` for use of this experimental API.
*
* \@publicApi
*/
class ActivationStart {
/**
* @param {?} snapshot
*/
constructor(snapshot) {
this.snapshot = snapshot;
}
/**
* @return {?}
*/
toString() {
/** @type {?} */
const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
return `ActivationStart(path: '${path}')`;
}
}
/**
* \@description
*
* Represents the start of end of the Resolve phase of routing. See note on
* `ActivationStart` for use of this experimental API.
*
* \@publicApi
*/
class ActivationEnd {
/**
* @param {?} snapshot
*/
constructor(snapshot) {
this.snapshot = snapshot;
}
/**
* @return {?}
*/
toString() {
/** @type {?} */
const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
return `ActivationEnd(path: '${path}')`;
}
}
/**
* \@description
*
* Represents a scrolling event.
*
* \@publicApi
*/
class Scroll {
/**
* @param {?} routerEvent
* @param {?} position
* @param {?} anchor
*/
constructor(/** @docsNotRequired */
routerEvent, /** @docsNotRequired */
position, /** @docsNotRequired */
anchor) {
this.routerEvent = routerEvent;
this.position = position;
this.anchor = anchor;
}
/**
* @return {?}
*/
toString() {
/** @type {?} */
const pos = this.position ? `${this.position[0]}, ${this.position[1]}` : null;
return `Scroll(anchor: '${this.anchor}', position: '${pos}')`;
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,uselessCode} checked by tsc
*/
/**
* 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>` }] }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,uselessCode} checked by tsc
*/
/**
* @license
* Copyright Google Inc. 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
*
* Name of the primary outlet.
*
* \@publicApi
@type {?} */
const PRIMARY_OUTLET = 'primary';
class ParamsAsMap {
/**
* @param {?} params
*/
constructor(params) { this.params = params || {}; }
/**
* @param {?} name
* @return {?}
*/
has(name) { return this.params.hasOwnProperty(name); }
/**
* @param {?} name
* @return {?}
*/
get(name) {
if (this.has(name)) {
/** @type {?} */
const v = this.params[name];
return Array.isArray(v) ? v[0] : v;
}
return null;
}
/**
* @param {?} name
* @return {?}
*/
getAll(name) {
if (this.has(name)) {
/** @type {?} */
const v = this.params[name];
return Array.isArray(v) ? v : [v];
}
return [];
}
/**
* @return {?}
*/
get keys() { return Object.keys(this.params); }
}
/**
* Convert a `Params` instance to a `ParamMap`.
*
* \@publicApi
* @param {?} params
* @return {?}
*/
function convertToParamMap(params) {
return new ParamsAsMap(params);
}
/** @type {?} */
const NAVIGATION_CANCELING_ERROR = 'ngNavigationCancelingError';
/**
* @param {?} message
* @return {?}
*/
function navigationCancelingError(message) {
/** @type {?} */
const error = Error('NavigationCancelingError: ' + message);
(/** @type {?} */ (error))[NAVIGATION_CANCELING_ERROR] = true;
return error;
}
/**
* @param {?} error
* @return {?}
*/
function isNavigationCancelingError(error) {
return error && (/** @type {?} */ (error))[NAVIGATION_CANCELING_ERROR];
}
/**
* @param {?} segments
* @param {?} segmentGroup
* @param {?} route
* @return {?}
*/
function defaultUrlMatcher(segments, segmentGroup, route) {
/** @type {?} */
const parts = /** @type {?} */ ((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;
}
/** @type {?} */
const posParams = {};
// Check each config part against the actual URL
for (let index = 0; index < parts.length; index++) {
/** @type {?} */
const part = parts[index];
/** @type {?} */
const segment = segments[index];
/** @type {?} */
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 };
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,uselessCode} checked by tsc
*/
class LoadedRouterConfig {
/**
* @param {?} routes
* @param {?} module
*/
constructor(routes, module) {
this.routes = routes;
this.module = module;
}
}
/**
* @param {?} config
* @param {?=} parentPath
* @return {?}
*/
function validateConfig(config, parentPath = '') {
// forEach doesn't iterate undefined values
for (let i = 0; i < config.length; i++) {
/** @type {?} */
const route = config[i];
/** @type {?} */
const fullPath = getFullPath(parentPath, route);
validateNode(route, fullPath);
}
}
/**
* @param {?} route
* @param {?} fullPath
* @return {?}
*/
function validateNode(route, fullPath) {
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.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) {
/** @type {?} */
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);
}
}
/**
* @param {?} parentPath
* @param {?} currentRoute
* @return {?}
*/
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.
* @param {?} r
* @return {?}
*/
function standardizeConfig(r) {
/** @type {?} */
const children = r.children && r.children.map(standardizeConfig);
/** @type {?} */
const c = children ? Object.assign({}, r, { children }) : Object.assign({}, r);
if (!c.component && (children || c.loadChildren) && (c.outlet && c.outlet !== PRIMARY_OUTLET)) {
c.component = EmptyOutletComponent;
}
return c;
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,uselessCode} checked by tsc
*/
/**
* @param {?} a
* @param {?} b
* @return {?}
*/
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;
}
/**
* @param {?} a
* @param {?} b
* @return {?}
*/
function shallowEqual(a, b) {
/** @type {?} */
const k1 = Object.keys(a);
/** @type {?} */
const k2 = Object.keys(b);
if (k1.length != k2.length) {
return false;
}
/** @type {?} */
let key;
for (let i = 0; i < k1.length; i++) {
key = k1[i];
if (a[key] !== b[key]) {
return false;
}
}
return true;
}
/**
* Flattens single-level nested arrays.
* @template T
* @param {?} arr
* @return {?}
*/
function flatten(arr) {
return Array.prototype.concat.apply([], arr);
}
/**
* Return the last element of an array.
* @template T
* @param {?} a
* @return {?}
*/
function last$1(a) {
return a.length > 0 ? a[a.length - 1] : null;
}
/**
* @template K, V
* @param {?} map
* @param {?} callback
* @return {?}
*/
function forEach(map$$1, callback) {
for (const prop in map$$1) {
if (map$$1.hasOwnProperty(prop)) {
callback(map$$1[prop], prop);
}
}
}
/**
* @template A, B
* @param {?} obj
* @param {?} fn
* @return {?}
*/
function waitForMap(obj, fn) {
if (Object.keys(obj).length === 0) {
return of({});
}
/** @type {?} */
const waitHead = [];
/** @type {?} */
const waitTail = [];
/** @type {?} */
const res = {};
forEach(obj, (a, k) => {
/** @type {?} */
const mapped = fn(k, a).pipe(map((r) => res[k] = r));
if (k === PRIMARY_OUTLET) {
waitHead.push(mapped);
}
else {
waitTail.push(mapped);
}
});
// Closure compiler has problem with using spread operator here. So just using Array.concat.
return of.apply(null, waitHead.concat(waitTail)).pipe(concatAll(), last(), map(() => res));
}
/**
* ANDs Observables by merging all input observables, reducing to an Observable verifying all
* input Observables return `true`.
* @param {?} observables
* @return {?}
*/
function andObservables(observables) {
return observables.pipe(mergeAll(), every((result) => result === true));
}
/**
* @template T
* @param {?} value
* @return {?}
*/
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(/** @type {?} */ (value));
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,uselessCode} checked by tsc
*/
/**
* @return {?}
*/
function createEmptyUrlTree() {
return new UrlTree(new UrlSegmentGroup([], {}), {}, null);
}
/**
* @param {?} container
* @param {?} containee
* @param {?} exact
* @return {?}
*/
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);
}
/**
* @param {?} container
* @param {?} containee
* @return {?}
*/
function equalQueryParams(container, containee) {
// TODO: This does not handle array params correctly.
return shallowEqual(container, containee);
}
/**
* @param {?} container
* @param {?} containee
* @return {?}
*/
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;
}
/**
* @param {?} container
* @param {?} containee
* @return {?}
*/
function containsQueryParams(container, containee) {
// TODO: This does not handle array params correctly.
return Object.keys(containee).length <= Object.keys(container).length &&
Object.keys(containee).every(key => containee[key] === container[key]);
}
/**
* @param {?} container
* @param {?} containee
* @return {?}
*/
function containsSegmentGroup(container, containee) {
return containsSegmentGroupHelper(container, containee, containee.segments);
}
/**
* @param {?} container
* @param {?} containee
* @param {?} containeePaths
* @return {?}
*/
function containsSegmentGroupHelper(container, containee, containeePaths) {
if (container.segments.length > containeePaths.length) {
/** @type {?} */
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 {
/** @type {?} */
const current = containeePaths.slice(0, container.segments.length);
/** @type {?} */
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
* @param {?} root
* @param {?} queryParams
* @param {?} fragment
*/
constructor(root, queryParams, fragment) {
this.root = root;
this.queryParams = queryParams;
this.fragment = fragment;
}
/**
* @return {?}
*/
get queryParamMap() {
if (!this._queryParamMap) {
this._queryParamMap = convertToParamMap(this.queryParams);
}
return this._queryParamMap;
}
/**
* \@docsNotRequired
* @return {?}
*/
toString() { return DEFAULT_SERIALIZER.serialize(this); }
}
/**
* \@description
*
* Represents the parsed URL segment group.
*
* See `UrlTree` for more information.
*
* \@publicApi
*/
class UrlSegmentGroup {
/**
* @param {?} segments
* @param {?} children
*/
constructor(segments, 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
* @return {?}
*/
hasChildren() { return this.numberOfChildren > 0; }
/**
* Number of child segments
* @return {?}
*/
get numberOfChildren() { return Object.keys(this.children).length; }
/**
* \@docsNotRequired
* @return {?}
*/
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 {
/**
* @param {?} path
* @param {?} parameters
*/
constructor(path, parameters) {
this.path = path;
this.parameters = parameters;
}
/**
* @return {?}
*/
get parameterMap() {
if (!this._parameterMap) {
this._parameterMap = convertToParamMap(this.parameters);
}
return this._parameterMap;
}
/**
* \@docsNotRequired
* @return {?}
*/
toString() { return serializePath(this); }
}
/**
* @param {?} as
* @param {?} bs
* @return {?}
*/
function equalSegments(as, bs) {
return equalPath(as, bs) && as.every((a, i) => shallowEqual(a.parameters, bs[i].parameters));
}
/**
* @param {?} as
* @param {?} bs
* @return {?}
*/
function equalPath(as, bs) {
if (as.length !== bs.length)
return false;
return as.every((a, i) => a.path === bs[i].path);
}
/**
* @template T
* @param {?} segment
* @param {?} fn
* @return {?}
*/
function mapChildrenIntoArray(segment, fn) {
/** @type {?} */
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
* @abstract
*/
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`
* @param {?} url
* @return {?}
*/
parse(url) {
/** @type {?} */
const p = new UrlParser(url);
return new UrlTree(p.parseRootSegment(), p.parseQueryParams(), p.parseFragment());
}
/**
* Converts a `UrlTree` into a url
* @param {?} tree
* @return {?}
*/
serialize(tree) {
/** @type {?} */
const segment = `/${serializeSegment(tree.root, true)}`;
/** @type {?} */
const query = serializeQueryParams(tree.queryParams);
/** @type {?} */
const fragment = typeof tree.fragment === `string` ? `#${encodeUriFragment((/** @type {?} */ ((tree.fragment))))}` : '';
return `${segment}${query}${fragment}`;
}
}
/** @type {?} */
const DEFAULT_SERIALIZER = new DefaultUrlSerializer();
/**
* @param {?} segment
* @return {?}
*/
function serializePaths(segment) {
return segment.segments.map(p => serializePath(p)).join('/');
}
/**
* @param {?} segment
* @param {?} root
* @return {?}
*/
function serializeSegment(segment, root) {
if (!segment.hasChildren()) {
return serializePaths(segment);
}
if (root) {
/** @type {?} */
const primary = segment.children[PRIMARY_OUTLET] ?
serializeSegment(segment.children[PRIMARY_OUTLET], false) :
'';
/** @type {?} */
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 {
/** @type {?} */
const children = mapChildrenIntoArray(segment, (v, k) => {
if (k === PRIMARY_OUTLET) {
return [serializeSegment(segment.children[PRIMARY_OUTLET], false)];
}
return [`${k}:${serializeSegment(v, false)}`];
});
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.
* @param {?} s
* @return {?}
*/
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
* @param {?} s
* @return {?}
*/
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
* @param {?} s
* @return {?}
*/
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
* @param {?} s
* @return {?}
*/
function encodeUriSegment(s) {
return encodeUriString(s).replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/%26/gi, '&');
}
/**
* @param {?} s
* @return {?}
*/
function decode(s) {
return decodeURIComponent(s);
}
/**
* @param {?} s
* @return {?}
*/
function decodeQuery(s) {
return decode(s.replace(/\+/g, '%20'));
}
/**
* @param {?} path
* @return {?}
*/
function serializePath(path) {
return `${encodeUriSegment(path.path)}${serializeMatrixParams(path.parameters)}`;
}
/**
* @param {?} params
* @return {?}
*/
function serializeMatrixParams(params) {
return Object.keys(params)
.map(key => `;${encodeUriSegment(key)}=${encodeUriSegment(params[key])}`)
.join('');
}
/**
* @param {?} params
* @return {?}
*/
function serializeQueryParams(params) {
/** @type {?} */
const strParams = Object.keys(params).map((name) => {
/** @type {?} */
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("&")}` : '';
}
/** @type {?} */
const SEGMENT_RE = /^[^\/()?;=#]+/;
/**
* @param {?} str
* @return {?}
*/
function matchSegments(str) {
/** @type {?} */
const match = str.match(SEGMENT_RE);
return match ? match[0] : '';
}
/** @type {?} */
const QUERY_PARAM_RE = /^[^=?&#]+/;
/**
* @param {?} str
* @return {?}
*/
function matchQueryParams(str) {
/** @type {?} */
const match = str.match(QUERY_PARAM_RE);
return match ? match[0] : '';
}
/** @type {?} */
const QUERY_PARAM_VALUE_RE = /^[^?&#]+/;
/**
* @param {?} str
* @return {?}
*/
function matchUrlQueryParamValue(str) {
/** @type {?} */
const match = str.match(QUERY_PARAM_VALUE_RE);
return match ? match[0] : '';
}
class UrlParser {
/**
* @param {?} url
*/
constructor(url) {
this.url = url;
this.remaining = url;
}
/**
* @return {?}
*/
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());
}
/**
* @return {?}
*/
parseQueryParams() {
/** @type {?} */
const params = {};
if (this.consumeOptional('?')) {
do {
this.parseQueryParam(params);
} while (this.consumeOptional('&'));
}
return params;
}
/**
* @return {?}
*/
parseFragment() {
return this.consumeOptional('#') ? decodeURIComponent(this.remaining) : null;
}
/**
* @return {?}
*/
parseChildren() {
if (this.remaining === '') {
return {};
}
this.consumeOptional('/');
/** @type {?} */
const segments = [];
if (!this.peekStartsWith('(')) {
segments.push(this.parseSegment());
}
while (this.peekStartsWith('/') && !this.peekStartsWith('//') && !this.peekStartsWith('/(')) {
this.capture('/');
segments.push(this.parseSegment());
}
/** @type {?} */
let children = {};
if (this.peekStartsWith('/(')) {
this.capture('/');
children = this.parseParens(true);
}
/** @type {?} */
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;
}
/**
* @return {?}
*/
parseSegment() {
/** @type {?} */
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());
}
/**
* @return {?}
*/
parseMatrixParams() {
/** @type {?} */
const params = {};
while (this.consumeOptional(';')) {
this.parseParam(params);
}
return params;
}
/**
* @param {?} params
* @return {?}
*/
parseParam(params) {
/** @type {?} */
const key = matchSegments(this.remaining);
if (!key) {
return;
}
this.capture(key);
/** @type {?} */
let value = '';
if (this.consumeOptional('=')) {
/** @type {?} */
const valueMatch = matchSegments(this.remaining);
if (valueMatch) {
value = valueMatch;
this.capture(value);
}
}
params[decode(key)] = decode(value);
}
/**
* @param {?} params
* @return {?}
*/
parseQueryParam(params) {
/** @type {?} */
const key = matchQueryParams(this.remaining);
if (!key) {
return;
}
this.capture(key);
/** @type {?} */
let value = '';
if (this.consumeOptional('=')) {
/** @type {?} */
const valueMatch = matchUrlQueryParamValue(this.remaining);
if (valueMatch) {
value = valueMatch;
this.capture(value);
}
}
/** @type {?} */
const decodedKey = decodeQuery(key);
/** @type {?} */
const decodedVal = decodeQuery(value);
if (params.hasOwnProperty(decodedKey)) {
/** @type {?} */
let currentVal = params[decodedKey];
if (!Array.isArray(currentVal)) {
currentVal = [currentVal];
params[decodedKey] = currentVal;
}
currentVal.push(decodedVal);
}
else {
// Create a new value
params[decodedKey] = decodedVal;
}
}
/**
* @param {?} allowPrimary
* @return {?}
*/
parseParens(allowPrimary) {
/** @type {?} */
const segments = {};
this.capture('(');
while (!this.consumeOptional(')') && this.remaining.length > 0) {
/** @type {?} */
const path = matchSegments(this.remaining);
/** @type {?} */
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}'`);
}
/** @type {?} */
let outletName = /** @type {?} */ ((undefined));
if (path.indexOf(':') > -1) {
outletName = path.substr(0, path.indexOf(':'));
this.capture(outletName);
this.capture(':');
}
else if (allowPrimary) {
outletName = PRIMARY_OUTLET;
}
/** @type {?} */
const children = this.parseChildren();
segments[outletName] = Object.keys(children).length === 1 ? children[PRIMARY_OUTLET] :
new UrlSegmentGroup([], children);
this.consumeOptional('//');
}
return segments;
}
/**
* @param {?} str
* @return {?}
*/
peekStartsWith(str) { return this.remaining.startsWith(str); }
/**
* @param {?} str
* @return {?}
*/
consumeOptional(str) {
if (this.peekStartsWith(str)) {
this.remaining = this.remaining.substring(str.length);
return true;
}
return false;
}
/**
* @param {?} str
* @return {?}
*/
capture(str) {
if (!this.consumeOptional(str)) {
throw new Error(`Expected "${str}".`);
}
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,uselessCode} checked by tsc
*/
/**
* @license
* Copyright Google Inc. 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
*/
/**
* @template T
*/
class Tree {
/**
* @param {?} root
*/
constructor(root) { this._root = root; }
/**
* @return {?}
*/
get root() { return this._root.value; }
/**
* \@internal
* @param {?} t
* @return {?}
*/
parent(t) {
/** @type {?} */
const p = this.pathFromRoot(t);
return p.length > 1 ? p[p.length - 2] : null;
}
/**
* \@internal
* @param {?} t
* @return {?}
*/
children(t) {
/** @type {?} */
const n = findNode(t, this._root);
return n ? n.children.map(t => t.value) : [];
}
/**
* \@internal
* @param {?} t
* @return {?}
*/
firstChild(t) {
/** @type {?} */
const n = findNode(t, this._root);
return n && n.children.length > 0 ? n.children[0].value : null;
}
/**
* \@internal
* @param {?} t
* @return {?}
*/
siblings(t) {
/** @type {?} */
const p = findPath(t, this._root);
if (p.length < 2)
return [];
/** @type {?} */
const c = p[p.length - 2].children.map(c => c.value);
return c.filter(cc => cc !== t);
}
/**
* \@internal
* @param {?} t
* @return {?}
*/
pathFromRoot(t) { return findPath(t, this._root).map(s => s.value); }
}
/**
* @template T
* @param {?} value
* @param {?} node
* @return {?}
*/
function findNode(value, node) {
if (value === node.value)
return node;
for (const child of node.children) {
/** @type {?} */
const node = findNode(value, child);
if (node)
return node;
}
return null;
}
/**
* @template T
* @param {?} value
* @param {?} node
* @return {?}
*/
function findPath(value, node) {
if (value === node.value)
return [node];
for (const child of node.children) {
/** @type {?} */
const path = findPath(value, child);
if (path.length) {
path.unshift(node);
return path;
}
}
return [];
}
/**
* @template T
*/
class TreeNode {
/**
* @param {?} value
* @param {?} children
*/
constructor(value, children) {
this.value = value;
this.children = children;
}
/**
* @return {?}
*/
toString() { return `TreeNode(${this.value})`; }
}
/**
* @template T
* @param {?} node
* @return {?}
*/
function nodeChildrenAsMap(node) {
/** @type {?} */
const map$$1 = {};
if (node) {
node.children.forEach(child => map$$1[child.value.outlet] = child);
}
return map$$1;
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,uselessCode} checked by tsc
*/
/**
* \@description
*
* Represents the state of the router.
*
* RouterState is a tree of activated routes. Every node in this tree knows about the "consumed" URL
* segments, the extracted parameters, and the resolved data.
*
* \@usageNotes
* ### Example
*
* ```
* \@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` for more information.
*
* \@publicApi
*/
class RouterState extends Tree {
/**
* \@internal
* @param {?} root
* @param {?} snapshot
*/
constructor(root, snapshot) {
super(root);
this.snapshot = snapshot;
setRouterState(/** @type {?} */ (this), root);
}
/**
* @return {?}
*/
toString() { return this.snapshot.toString(); }
}
/**
* @param {?} urlTree
* @param {?} rootComponent
* @return {?}
*/
function createEmptyState(urlTree, rootComponent) {
/** @type {?} */
const snapshot = createEmptyStateSnapshot(urlTree, rootComponent);
/** @type {?} */
const emptyUrl = new BehaviorSubject([new UrlSegment('', {})]);
/** @type {?} */
const emptyParams = new BehaviorSubject({});
/** @type {?} */
const emptyData = new BehaviorSubject({});
/** @type {?} */
const emptyQueryParams = new BehaviorSubject({});
/** @type {?} */
const fragment = new BehaviorSubject('');
/** @type {?} */
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);
}
/**
* @param {?} urlTree
* @param {?} rootComponent
* @return {?}
*/
function createEmptyStateSnapshot(urlTree, rootComponent) {
/** @type {?} */
const emptyParams = {};
/** @type {?} */
const emptyData = {};
/** @type {?} */
const emptyQueryParams = {};
/** @type {?} */
const fragment = '';
/** @type {?} */
const activated = new ActivatedRouteSnapshot([], emptyParams, emptyQueryParams, fragment, emptyData, PRIMARY_OUTLET, rootComponent, null, urlTree.root, -1, {});
return new RouterStateSnapshot('', new TreeNode(activated, []));
}
/**
* \@description
*
* Contains the information about a route associated with a component loaded in an
* outlet. An `ActivatedRoute` can also be used to traverse the router state tree.
*
* ```
* \@Component({...})
* class MyComponent {
* constructor(route: ActivatedRoute) {
* const id: Observable<string> = route.params.map(p => p.id);
* const url: Observable<string> = route.url.map(segments => segments.