UNPKG

@angular/router

Version:
1,851 lines (1,841 loc) 225 kB
/** * @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.