refactor(router): update link to reuse url segments when possible
This commit is contained in:
parent
12637a761c
commit
d00b26d941
|
@ -1,11 +1,10 @@
|
|||
import {Tree, TreeNode, UrlSegment, RouteSegment, rootNode, UrlTree, RouteTree} from './segments';
|
||||
import {isBlank, isPresent, isString, isStringMap} from './facade/lang';
|
||||
import {BaseException} from './facade/exceptions';
|
||||
import {ListWrapper} from './facade/collection';
|
||||
import {ListWrapper, StringMapWrapper} from './facade/collection';
|
||||
|
||||
// TODO: vsavkin: should reuse segments
|
||||
export function link(segment: RouteSegment, routeTree: RouteTree, urlTree: UrlTree,
|
||||
commands: any[]): UrlTree {
|
||||
export function link(segment: RouteSegment, routeTree: RouteTree, urlTree: UrlTree, commands: any[]): UrlTree {
|
||||
if (commands.length === 0) return urlTree;
|
||||
|
||||
let normalizedCommands = _normalizeCommands(commands);
|
||||
|
@ -14,22 +13,20 @@ export function link(segment: RouteSegment, routeTree: RouteTree, urlTree: UrlTr
|
|||
}
|
||||
|
||||
let startingNode = _findStartingNode(normalizedCommands, urlTree, segment, routeTree);
|
||||
let updated =
|
||||
normalizedCommands.commands.length > 0 ?
|
||||
_updateMany(ListWrapper.clone(startingNode.children), normalizedCommands.commands) :
|
||||
[];
|
||||
let updated = normalizedCommands.commands.length > 0 ?
|
||||
_updateMany(ListWrapper.clone(startingNode.children), normalizedCommands.commands) : [];
|
||||
let newRoot = _constructNewTree(rootNode(urlTree), startingNode, updated);
|
||||
|
||||
return new UrlTree(newRoot);
|
||||
}
|
||||
|
||||
function _navigateToRoot(normalizedChange: _NormalizedNavigationCommands): boolean {
|
||||
return normalizedChange.isAbsolute && normalizedChange.commands.length === 1 &&
|
||||
normalizedChange.commands[0] == "/";
|
||||
function _navigateToRoot(normalizedChange:_NormalizedNavigationCommands):boolean {
|
||||
return normalizedChange.isAbsolute && normalizedChange.commands.length === 1 && normalizedChange.commands[0] == "/";
|
||||
}
|
||||
|
||||
class _NormalizedNavigationCommands {
|
||||
constructor(public isAbsolute: boolean, public numberOfDoubleDots: number,
|
||||
constructor(public isAbsolute: boolean,
|
||||
public numberOfDoubleDots: number,
|
||||
public commands: any[]) {}
|
||||
}
|
||||
|
||||
|
@ -56,18 +53,18 @@ function _normalizeCommands(commands: any[]): _NormalizedNavigationCommands {
|
|||
|
||||
// first exp is treated in a special way
|
||||
if (i == 0) {
|
||||
if (j == 0 && cc == ".") { // './a'
|
||||
if (j == 0 && cc == ".") { // './a'
|
||||
// skip it
|
||||
} else if (j == 0 && cc == "") { // '/a'
|
||||
} else if (j == 0 && cc == "") { // '/a'
|
||||
isAbsolute = true;
|
||||
} else if (cc == "..") { // '../a'
|
||||
} else if (cc == "..") { // '../a'
|
||||
numberOfDoubleDots++;
|
||||
} else if (cc != '') {
|
||||
res.push(cc);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (cc != '') {
|
||||
if (cc != ''){
|
||||
res.push(cc);
|
||||
}
|
||||
}
|
||||
|
@ -77,8 +74,7 @@ function _normalizeCommands(commands: any[]): _NormalizedNavigationCommands {
|
|||
return new _NormalizedNavigationCommands(isAbsolute, numberOfDoubleDots, res);
|
||||
}
|
||||
|
||||
function _findUrlSegment(segment: RouteSegment, routeTree: RouteTree, urlTree: UrlTree,
|
||||
numberOfDoubleDots: number): UrlSegment {
|
||||
function _findUrlSegment(segment: RouteSegment, routeTree: RouteTree, urlTree: UrlTree, numberOfDoubleDots: number): UrlSegment {
|
||||
let s = segment;
|
||||
while (s.urlSegments.length === 0) {
|
||||
s = routeTree.parent(s);
|
||||
|
@ -91,13 +87,11 @@ function _findUrlSegment(segment: RouteSegment, routeTree: RouteTree, urlTree: U
|
|||
return path[path.length - 1 - numberOfDoubleDots];
|
||||
}
|
||||
|
||||
function _findStartingNode(normalizedChange: _NormalizedNavigationCommands, urlTree: UrlTree,
|
||||
segment: RouteSegment, routeTree: RouteTree): TreeNode<UrlSegment> {
|
||||
function _findStartingNode(normalizedChange:_NormalizedNavigationCommands, urlTree:UrlTree, segment:RouteSegment, routeTree:RouteTree):TreeNode<UrlSegment> {
|
||||
if (normalizedChange.isAbsolute) {
|
||||
return rootNode(urlTree);
|
||||
} else {
|
||||
let urlSegment =
|
||||
_findUrlSegment(segment, routeTree, urlTree, normalizedChange.numberOfDoubleDots);
|
||||
let urlSegment = _findUrlSegment(segment, routeTree, urlTree, normalizedChange.numberOfDoubleDots);
|
||||
return _findMatchingNode(urlSegment, rootNode(urlTree));
|
||||
}
|
||||
}
|
||||
|
@ -117,7 +111,7 @@ function _constructNewTree(node: TreeNode<UrlSegment>, original: TreeNode<UrlSeg
|
|||
return new TreeNode<UrlSegment>(node.value, updated);
|
||||
} else {
|
||||
return new TreeNode<UrlSegment>(
|
||||
node.value, node.children.map(c => _constructNewTree(c, original, updated)));
|
||||
node.value, node.children.map(c => _constructNewTree(c, original, updated)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,7 +128,7 @@ function _update(node: TreeNode<UrlSegment>, commands: any[]): TreeNode<UrlSegme
|
|||
return new TreeNode<UrlSegment>(urlSegment, children);
|
||||
|
||||
} else if (isBlank(node) && isStringMap(next)) {
|
||||
let urlSegment = new UrlSegment(segment, next, outlet);
|
||||
let urlSegment = new UrlSegment(segment, _stringify(next), outlet);
|
||||
return _recurse(urlSegment, node, rest.slice(1));
|
||||
|
||||
// different outlet => preserve the subtree
|
||||
|
@ -143,23 +137,40 @@ function _update(node: TreeNode<UrlSegment>, commands: any[]): TreeNode<UrlSegme
|
|||
|
||||
// params command
|
||||
} else if (isStringMap(segment)) {
|
||||
let newSegment = new UrlSegment(node.value.segment, segment, node.value.outlet);
|
||||
let newSegment = new UrlSegment(node.value.segment, _stringify(segment), node.value.outlet);
|
||||
return _recurse(newSegment, node, rest);
|
||||
|
||||
// next one is a params command
|
||||
// next one is a params command && can reuse the node
|
||||
} else if (isStringMap(next) && _compare(segment, _stringify(next), node.value)){
|
||||
return _recurse(node.value, node, rest.slice(1));
|
||||
|
||||
// next one is a params command && cannot reuse the node
|
||||
} else if (isStringMap(next)) {
|
||||
let urlSegment = new UrlSegment(segment, next, outlet);
|
||||
let urlSegment = new UrlSegment(segment, _stringify(next), outlet);
|
||||
return _recurse(urlSegment, node, rest.slice(1));
|
||||
|
||||
// next one is not a params command
|
||||
// next one is not a params command && can reuse the node
|
||||
} else if (_compare(segment, {}, node.value)) {
|
||||
return _recurse(node.value, node, rest);
|
||||
|
||||
// next one is not a params command && cannot reuse the node
|
||||
} else {
|
||||
let urlSegment = new UrlSegment(segment, {}, outlet);
|
||||
return _recurse(urlSegment, node, rest);
|
||||
}
|
||||
}
|
||||
|
||||
function _recurse(urlSegment: UrlSegment, node: TreeNode<UrlSegment>,
|
||||
rest: any[]): TreeNode<UrlSegment> {
|
||||
function _stringify(params: {[key: string]: any}):{[key: string]: string} {
|
||||
let res = {};
|
||||
StringMapWrapper.forEach(params, (v, k) => res[k] = v.toString());
|
||||
return res;
|
||||
}
|
||||
|
||||
function _compare(path: string, params: {[key: string]: any}, segment: UrlSegment): boolean {
|
||||
return path == segment.segment && StringMapWrapper.equals(params, segment.parameters);
|
||||
}
|
||||
|
||||
function _recurse(urlSegment: UrlSegment, node: TreeNode<UrlSegment>, rest: any[]): TreeNode<UrlSegment> {
|
||||
if (rest.length === 0) {
|
||||
return new TreeNode<UrlSegment>(urlSegment, []);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {ComponentFactory, Type} from '@angular/core';
|
||||
import {StringMapWrapper, ListWrapper} from './facade/collection';
|
||||
import {isBlank, isPresent, stringify} from './facade/lang';
|
||||
import {isBlank, isPresent, stringify, NumberWrapper} from './facade/lang';
|
||||
|
||||
export class Tree<T> {
|
||||
/** @internal */
|
||||
|
@ -80,8 +80,8 @@ function _contains<T>(tree: TreeNode<T>, subtree: TreeNode<T>): boolean {
|
|||
}
|
||||
|
||||
function _equalValues(a: any, b: any): boolean {
|
||||
if (a instanceof RouteSegment) return equalSegments(<any>a, <any>b);
|
||||
if (a instanceof UrlSegment) return equalUrlSegments(<any>a, <any>b);
|
||||
// if (a instanceof RouteSegment) return equalSegments(<any>a, <any>b);
|
||||
// if (a instanceof UrlSegment) return equalUrlSegments(<any>a, <any>b);
|
||||
return a === b;
|
||||
}
|
||||
|
||||
|
@ -90,8 +90,8 @@ export class TreeNode<T> {
|
|||
}
|
||||
|
||||
export class UrlSegment {
|
||||
constructor(public segment: any, public parameters: {[key: string]: any}, public outlet: string) {
|
||||
}
|
||||
constructor(public segment: any, public parameters: {[key: string]: string},
|
||||
public outlet: string) {}
|
||||
|
||||
toString(): string {
|
||||
let outletPrefix = isBlank(this.outlet) ? "" : `${this.outlet}:`;
|
||||
|
@ -112,7 +112,7 @@ export class RouteSegment {
|
|||
/** @internal */
|
||||
_componentFactory: ComponentFactory<any>;
|
||||
|
||||
constructor(public urlSegments: UrlSegment[], public parameters: {[key: string]: any},
|
||||
constructor(public urlSegments: UrlSegment[], public parameters: {[key: string]: string},
|
||||
public outlet: string, type: Type, componentFactory: ComponentFactory<any>) {
|
||||
this._type = type;
|
||||
this._componentFactory = componentFactory;
|
||||
|
@ -122,6 +122,10 @@ export class RouteSegment {
|
|||
return isPresent(this.parameters) ? this.parameters[param] : null;
|
||||
}
|
||||
|
||||
getParamAsNumber(param: string): number {
|
||||
return isPresent(this.parameters) ? NumberWrapper.parseFloat(this.parameters[param]) : null;
|
||||
}
|
||||
|
||||
get type(): Type { return this._type; }
|
||||
|
||||
get stringifiedUrlSegments(): string { return this.urlSegments.map(s => s.toString()).join("/"); }
|
||||
|
|
|
@ -69,6 +69,34 @@ export function main() {
|
|||
expect(parser.serialize(t)).toEqual("/a/b;aa=22;bb=33");
|
||||
});
|
||||
|
||||
describe("node reuse", () => {
|
||||
it('should reuse nodes when path is the same', () => {
|
||||
let p = parser.parse("/a/b");
|
||||
let tree = s(p.root);
|
||||
let t = link(tree.root, tree, p, ['/a/c']);
|
||||
|
||||
expect(t.root).toBe(p.root);
|
||||
expect(t.firstChild(t.root)).toBe(p.firstChild(p.root));
|
||||
expect(t.firstChild(t.firstChild(t.root))).not.toBe(p.firstChild(p.firstChild(p.root)));
|
||||
});
|
||||
|
||||
it("should create new node when params are the same", () => {
|
||||
let p = parser.parse("/a;x=1");
|
||||
let tree = s(p.root);
|
||||
let t = link(tree.root, tree, p, ['/a', {'x': 1}]);
|
||||
|
||||
expect(t.firstChild(t.root)).toBe(p.firstChild(p.root));
|
||||
});
|
||||
|
||||
it("should create new node when params are different", () => {
|
||||
let p = parser.parse("/a;x=1");
|
||||
let tree = s(p.root);
|
||||
let t = link(tree.root, tree, p, ['/a', {'x': 2}]);
|
||||
|
||||
expect(t.firstChild(t.root)).not.toBe(p.firstChild(p.root));
|
||||
});
|
||||
});
|
||||
|
||||
describe("relative navigation", () => {
|
||||
it("should work", () => {
|
||||
let p = parser.parse("/a(ap)/c(cp)");
|
||||
|
@ -155,9 +183,9 @@ export function main() {
|
|||
let p = parser.parse("/a(ap)/c(cp)");
|
||||
let c = p.firstChild(p.root);
|
||||
|
||||
let child = new RouteSegment([], {'one': 1}, null, null, null);
|
||||
let child = new RouteSegment([], {'one':'1'}, null, null, null);
|
||||
let root = new TreeNode<RouteSegment>(new RouteSegment([c], {}, null, null, null),
|
||||
[new TreeNode<RouteSegment>(child, [])]);
|
||||
[new TreeNode<RouteSegment>(child, [])]);
|
||||
let tree = new RouteTree(root);
|
||||
|
||||
let t = link(child, tree, p, ["./c2"]);
|
||||
|
|
|
@ -20,7 +20,7 @@ import {DefaultRouterUrlSerializer} from '../src/router_url_serializer';
|
|||
import {DEFAULT_OUTLET_NAME} from '../src/constants';
|
||||
|
||||
export function main() {
|
||||
describe('recognize', () => {
|
||||
ddescribe('recognize', () => {
|
||||
it('should handle position args',
|
||||
inject([AsyncTestCompleter, ComponentResolver], (async, resolver) => {
|
||||
recognize(resolver, ComponentA, tree("b/paramB/c/paramC/d"))
|
||||
|
|
Loading…
Reference in New Issue