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 #4728 Closes #4228 Closes #4170 Closes #4490 Closes #4694 Closes #5200 Closes #5475
120 lines
3.9 KiB
TypeScript
120 lines
3.9 KiB
TypeScript
import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
|
import {BaseException} from 'angular2/src/facade/exceptions';
|
|
import {PromiseWrapper, Promise} from 'angular2/src/facade/promise';
|
|
import {Map} from 'angular2/src/facade/collection';
|
|
|
|
import {RouteHandler} from './route_handler';
|
|
import {Url} from './url_parser';
|
|
import {ComponentInstruction} from './instruction';
|
|
import {PathRecognizer} from './path_recognizer';
|
|
|
|
|
|
export abstract class RouteMatch {}
|
|
|
|
export interface AbstractRecognizer {
|
|
hash: string;
|
|
path: string;
|
|
recognize(beginningSegment: Url): Promise<RouteMatch>;
|
|
generate(params: {[key: string]: any}): ComponentInstruction;
|
|
}
|
|
|
|
|
|
export class PathMatch extends RouteMatch {
|
|
constructor(public instruction: ComponentInstruction, public remaining: Url,
|
|
public remainingAux: Url[]) {
|
|
super();
|
|
}
|
|
}
|
|
|
|
|
|
export class RedirectMatch extends RouteMatch {
|
|
constructor(public redirectTo: any[], public specificity) { super(); }
|
|
}
|
|
|
|
export class RedirectRecognizer implements AbstractRecognizer {
|
|
private _pathRecognizer: PathRecognizer;
|
|
public hash: string;
|
|
|
|
constructor(public path: string, public redirectTo: any[]) {
|
|
this._pathRecognizer = new PathRecognizer(path);
|
|
this.hash = this._pathRecognizer.hash;
|
|
}
|
|
|
|
/**
|
|
* Returns `null` or a `ParsedUrl` representing the new path to match
|
|
*/
|
|
recognize(beginningSegment: Url): Promise<RouteMatch> {
|
|
var match = null;
|
|
if (isPresent(this._pathRecognizer.recognize(beginningSegment))) {
|
|
match = new RedirectMatch(this.redirectTo, this._pathRecognizer.specificity);
|
|
}
|
|
return PromiseWrapper.resolve(match);
|
|
}
|
|
|
|
generate(params: {[key: string]: any}): ComponentInstruction {
|
|
throw new BaseException(`Tried to generate a redirect.`);
|
|
}
|
|
}
|
|
|
|
|
|
// represents something like '/foo/:bar'
|
|
export class RouteRecognizer implements AbstractRecognizer {
|
|
specificity: number;
|
|
terminal: boolean = true;
|
|
hash: string;
|
|
|
|
private _cache: Map<string, ComponentInstruction> = new Map<string, ComponentInstruction>();
|
|
private _pathRecognizer: PathRecognizer;
|
|
|
|
// TODO: cache component instruction instances by params and by ParsedUrl instance
|
|
|
|
constructor(public path: string, public handler: RouteHandler) {
|
|
this._pathRecognizer = new PathRecognizer(path);
|
|
this.specificity = this._pathRecognizer.specificity;
|
|
this.hash = this._pathRecognizer.hash;
|
|
this.terminal = this._pathRecognizer.terminal;
|
|
}
|
|
|
|
recognize(beginningSegment: Url): Promise<RouteMatch> {
|
|
var res = this._pathRecognizer.recognize(beginningSegment);
|
|
if (isBlank(res)) {
|
|
return null;
|
|
}
|
|
|
|
return this.handler.resolveComponentType().then((_) => {
|
|
var componentInstruction =
|
|
this._getInstruction(res['urlPath'], res['urlParams'], res['allParams']);
|
|
return new PathMatch(componentInstruction, res['nextSegment'], res['auxiliary']);
|
|
});
|
|
}
|
|
|
|
generate(params: {[key: string]: any}): ComponentInstruction {
|
|
var generated = this._pathRecognizer.generate(params);
|
|
var urlPath = generated['urlPath'];
|
|
var urlParams = generated['urlParams'];
|
|
return this._getInstruction(urlPath, urlParams, params);
|
|
}
|
|
|
|
generateComponentPathValues(params: {[key: string]: any}): {[key: string]: any} {
|
|
return this._pathRecognizer.generate(params);
|
|
}
|
|
|
|
private _getInstruction(urlPath: string, urlParams: string[],
|
|
params: {[key: string]: any}): ComponentInstruction {
|
|
if (isBlank(this.handler.componentType)) {
|
|
throw new BaseException(`Tried to get instruction before the type was loaded.`);
|
|
}
|
|
|
|
var hashKey = urlPath + '?' + urlParams.join('?');
|
|
if (this._cache.has(hashKey)) {
|
|
return this._cache.get(hashKey);
|
|
}
|
|
var instruction =
|
|
new ComponentInstruction(urlPath, urlParams, this.handler.data, this.handler.componentType,
|
|
this.terminal, this.specificity, params);
|
|
this._cache.set(hashKey, instruction);
|
|
|
|
return instruction;
|
|
}
|
|
}
|