refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5352
This commit is contained in:
@@ -7,12 +7,9 @@ import {
|
||||
isBlank
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
|
||||
|
||||
import {Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {RouteHandler} from './route_handler';
|
||||
import {Url, RootUrl, serializeParams} from './url_parser';
|
||||
import {ComponentInstruction, ComponentInstruction_} from './instruction';
|
||||
|
||||
class TouchMap {
|
||||
map: {[key: string]: string} = {};
|
||||
@@ -33,7 +30,7 @@ class TouchMap {
|
||||
}
|
||||
|
||||
getUnused(): {[key: string]: any} {
|
||||
var unused: {[key: string]: any} = StringMapWrapper.create();
|
||||
var unused: {[key: string]: any} = {};
|
||||
var keys = StringMapWrapper.keys(this.keys);
|
||||
keys.forEach(key => unused[key] = StringMapWrapper.get(this.map, key));
|
||||
return unused;
|
||||
@@ -126,7 +123,6 @@ function parsePathString(route: string): {[key: string]: any} {
|
||||
results.push(new StarSegment(match[1]));
|
||||
} else if (segment == '...') {
|
||||
if (i < limit) {
|
||||
// TODO (matsko): setup a proper error here `
|
||||
throw new BaseException(`Unexpected "..." before the end of the path for "${route}".`);
|
||||
}
|
||||
results.push(new ContinuationSegment());
|
||||
@@ -175,23 +171,17 @@ function assertPath(path: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export class PathMatch {
|
||||
constructor(public instruction: ComponentInstruction, public remaining: Url,
|
||||
public remainingAux: Url[]) {}
|
||||
}
|
||||
|
||||
// represents something like '/foo/:bar'
|
||||
/**
|
||||
* Parses a URL string using a given matcher DSL, and generates URLs from param maps
|
||||
*/
|
||||
export class PathRecognizer {
|
||||
private _segments: Segment[];
|
||||
specificity: number;
|
||||
terminal: boolean = true;
|
||||
hash: string;
|
||||
private _cache: Map<string, ComponentInstruction> = new Map<string, ComponentInstruction>();
|
||||
|
||||
|
||||
// TODO: cache component instruction instances by params and by ParsedUrl instance
|
||||
|
||||
constructor(public path: string, public handler: RouteHandler) {
|
||||
constructor(public path: string) {
|
||||
assertPath(path);
|
||||
var parsed = parsePathString(path);
|
||||
|
||||
@@ -203,8 +193,7 @@ export class PathRecognizer {
|
||||
this.terminal = !(lastSegment instanceof ContinuationSegment);
|
||||
}
|
||||
|
||||
|
||||
recognize(beginningSegment: Url): PathMatch {
|
||||
recognize(beginningSegment: Url): {[key: string]: any} {
|
||||
var nextSegment = beginningSegment;
|
||||
var currentSegment: Url;
|
||||
var positionalParams = {};
|
||||
@@ -247,7 +236,6 @@ export class PathRecognizer {
|
||||
var urlPath = captured.join('/');
|
||||
|
||||
var auxiliary;
|
||||
var instruction: ComponentInstruction;
|
||||
var urlParams;
|
||||
var allParams;
|
||||
if (isPresent(currentSegment)) {
|
||||
@@ -267,12 +255,11 @@ export class PathRecognizer {
|
||||
auxiliary = [];
|
||||
urlParams = [];
|
||||
}
|
||||
instruction = this._getInstruction(urlPath, urlParams, this, allParams);
|
||||
return new PathMatch(instruction, nextSegment, auxiliary);
|
||||
return {urlPath, urlParams, allParams, auxiliary, nextSegment};
|
||||
}
|
||||
|
||||
|
||||
generate(params: {[key: string]: any}): ComponentInstruction {
|
||||
generate(params: {[key: string]: any}): {[key: string]: any} {
|
||||
var paramTokens = new TouchMap(params);
|
||||
|
||||
var path = [];
|
||||
@@ -288,18 +275,6 @@ export class PathRecognizer {
|
||||
var nonPositionalParams = paramTokens.getUnused();
|
||||
var urlParams = serializeParams(nonPositionalParams);
|
||||
|
||||
return this._getInstruction(urlPath, urlParams, this, params);
|
||||
}
|
||||
|
||||
private _getInstruction(urlPath: string, urlParams: string[], _recognizer: PathRecognizer,
|
||||
params: {[key: string]: any}): ComponentInstruction {
|
||||
var hashKey = urlPath + '?' + urlParams.join('?');
|
||||
if (this._cache.has(hashKey)) {
|
||||
return this._cache.get(hashKey);
|
||||
}
|
||||
var instruction = new ComponentInstruction_(urlPath, urlParams, _recognizer, params);
|
||||
this._cache.set(hashKey, instruction);
|
||||
|
||||
return instruction;
|
||||
return {urlPath, urlParams};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user