refactor(router): misc refactoring (#13330)

This commit is contained in:
Victor Berchet
2016-12-09 10:44:46 -08:00
committed by GitHub
parent 56c361ff6a
commit b5c4bf1c59
13 changed files with 217 additions and 411 deletions

View File

@ -9,8 +9,6 @@
import {Injector, THROW_IF_NOT_FOUND} from '../di/injector'; import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
import {AppView} from './view'; import {AppView} from './view';
const _UNDEFINED = new Object();
export class ElementInjector extends Injector { export class ElementInjector extends Injector {
constructor(private _view: AppView<any>, private _nodeIndex: number) { super(); } constructor(private _view: AppView<any>, private _nodeIndex: number) { super(); }

View File

@ -9,7 +9,6 @@
import {ApplicationRef} from '../application_ref'; import {ApplicationRef} from '../application_ref';
import {ChangeDetectorRef, ChangeDetectorStatus} from '../change_detection/change_detection'; import {ChangeDetectorRef, ChangeDetectorStatus} from '../change_detection/change_detection';
import {Injector, THROW_IF_NOT_FOUND} from '../di/injector'; import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
import {ListWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {WtfScopeFn, wtfCreateScope, wtfLeave} from '../profile/profile'; import {WtfScopeFn, wtfCreateScope, wtfLeave} from '../profile/profile';
import {DirectRenderer, RenderComponentType, RenderDebugInfo, Renderer} from '../render/api'; import {DirectRenderer, RenderComponentType, RenderDebugInfo, Renderer} from '../render/api';

View File

@ -18,7 +18,7 @@ import {map} from 'rxjs/operator/map';
import {mergeMap} from 'rxjs/operator/mergeMap'; import {mergeMap} from 'rxjs/operator/mergeMap';
import {EmptyError} from 'rxjs/util/EmptyError'; import {EmptyError} from 'rxjs/util/EmptyError';
import {Route, Routes, UrlMatchResult} from './config'; import {Route, Routes} from './config';
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader'; import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
import {NavigationCancelingError, PRIMARY_OUTLET, Params, defaultUrlMatcher} from './shared'; import {NavigationCancelingError, PRIMARY_OUTLET, Params, defaultUrlMatcher} from './shared';
import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree'; import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
@ -54,7 +54,6 @@ function canLoadFails(route: Route): Observable<LoadedRouterConfig> {
`Cannot load children because the guard of the route "path: '${route.path}'" returned false`))); `Cannot load children because the guard of the route "path: '${route.path}'" returned false`)));
} }
export function applyRedirects( export function applyRedirects(
injector: Injector, configLoader: RouterConfigLoader, urlSerializer: UrlSerializer, injector: Injector, configLoader: RouterConfigLoader, urlSerializer: UrlSerializer,
urlTree: UrlTree, config: Routes): Observable<UrlTree> { urlTree: UrlTree, config: Routes): Observable<UrlTree> {

View File

@ -9,7 +9,7 @@
import {ActivatedRoute} from './router_state'; import {ActivatedRoute} from './router_state';
import {PRIMARY_OUTLET, Params} from './shared'; import {PRIMARY_OUTLET, Params} from './shared';
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree'; import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
import {forEach, shallowEqual} from './utils/collection'; import {forEach, shallowEqual, last} from './utils/collection';
export function createUrlTree( export function createUrlTree(
route: ActivatedRoute, urlTree: UrlTree, commands: any[], queryParams: Params, route: ActivatedRoute, urlTree: UrlTree, commands: any[], queryParams: Params,
@ -18,36 +18,24 @@ export function createUrlTree(
return tree(urlTree.root, urlTree.root, urlTree, queryParams, fragment); return tree(urlTree.root, urlTree.root, urlTree, queryParams, fragment);
} }
const normalizedCommands = normalizeCommands(commands); const nav = computeNavigation(commands);
validateCommands(normalizedCommands);
if (navigateToRoot(normalizedCommands)) { if (nav.toRoot()) {
return tree(urlTree.root, new UrlSegmentGroup([], {}), urlTree, queryParams, fragment); return tree(urlTree.root, new UrlSegmentGroup([], {}), urlTree, queryParams, fragment);
} }
const startingPosition = findStartingPosition(normalizedCommands, urlTree, route); const startingPosition = findStartingPosition(nav, urlTree, route);
const segmentGroup = startingPosition.processChildren ? const segmentGroup = startingPosition.processChildren ?
updateSegmentGroupChildren( updateSegmentGroupChildren(
startingPosition.segmentGroup, startingPosition.index, normalizedCommands.commands) : startingPosition.segmentGroup, startingPosition.index, nav.commands) :
updateSegmentGroup( updateSegmentGroup(
startingPosition.segmentGroup, startingPosition.index, normalizedCommands.commands); startingPosition.segmentGroup, startingPosition.index, nav.commands);
return tree(startingPosition.segmentGroup, segmentGroup, urlTree, queryParams, fragment); return tree(startingPosition.segmentGroup, segmentGroup, urlTree, queryParams, fragment);
} }
function validateCommands(n: NormalizedNavigationCommands): void {
if (n.isAbsolute && n.commands.length > 0 && isMatrixParams(n.commands[0])) {
throw new Error('Root segment cannot have matrix parameters');
}
const c = n.commands.filter(c => typeof c === 'object' && c.outlets !== undefined);
if (c.length > 0 && c[0] !== n.commands[n.commands.length - 1]) {
throw new Error('{outlets:{}} has to be the last command');
}
}
function isMatrixParams(command: any): boolean { function isMatrixParams(command: any): boolean {
return typeof command === 'object' && command.outlets === undefined && return typeof command === 'object' && !command.outlets && !command.segmentPath;
command.segmentPath === undefined;
} }
function tree( function tree(
@ -55,11 +43,11 @@ function tree(
queryParams: Params, fragment: string): UrlTree { queryParams: Params, fragment: string): UrlTree {
if (urlTree.root === oldSegmentGroup) { if (urlTree.root === oldSegmentGroup) {
return new UrlTree(newSegmentGroup, stringify(queryParams), fragment); return new UrlTree(newSegmentGroup, stringify(queryParams), fragment);
} else {
return new UrlTree(
replaceSegment(urlTree.root, oldSegmentGroup, newSegmentGroup), stringify(queryParams),
fragment);
} }
return new UrlTree(
replaceSegment(urlTree.root, oldSegmentGroup, newSegmentGroup), stringify(queryParams),
fragment);
} }
function replaceSegment( function replaceSegment(
@ -76,72 +64,71 @@ function replaceSegment(
return new UrlSegmentGroup(current.segments, children); return new UrlSegmentGroup(current.segments, children);
} }
function navigateToRoot(normalizedChange: NormalizedNavigationCommands): boolean { class Navigation {
return normalizedChange.isAbsolute && normalizedChange.commands.length === 1 && constructor(public isAbsolute: boolean, public numberOfDoubleDots: number, public commands: any[]) {
normalizedChange.commands[0] == '/'; if (isAbsolute && commands.length > 0 && isMatrixParams(commands[0])) {
throw new Error('Root segment cannot have matrix parameters');
}
const cmdWithOutlet = commands.find(c => typeof c === 'object' && c.outlets);
if (cmdWithOutlet && cmdWithOutlet !== last(commands)) {
throw new Error('{outlets:{}} has to be the last command');
}
}
public toRoot(): boolean {
return this.isAbsolute && this.commands.length === 1 && this.commands[0] == '/';
}
} }
class NormalizedNavigationCommands { /** Transforms commands to a normalized `Navigation` */
constructor( function computeNavigation(commands: any[]): Navigation {
public isAbsolute: boolean, public numberOfDoubleDots: number, public commands: any[]) {} if ((typeof commands[0] === 'string') && commands.length === 1 && commands[0] === '/') {
} return new Navigation(true, 0, commands);
function normalizeCommands(commands: any[]): NormalizedNavigationCommands {
if ((typeof commands[0] === 'string') && commands.length === 1 && commands[0] == '/') {
return new NormalizedNavigationCommands(true, 0, commands);
} }
let numberOfDoubleDots = 0; let numberOfDoubleDots = 0;
let isAbsolute = false; let isAbsolute = false;
const res: any[] = [];
for (let i = 0; i < commands.length; ++i) { const res: any[] = commands.reduce((res, cmd, cmdIdx) => {
const c = commands[i]; if (typeof cmd === 'object') {
if (cmd.outlets) {
const outlets: {[k: string]: any} = {};
forEach(cmd.outlets, (commands: any, name: string) => {
outlets[name] = typeof commands === 'string' ? commands.split('/') : commands;
});
return [...res, {outlets}];
}
if (typeof c === 'object' && c.outlets !== undefined) { if (cmd.segmentPath) {
const r: {[k: string]: any} = {}; return [...res, cmd.segmentPath];
forEach(c.outlets, (commands: any, name: string) => { }
if (typeof commands === 'string') { }
r[name] = commands.split('/');
} else { if (!(typeof cmd === 'string')) {
r[name] = commands; 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);
} }
}); });
res.push({outlets: r});
continue; return res;
} }
if (typeof c === 'object' && c.segmentPath !== undefined) { return [...res, cmd];
res.push(c.segmentPath); }, []);
continue;
}
if (!(typeof c === 'string')) { return new Navigation(isAbsolute, numberOfDoubleDots, res);
res.push(c);
continue;
}
if (i === 0) {
const parts = c.split('/');
for (let j = 0; j < parts.length; ++j) {
const cc = parts[j];
if (j == 0 && cc == '.') { // './a'
// skip it
} else if (j == 0 && cc == '') { // '/a'
isAbsolute = true;
} else if (cc == '..') { // '../a'
numberOfDoubleDots++;
} else if (cc != '') {
res.push(cc);
}
}
} else {
res.push(c);
}
}
return new NormalizedNavigationCommands(isAbsolute, numberOfDoubleDots, res);
} }
class Position { class Position {
@ -150,19 +137,19 @@ class Position {
} }
} }
function findStartingPosition( function findStartingPosition(nav: Navigation, tree: UrlTree, route: ActivatedRoute): Position {
normalizedChange: NormalizedNavigationCommands, urlTree: UrlTree, if (nav.isAbsolute) {
route: ActivatedRoute): Position { return new Position(tree.root, true, 0);
if (normalizedChange.isAbsolute) {
return new Position(urlTree.root, true, 0);
} else if (route.snapshot._lastPathIndex === -1) {
return new Position(route.snapshot._urlSegment, true, 0);
} else {
const modifier = isMatrixParams(normalizedChange.commands[0]) ? 0 : 1;
const index = route.snapshot._lastPathIndex + modifier;
return createPositionApplyingDoubleDots(
route.snapshot._urlSegment, index, normalizedChange.numberOfDoubleDots);
} }
if (route.snapshot._lastPathIndex === -1) {
return new Position(route.snapshot._urlSegment, true, 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( function createPositionApplyingDoubleDots(
@ -276,7 +263,7 @@ function createNewSegmentGroup(
let i = 0; let i = 0;
while (i < commands.length) { while (i < commands.length) {
if (typeof commands[i] === 'object' && commands[i].outlets !== undefined) { if (typeof commands[i] === 'object' && commands[i].outlets !== undefined) {
const children = createNewSegmentChldren(commands[i].outlets); const children = createNewSegmentChildren(commands[i].outlets);
return new UrlSegmentGroup(paths, children); return new UrlSegmentGroup(paths, children);
} }
@ -301,7 +288,7 @@ function createNewSegmentGroup(
return new UrlSegmentGroup(paths, {}); return new UrlSegmentGroup(paths, {});
} }
function createNewSegmentChldren(outlets: {[name: string]: any}): any { function createNewSegmentChildren(outlets: {[name: string]: any}): any {
const children: {[key: string]: UrlSegmentGroup} = {}; const children: {[key: string]: UrlSegmentGroup} = {};
forEach(outlets, (commands: any, outlet: string) => { forEach(outlets, (commands: any, outlet: string) => {
if (commands !== null) { if (commands !== null) {

View File

@ -12,9 +12,12 @@ import {ROUTES} from './router_config_loader';
import {ROUTER_PROVIDERS} from './router_module'; import {ROUTER_PROVIDERS} from './router_module';
import {flatten} from './utils/collection'; import {flatten} from './utils/collection';
export var __router_private__: export const __router_private__: {
{ROUTER_PROVIDERS: typeof ROUTER_PROVIDERS; ROUTES: typeof ROUTES; flatten: typeof flatten;} = { ROUTER_PROVIDERS: typeof ROUTER_PROVIDERS,
ROUTER_PROVIDERS: ROUTER_PROVIDERS, ROUTES: typeof ROUTES,
ROUTES: ROUTES, flatten: typeof flatten,
flatten: flatten } = {
}; ROUTER_PROVIDERS: ROUTER_PROVIDERS,
ROUTES: ROUTES,
flatten: flatten,
};

View File

@ -7,4 +7,4 @@
*/ */
import {__platform_browser_private__ as r} from '@angular/platform-browser'; import {__platform_browser_private__ as r} from '@angular/platform-browser';
export var getDOM: typeof r.getDOM = r.getDOM; export const getDOM: typeof r.getDOM = r.getDOM;

View File

@ -11,9 +11,9 @@ import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer'; import {Observer} from 'rxjs/Observer';
import {of } from 'rxjs/observable/of'; import {of } from 'rxjs/observable/of';
import {Data, ResolveData, Route, Routes, UrlMatchResult} from './config'; import {Data, ResolveData, Route, Routes} from './config';
import {ActivatedRouteSnapshot, RouterStateSnapshot, inheritedParamsDataResolve} from './router_state'; import {ActivatedRouteSnapshot, RouterStateSnapshot, inheritedParamsDataResolve} from './router_state';
import {PRIMARY_OUTLET, Params, defaultUrlMatcher} from './shared'; import {PRIMARY_OUTLET, defaultUrlMatcher} from './shared';
import {UrlSegment, UrlSegmentGroup, UrlTree, mapChildrenIntoArray} from './url_tree'; import {UrlSegment, UrlSegmentGroup, UrlTree, mapChildrenIntoArray} from './url_tree';
import {forEach, last, merge} from './utils/collection'; import {forEach, last, merge} from './utils/collection';
import {TreeNode} from './utils/tree'; import {TreeNode} from './utils/tree';

View File

@ -11,7 +11,6 @@ import {ComponentRef} from '@angular/core';
import {ActivatedRoute, ActivatedRouteSnapshot} from './router_state'; import {ActivatedRoute, ActivatedRouteSnapshot} from './router_state';
import {TreeNode} from './utils/tree'; import {TreeNode} from './utils/tree';
/** /**
* @whatItDoes Represents the detached route tree. * @whatItDoes Represents the detached route tree.
* *
@ -22,43 +21,30 @@ import {TreeNode} from './utils/tree';
*/ */
export type DetachedRouteHandle = {}; export type DetachedRouteHandle = {};
/** /** @internal */
* @internal
*/
export type DetachedRouteHandleInternal = { export type DetachedRouteHandleInternal = {
componentRef: ComponentRef<any>, componentRef: ComponentRef<any>,
route: TreeNode<ActivatedRoute> route: TreeNode<ActivatedRoute>,
}; };
/** /**
* @whatItDoes Provides a way to customize when activated routes get reused. * @whatItDoes Provides a way to customize when activated routes get reused.
* *
* @experimental * @experimental
*/ */
export abstract class RouteReuseStrategy { export abstract class RouteReuseStrategy {
/** /** Determines if this route (and its subtree) should be detached to be reused later */
* Determines if this route (and its subtree) should be detached to be reused later.
*/
abstract shouldDetach(route: ActivatedRouteSnapshot): boolean; abstract shouldDetach(route: ActivatedRouteSnapshot): boolean;
/** /** Stores the detached route */
* Stores the detached route.
*/
abstract store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void; abstract store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void;
/** /** Determines if this route (and its subtree) should be reattached */
* Determines if this route (and its subtree) should be reattached.
*/
abstract shouldAttach(route: ActivatedRouteSnapshot): boolean; abstract shouldAttach(route: ActivatedRouteSnapshot): boolean;
/** /** Retrieves the previously stored route */
* Retrieves the previously stored route.
*/
abstract retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle; abstract retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle;
/** /** Determines if a route should be reused */
* Determines if a route should be reused.
*/
abstract shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean; abstract shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean;
} }

View File

@ -13,7 +13,6 @@ import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject'; import {Subject} from 'rxjs/Subject';
import {Subscription} from 'rxjs/Subscription'; import {Subscription} from 'rxjs/Subscription';
import {from} from 'rxjs/observable/from'; import {from} from 'rxjs/observable/from';
import {fromPromise} from 'rxjs/observable/fromPromise';
import {of } from 'rxjs/observable/of'; import {of } from 'rxjs/observable/of';
import {concatMap} from 'rxjs/operator/concatMap'; import {concatMap} from 'rxjs/operator/concatMap';
import {every} from 'rxjs/operator/every'; import {every} from 'rxjs/operator/every';
@ -148,7 +147,6 @@ export class NavigationStart {
constructor( constructor(
/** @docsNotRequired */ /** @docsNotRequired */
public id: number, public id: number,
/** @docsNotRequired */ /** @docsNotRequired */
public url: string) {} public url: string) {}
@ -166,10 +164,8 @@ export class NavigationEnd {
constructor( constructor(
/** @docsNotRequired */ /** @docsNotRequired */
public id: number, public id: number,
/** @docsNotRequired */ /** @docsNotRequired */
public url: string, public url: string,
/** @docsNotRequired */ /** @docsNotRequired */
public urlAfterRedirects: string) {} public urlAfterRedirects: string) {}
@ -189,10 +185,8 @@ export class NavigationCancel {
constructor( constructor(
/** @docsNotRequired */ /** @docsNotRequired */
public id: number, public id: number,
/** @docsNotRequired */ /** @docsNotRequired */
public url: string, public url: string,
/** @docsNotRequired */ /** @docsNotRequired */
public reason: string) {} public reason: string) {}
@ -210,10 +204,8 @@ export class NavigationError {
constructor( constructor(
/** @docsNotRequired */ /** @docsNotRequired */
public id: number, public id: number,
/** @docsNotRequired */ /** @docsNotRequired */
public url: string, public url: string,
/** @docsNotRequired */ /** @docsNotRequired */
public error: any) {} public error: any) {}
@ -233,7 +225,6 @@ export class RoutesRecognized {
constructor( constructor(
/** @docsNotRequired */ /** @docsNotRequired */
public id: number, public id: number,
/** @docsNotRequired */ /** @docsNotRequired */
public url: string, public url: string,
/** @docsNotRequired */ /** @docsNotRequired */
@ -282,7 +273,7 @@ type NavigationParams = {
resolve: any, resolve: any,
reject: any, reject: any,
promise: Promise<boolean>, promise: Promise<boolean>,
imperative: boolean imperative: boolean,
}; };
@ -312,9 +303,8 @@ export class Router {
private currentUrlTree: UrlTree; private currentUrlTree: UrlTree;
private rawUrlTree: UrlTree; private rawUrlTree: UrlTree;
private navigations: BehaviorSubject<NavigationParams> = private navigations = new BehaviorSubject<NavigationParams>(null);
new BehaviorSubject<NavigationParams>(null); private routerEvents = new Subject<Event>();
private routerEvents: Subject<Event> = new Subject<Event>();
private currentRouterState: RouterState; private currentRouterState: RouterState;
private locationSubscription: Subscription; private locationSubscription: Subscription;
@ -353,7 +343,6 @@ export class Router {
this.rawUrlTree = this.currentUrlTree; this.rawUrlTree = this.currentUrlTree;
this.configLoader = new RouterConfigLoader(loader, compiler); this.configLoader = new RouterConfigLoader(loader, compiler);
this.currentRouterState = createEmptyState(this.currentUrlTree, this.rootComponentType); this.currentRouterState = createEmptyState(this.currentUrlTree, this.rootComponentType);
this.processNavigations(); this.processNavigations();
} }
@ -403,19 +392,13 @@ export class Router {
} }
} }
/** /** The current route state */
* Returns the current route state.
*/
get routerState(): RouterState { return this.currentRouterState; } get routerState(): RouterState { return this.currentRouterState; }
/** /** The current url */
* Returns the current url.
*/
get url(): string { return this.serializeUrl(this.currentUrlTree); } get url(): string { return this.serializeUrl(this.currentUrlTree); }
/** /** An observable of router events */
* Returns an observable of route events
*/
get events(): Observable<Event> { return this.routerEvents; } get events(): Observable<Event> { return this.routerEvents; }
/** /**
@ -428,7 +411,7 @@ export class Router {
* { path: 'team/:id', component: TeamCmp, children: [ * { path: 'team/:id', component: TeamCmp, children: [
* { path: 'simple', component: SimpleCmp }, * { path: 'simple', component: SimpleCmp },
* { path: 'user/:name', component: UserCmp } * { path: 'user/:name', component: UserCmp }
* ] } * ]}
* ]); * ]);
* ``` * ```
*/ */
@ -437,14 +420,10 @@ export class Router {
this.config = config; this.config = config;
} }
/** /** @docsNotRequired */
* @docsNotRequired
*/
ngOnDestroy() { this.dispose(); } ngOnDestroy() { this.dispose(); }
/** /** Disposes of the router */
* Disposes of the router.
*/
dispose(): void { dispose(): void {
if (this.locationSubscription) { if (this.locationSubscription) {
this.locationSubscription.unsubscribe(); this.locationSubscription.unsubscribe();
@ -496,7 +475,7 @@ export class Router {
createUrlTree( createUrlTree(
commands: any[], {relativeTo, queryParams, fragment, preserveQueryParams, commands: any[], {relativeTo, queryParams, fragment, preserveQueryParams,
preserveFragment}: NavigationExtras = {}): UrlTree { preserveFragment}: NavigationExtras = {}): UrlTree {
const a = relativeTo ? relativeTo : this.routerState.root; const a = relativeTo || this.routerState.root;
const q = preserveQueryParams ? this.currentUrlTree.queryParams : queryParams; const q = preserveQueryParams ? this.currentUrlTree.queryParams : queryParams;
const f = preserveFragment ? this.currentUrlTree.fragment : fragment; const f = preserveFragment ? this.currentUrlTree.fragment : fragment;
return createUrlTree(a, this.currentUrlTree, commands, q, f); return createUrlTree(a, this.currentUrlTree, commands, q, f);
@ -506,9 +485,9 @@ export class Router {
* Navigate based on the provided url. This navigation is always absolute. * Navigate based on the provided url. This navigation is always absolute.
* *
* Returns a promise that: * Returns a promise that:
* - is resolved with 'true' when navigation succeeds * - resolves to 'true' when navigation succeeds,
* - is resolved with 'false' when navigation fails * - resolves to 'false' when navigation fails,
* - is rejected when an error happens * - is rejected when an error happens.
* *
* ### Usage * ### Usage
* *
@ -527,11 +506,11 @@ export class Router {
if (url instanceof UrlTree) { if (url instanceof UrlTree) {
return this.scheduleNavigation( return this.scheduleNavigation(
this.urlHandlingStrategy.merge(url, this.rawUrlTree), true, extras); this.urlHandlingStrategy.merge(url, this.rawUrlTree), true, extras);
} else {
const urlTree = this.urlSerializer.parse(url);
return this.scheduleNavigation(
this.urlHandlingStrategy.merge(urlTree, this.rawUrlTree), true, extras);
} }
const urlTree = this.urlSerializer.parse(url);
return this.scheduleNavigation(
this.urlHandlingStrategy.merge(urlTree, this.rawUrlTree), true, extras);
} }
/** /**
@ -539,9 +518,9 @@ export class Router {
* If no starting route is provided, the navigation is absolute. * If no starting route is provided, the navigation is absolute.
* *
* Returns a promise that: * Returns a promise that:
* - is resolved with 'true' when navigation succeeds * - resolves to 'true' when navigation succeeds,
* - is resolved with 'false' when navigation fails * - resolves to 'false' when navigation fails,
* - is rejected when an error happens * - is rejected when an error happens.
* *
* ### Usage * ### Usage
* *
@ -549,11 +528,11 @@ export class Router {
* router.navigate(['team', 33, 'user', 11], {relativeTo: route}); * router.navigate(['team', 33, 'user', 11], {relativeTo: route});
* *
* // Navigate without updating the URL * // Navigate without updating the URL
* router.navigate(['team', 33, 'user', 11], {relativeTo: route, skipLocationChange: true }); * router.navigate(['team', 33, 'user', 11], {relativeTo: route, skipLocationChange: true});
* ``` * ```
* *
* In opposite to `navigateByUrl`, `navigate` always takes a delta * In opposite to `navigateByUrl`, `navigate` always takes a delta that is applied to the current
* that is applied to the current URL. * URL.
*/ */
navigate(commands: any[], extras: NavigationExtras = {skipLocationChange: false}): navigate(commands: any[], extras: NavigationExtras = {skipLocationChange: false}):
Promise<boolean> { Promise<boolean> {
@ -563,19 +542,13 @@ export class Router {
return this.navigateByUrl(this.createUrlTree(commands, extras), extras); return this.navigateByUrl(this.createUrlTree(commands, extras), extras);
} }
/** /** Serializes a {@link UrlTree} into a string */
* Serializes a {@link UrlTree} into a string.
*/
serializeUrl(url: UrlTree): string { return this.urlSerializer.serialize(url); } serializeUrl(url: UrlTree): string { return this.urlSerializer.serialize(url); }
/** /** Parses a string into a {@link UrlTree} */
* Parses a string into a {@link UrlTree}.
*/
parseUrl(url: string): UrlTree { return this.urlSerializer.parse(url); } parseUrl(url: string): UrlTree { return this.urlSerializer.parse(url); }
/** /** Returns whether the url is activated */
* Returns if the url is activated or not.
*/
isActive(url: string|UrlTree, exact: boolean): boolean { isActive(url: string|UrlTree, exact: boolean): boolean {
if (url instanceof UrlTree) { if (url instanceof UrlTree) {
return containsTree(this.currentUrlTree, url, exact); return containsTree(this.currentUrlTree, url, exact);

View File

@ -16,7 +16,6 @@ import {UrlSegment, UrlSegmentGroup, UrlTree, equalSegments} from './url_tree';
import {merge, shallowEqual, shallowEqualArrays} from './utils/collection'; import {merge, shallowEqual, shallowEqualArrays} from './utils/collection';
import {Tree, TreeNode} from './utils/tree'; import {Tree, TreeNode} from './utils/tree';
/** /**
* @whatItDoes Represents the state of the router. * @whatItDoes Represents the state of the router.
* *
@ -45,14 +44,10 @@ import {Tree, TreeNode} from './utils/tree';
* @stable * @stable
*/ */
export class RouterState extends Tree<ActivatedRoute> { export class RouterState extends Tree<ActivatedRoute> {
/** /** @internal */
* @internal
*/
constructor( constructor(
root: TreeNode<ActivatedRoute>, root: TreeNode<ActivatedRoute>,
/** /** The current snapshot of the router state */
* The current snapshot of the router state.
*/
public snapshot: RouterStateSnapshot) { public snapshot: RouterStateSnapshot) {
super(root); super(root);
setRouterStateSnapshot<RouterState, ActivatedRoute>(this, root); setRouterStateSnapshot<RouterState, ActivatedRoute>(this, root);
@ -90,17 +85,18 @@ export function createEmptyStateSnapshot(
/** /**
* @whatItDoes Contains the information about a route associated with a component loaded in an * @whatItDoes Contains the information about a route associated with a component loaded in an
* outlet. * outlet.
* ActivatedRoute can also be used to traverse the router state tree. * An `ActivatedRoute` can also be used to traverse the router state tree.
* *
* @howToUse * @howToUse
* *
* ``` * ```
* @Component({templateUrl:'./my-component.html'}) * @Component({...})
* class MyComponent { * class MyComponent {
* constructor(route: ActivatedRoute) { * constructor(route: ActivatedRoute) {
* const id: Observable<string> = route.params.map(p => p.id); * const id: Observable<string> = route.params.map(p => p.id);
* const url: Observable<string> = route.url.map(s => s.join('')); * const url: Observable<string> = route.url.map(segments => segments.join(''));
* const user = route.data.map(d => d.user); //includes `data` and `resolve` * // route.data includes both `data` and `resolve`
* const user = route.data.map(d => d.user);
* } * }
* } * }
* ``` * ```
@ -108,112 +104,64 @@ export function createEmptyStateSnapshot(
* @stable * @stable
*/ */
export class ActivatedRoute { export class ActivatedRoute {
/** The current snapshot of this route */
snapshot: ActivatedRouteSnapshot;
/** @internal */ /** @internal */
_futureSnapshot: ActivatedRouteSnapshot; _futureSnapshot: ActivatedRouteSnapshot;
/**
* The current snapshot of this route.
*/
snapshot: ActivatedRouteSnapshot;
/** @internal */ /** @internal */
_routerState: RouterState; _routerState: RouterState;
/** /** @internal */
* @internal
*/
constructor( constructor(
/** /** An observable of the URL segments matched by this route */
* The URL segments matched by this route. The observable will emit a new value when
* the array of segments changes.
*/
public url: Observable<UrlSegment[]>, public url: Observable<UrlSegment[]>,
/** An observable of the matrix parameters scoped to this route */
/**
* The matrix parameters scoped to this route. The observable will emit a new value when
* the set of the parameters changes.
*/
public params: Observable<Params>, public params: Observable<Params>,
/** An observable of the query parameters shared by all the routes */
/**
* The query parameters shared by all the routes. The observable will emit a new value when
* the set of the parameters changes.
*/
public queryParams: Observable<Params>, public queryParams: Observable<Params>,
/** An observable of the URL fragment shared by all the routes */
/**
* The URL fragment shared by all the routes. The observable will emit a new value when
* the URL fragment changes.
*/
public fragment: Observable<string>, public fragment: Observable<string>,
/** An observable of the static and resolved data of this route. */
/**
* The static and resolved data of this route. The observable will emit a new value when
* any of the resolvers returns a new object.
*/
public data: Observable<Data>, public data: Observable<Data>,
/** The outlet name of the route. It's a constant */
/**
* The outlet name of the route. It's a constant.
*/
public outlet: string, public outlet: string,
/** The component of the route. It's a constant */
/** // TODO(vsavkin): remove |string
* The component of the route. It's a constant. public component: Type<any>|string, futureSnapshot: ActivatedRouteSnapshot) {
*/
public component: Type<any>|string, // TODO: vsavkin: remove |string
futureSnapshot: ActivatedRouteSnapshot) {
this._futureSnapshot = futureSnapshot; this._futureSnapshot = futureSnapshot;
} }
/** /** The configuration used to match this route */
* The configuration used to match this route.
*/
get routeConfig(): Route { return this._futureSnapshot.routeConfig; } get routeConfig(): Route { return this._futureSnapshot.routeConfig; }
/** /** The root of the router state */
* The root of the router state.
*/
get root(): ActivatedRoute { return this._routerState.root; } get root(): ActivatedRoute { return this._routerState.root; }
/** /** The parent of this route in the router state tree */
* The parent of this route in the router state tree.
*/
get parent(): ActivatedRoute { return this._routerState.parent(this); } get parent(): ActivatedRoute { return this._routerState.parent(this); }
/** /** The first child of this route in the router state tree */
* The first child of this route in the router state tree.
*/
get firstChild(): ActivatedRoute { return this._routerState.firstChild(this); } get firstChild(): ActivatedRoute { return this._routerState.firstChild(this); }
/** /** The children of this route in the router state tree */
* The children of this route in the router state tree.
*/
get children(): ActivatedRoute[] { return this._routerState.children(this); } get children(): ActivatedRoute[] { return this._routerState.children(this); }
/** /** The path from the root of the router state tree to this route */
* The path from the root of the router state tree to this route.
*/
get pathFromRoot(): ActivatedRoute[] { return this._routerState.pathFromRoot(this); } get pathFromRoot(): ActivatedRoute[] { return this._routerState.pathFromRoot(this); }
/**
* @docsNotRequired
*/
toString(): string { toString(): string {
return this.snapshot ? this.snapshot.toString() : `Future(${this._futureSnapshot})`; return this.snapshot ? this.snapshot.toString() : `Future(${this._futureSnapshot})`;
} }
} }
/** /** @internal */
* @internal
*/
export type Inherited = { export type Inherited = {
params: Params; data: Data; resolve: Data; params: Params,
data: Data,
resolve: Data,
} }
/** /** @internal */
* @internal
*/
export function export function
inheritedParamsDataResolve(route: ActivatedRouteSnapshot): inheritedParamsDataResolve(route: ActivatedRouteSnapshot):
Inherited { Inherited {
@ -269,59 +217,32 @@ inheritedParamsDataResolve(route: ActivatedRouteSnapshot):
export class ActivatedRouteSnapshot { export class ActivatedRouteSnapshot {
/** @internal **/ /** @internal **/
_routeConfig: Route; _routeConfig: Route;
/** @internal **/ /** @internal **/
_urlSegment: UrlSegmentGroup; _urlSegment: UrlSegmentGroup;
/** @internal */ /** @internal */
_lastPathIndex: number; _lastPathIndex: number;
/** @internal */ /** @internal */
_resolve: ResolveData; _resolve: ResolveData;
/** @internal */ /** @internal */
_resolvedData: Data; _resolvedData: Data;
/** @internal */ /** @internal */
_routerState: RouterStateSnapshot; _routerState: RouterStateSnapshot;
/** /** @internal */
* @internal
*/
constructor( constructor(
/** /** The URL segments matched by this route */
* The URL segments matched by this route.
*/
public url: UrlSegment[], public url: UrlSegment[],
/** The matrix parameters scoped to this route */
/**
* The matrix parameters scoped to this route.
*/
public params: Params, public params: Params,
/** The query parameters shared by all the routes */
/**
* The query parameters shared by all the routes.
*/
public queryParams: Params, public queryParams: Params,
/** The URL fragment shared by all the routes */
/**
* The URL fragment shared by all the routes.
*/
public fragment: string, public fragment: string,
/** The static and resolved data of this route */
/**
* The static and resolved data of this route.
*/
public data: Data, public data: Data,
/** The outlet name of the route */
/**
* The outlet name of the route.
*/
public outlet: string, public outlet: string,
/** The component of the route */
/**
* The component of the route.
*/
public component: Type<any>|string, routeConfig: Route, urlSegment: UrlSegmentGroup, public component: Type<any>|string, routeConfig: Route, urlSegment: UrlSegmentGroup,
lastPathIndex: number, resolve: ResolveData) { lastPathIndex: number, resolve: ResolveData) {
this._routeConfig = routeConfig; this._routeConfig = routeConfig;
@ -330,41 +251,26 @@ export class ActivatedRouteSnapshot {
this._resolve = resolve; this._resolve = resolve;
} }
/** /** The configuration used to match this route */
* The configuration used to match this route.
*/
get routeConfig(): Route { return this._routeConfig; } get routeConfig(): Route { return this._routeConfig; }
/** /** The root of the router state */
* The root of the router state.
*/
get root(): ActivatedRouteSnapshot { return this._routerState.root; } get root(): ActivatedRouteSnapshot { return this._routerState.root; }
/** /** The parent of this route in the router state tree */
* The parent of this route in the router state tree.
*/
get parent(): ActivatedRouteSnapshot { return this._routerState.parent(this); } get parent(): ActivatedRouteSnapshot { return this._routerState.parent(this); }
/** /** The first child of this route in the router state tree */
* The first child of this route in the router state tree.
*/
get firstChild(): ActivatedRouteSnapshot { return this._routerState.firstChild(this); } get firstChild(): ActivatedRouteSnapshot { return this._routerState.firstChild(this); }
/** /** The children of this route in the router state tree */
* The children of this route in the router state tree.
*/
get children(): ActivatedRouteSnapshot[] { return this._routerState.children(this); } get children(): ActivatedRouteSnapshot[] { return this._routerState.children(this); }
/** /** The path from the root of the router state tree to this route */
* The path from the root of the router state tree to this route.
*/
get pathFromRoot(): ActivatedRouteSnapshot[] { return this._routerState.pathFromRoot(this); } get pathFromRoot(): ActivatedRouteSnapshot[] { return this._routerState.pathFromRoot(this); }
/**
* @docsNotRequired
*/
toString(): string { toString(): string {
const url = this.url.map(s => s.toString()).join('/'); const url = this.url.map(segment => segment.toString()).join('/');
const matched = this._routeConfig ? this._routeConfig.path : ''; const matched = this._routeConfig ? this._routeConfig.path : '';
return `Route(url:'${url}', path:'${matched}')`; return `Route(url:'${url}', path:'${matched}')`;
} }
@ -396,11 +302,8 @@ export class ActivatedRouteSnapshot {
* @stable * @stable
*/ */
export class RouterStateSnapshot extends Tree<ActivatedRouteSnapshot> { export class RouterStateSnapshot extends Tree<ActivatedRouteSnapshot> {
/** /** @internal */
* @internal
*/
constructor( constructor(
/** The url from which this snapshot was created */ /** The url from which this snapshot was created */
public url: string, root: TreeNode<ActivatedRouteSnapshot>) { public url: string, root: TreeNode<ActivatedRouteSnapshot>) {
super(root); super(root);

View File

@ -17,10 +17,10 @@ export function containsTree(container: UrlTree, containee: UrlTree, exact: bool
if (exact) { if (exact) {
return equalQueryParams(container.queryParams, containee.queryParams) && return equalQueryParams(container.queryParams, containee.queryParams) &&
equalSegmentGroups(container.root, containee.root); equalSegmentGroups(container.root, containee.root);
} else {
return containsQueryParams(container.queryParams, containee.queryParams) &&
containsSegmentGroup(container.root, containee.root);
} }
return containsQueryParams(container.queryParams, containee.queryParams) &&
containsSegmentGroup(container.root, containee.root);
} }
function equalQueryParams( function equalQueryParams(
@ -83,7 +83,7 @@ function containsSegmentGroupHelper(
* class MyComponent { * class MyComponent {
* constructor(router: Router) { * constructor(router: Router) {
* const tree: UrlTree = * const tree: UrlTree =
* router.parseUrl('/team/33/(user/victor//support:help)?debug=true#fragment'); * router.parseUrl('/team/33/(user/victor//support:help)?debug=true#fragment');
* const f = tree.fragment; // return 'fragment' * const f = tree.fragment; // return 'fragment'
* const q = tree.queryParams; // returns {debug: 'true'} * const q = tree.queryParams; // returns {debug: 'true'}
* const g: UrlSegmentGroup = tree.root.children[PRIMARY_OUTLET]; * const g: UrlSegmentGroup = tree.root.children[PRIMARY_OUTLET];
@ -103,79 +103,49 @@ function containsSegmentGroupHelper(
* @stable * @stable
*/ */
export class UrlTree { export class UrlTree {
/** /** @internal */
* @internal
*/
constructor( constructor(
/** /** The root segment group of the URL tree */
* The root segment group of the URL tree.
*/
public root: UrlSegmentGroup, public root: UrlSegmentGroup,
/** /** The query params of the URL */
* The query params of the URL.
*/
public queryParams: {[key: string]: string}, public queryParams: {[key: string]: string},
/** /** The fragment of the URL */
* The fragment of the URL.
*/
public fragment: string) {} public fragment: string) {}
/** /** @docsNotRequired */
* @docsNotRequired
*/
toString(): string { return new DefaultUrlSerializer().serialize(this); } toString(): string { return new DefaultUrlSerializer().serialize(this); }
} }
/** /**
* @whatItDoes Represents the parsed URL segment. * @whatItDoes Represents the parsed URL segment group.
* *
* See {@link UrlTree} for more information. * See {@link UrlTree} for more information.
* *
* @stable * @stable
*/ */
export class UrlSegmentGroup { export class UrlSegmentGroup {
/** /** @internal */
* @internal
*/
_sourceSegment: UrlSegmentGroup; _sourceSegment: UrlSegmentGroup;
/** @internal */
/**
* @internal
*/
_segmentIndexShift: number; _segmentIndexShift: number;
/** The parent node in the url tree */
/** parent: UrlSegmentGroup = null;
* The parent node in the url tree.
*/
public parent: UrlSegmentGroup = null;
constructor( constructor(
/** /** The URL segments of this group. See {@link UrlSegment} for more information */
* The URL segments of this group. See {@link UrlSegment} for more information.
*/
public segments: UrlSegment[], public segments: UrlSegment[],
/** /** The list of children of this group */
* The list of children of this group. public children: {[key: string]: UrlSegmentGroup}) {
*/
public children: {[key: string]: UrlSegmentGroup}
) {
forEach(children, (v: any, k: any) => v.parent = this); forEach(children, (v: any, k: any) => v.parent = this);
} }
/** /** Wether the segment has child segments */
* Return true if the segment has child segments
*/
hasChildren(): boolean { return this.numberOfChildren > 0; } hasChildren(): boolean { return this.numberOfChildren > 0; }
/** /** Number of child segments */
* Returns the number of child sements.
*/
get numberOfChildren(): number { return Object.keys(this.children).length; } get numberOfChildren(): number { return Object.keys(this.children).length; }
/** /** @docsNotRequired */
* @docsNotRequired
*/
toString(): string { return serializePaths(this); } toString(): string { return serializePaths(this); }
} }
@ -200,26 +170,20 @@ export class UrlSegmentGroup {
* *
* @description * @description
* *
* A UrlSegment is a part of a URL between the two slashes. It contains a path and * A UrlSegment is a part of a URL between the two slashes. It contains a path and the matrix
* the matrix parameters associated with the segment. * parameters associated with the segment.
* *
* @stable * @stable
*/ */
export class UrlSegment { export class UrlSegment {
constructor( constructor(
/** /** The path part of a URL segment */
* The path part of a URL segment.
*/
public path: string, public path: string,
/** /** The matrix parameters associated with a segment */
* The matrix parameters associated with a segment. public parameters: {[name: string]: string}) {}
*/
public parameters: {[key: string]: string}) {}
/** /** @docsNotRequired */
* @docsNotRequired
*/
toString(): string { return serializePath(this); } toString(): string { return serializePath(this); }
} }
@ -268,14 +232,10 @@ export function mapChildrenIntoArray<T>(
* @stable * @stable
*/ */
export abstract class UrlSerializer { export abstract class UrlSerializer {
/** /** Parse a url into a {@link UrlTree} */
* Parse a url into a {@link UrlTree}.
*/
abstract parse(url: string): UrlTree; abstract parse(url: string): UrlTree;
/** /** Converts a {@link UrlTree} into a url */
* Converts a {@link UrlTree} into a url.
*/
abstract serialize(tree: UrlTree): string; abstract serialize(tree: UrlTree): string;
} }
@ -298,17 +258,13 @@ export abstract class UrlSerializer {
* @stable * @stable
*/ */
export class DefaultUrlSerializer implements UrlSerializer { export class DefaultUrlSerializer implements UrlSerializer {
/** /** Parses a url into a {@link UrlTree} */
* Parse a url into a {@link UrlTree}.
*/
parse(url: string): UrlTree { parse(url: string): UrlTree {
const p = new UrlParser(url); const p = new UrlParser(url);
return new UrlTree(p.parseRootSegment(), p.parseQueryParams(), p.parseFragment()); return new UrlTree(p.parseRootSegment(), p.parseQueryParams(), p.parseFragment());
} }
/** /** Converts a {@link UrlTree} into a url */
* Converts a {@link UrlTree} into a url.
*/
serialize(tree: UrlTree): string { serialize(tree: UrlTree): string {
const segment = `/${serializeSegment(tree.root, true)}`; const segment = `/${serializeSegment(tree.root, true)}`;
const query = serializeQueryParams(tree.queryParams); const query = serializeQueryParams(tree.queryParams);
@ -383,6 +339,7 @@ function serializeQueryParams(params: {[key: string]: any}): string {
class Pair<A, B> { class Pair<A, B> {
constructor(public first: A, public second: B) {} constructor(public first: A, public second: B) {}
} }
function pairs<T>(obj: {[key: string]: T}): Pair<string, T>[] { function pairs<T>(obj: {[key: string]: T}): Pair<string, T>[] {
const res: Pair<string, T>[] = []; const res: Pair<string, T>[] = [];
for (const prop in obj) { for (const prop in obj) {
@ -436,9 +393,9 @@ class UrlParser {
if (this.remaining === '' || this.remaining.startsWith('?') || this.remaining.startsWith('#')) { if (this.remaining === '' || this.remaining.startsWith('?') || this.remaining.startsWith('#')) {
return new UrlSegmentGroup([], {}); return new UrlSegmentGroup([], {});
} else {
return new UrlSegmentGroup([], this.parseChildren());
} }
return new UrlSegmentGroup([], this.parseChildren());
} }
parseChildren(): {[key: string]: UrlSegmentGroup} { parseChildren(): {[key: string]: UrlSegmentGroup} {
@ -508,9 +465,9 @@ class UrlParser {
parseFragment(): string { parseFragment(): string {
if (this.peekStartsWith('#')) { if (this.peekStartsWith('#')) {
return decodeURI(this.remaining.substring(1)); return decodeURI(this.remaining.substring(1));
} else {
return null;
} }
return null;
} }
parseMatrixParams(): {[key: string]: any} { parseMatrixParams(): {[key: string]: any} {

View File

@ -60,7 +60,7 @@ export function last<T>(a: T[]): T {
} }
export function and(bools: boolean[]): boolean { export function and(bools: boolean[]): boolean {
return bools.reduce((a, b) => a && b, true); return !bools.some(v => !v);
} }
export function merge<V>(m1: {[key: string]: V}, m2: {[key: string]: V}): {[key: string]: V} { export function merge<V>(m1: {[key: string]: V}, m2: {[key: string]: V}): {[key: string]: V} {
@ -81,8 +81,7 @@ export function merge<V>(m1: {[key: string]: V}, m2: {[key: string]: V}): {[key:
return m; return m;
} }
export function forEach<K, V>( export function forEach<K, V>(map: {[key: string]: V}, callback: (v: V, k: string) => void): void {
map: {[key: string]: V}, callback: /*(V, K) => void*/ Function): void {
for (const prop in map) { for (const prop in map) {
if (map.hasOwnProperty(prop)) { if (map.hasOwnProperty(prop)) {
callback(map[prop], prop); callback(map[prop], prop);
@ -117,9 +116,9 @@ export function waitForMap<A, B>(
const concatted$ = concatAll.call(of (...waitFor)); const concatted$ = concatAll.call(of (...waitFor));
const last$ = l.last.call(concatted$); const last$ = l.last.call(concatted$);
return map.call(last$, () => res); return map.call(last$, () => res);
} else {
return of (res);
} }
return of (res);
} }
export function andObservables(observables: Observable<Observable<any>>): Observable<boolean> { export function andObservables(observables: Observable<Observable<any>>): Observable<boolean> {
@ -130,9 +129,11 @@ export function andObservables(observables: Observable<Observable<any>>): Observ
export function wrapIntoObservable<T>(value: T | Promise<T>| Observable<T>): Observable<T> { export function wrapIntoObservable<T>(value: T | Promise<T>| Observable<T>): Observable<T> {
if (value instanceof Observable) { if (value instanceof Observable) {
return value; return value;
} else if (value instanceof Promise) {
return fromPromise(value);
} else {
return of (value);
} }
if (value instanceof Promise) {
return fromPromise(value);
}
return of (value);
} }

View File

@ -366,13 +366,13 @@ export declare abstract class UrlHandlingStrategy {
/** @stable */ /** @stable */
export declare class UrlSegment { export declare class UrlSegment {
parameters: { parameters: {
[key: string]: string; [name: string]: string;
}; };
path: string; path: string;
constructor( constructor(
path: string, path: string,
parameters: { parameters: {
[key: string]: string; [name: string]: string;
}); });
toString(): string; toString(): string;
} }