parent
2335075506
commit
f66ce096d8
@ -34,7 +34,8 @@ import {List} from './src/facade/collection';
|
|||||||
export const routerDirectives: List<any> = CONST_EXPR([RouterOutlet, RouterLink]);
|
export const routerDirectives: List<any> = CONST_EXPR([RouterOutlet, RouterLink]);
|
||||||
|
|
||||||
export var routerInjectables: List<any> = [
|
export var routerInjectables: List<any> = [
|
||||||
RouteRegistry,
|
bind(RouteRegistry)
|
||||||
|
.toFactory((appRoot) => new RouteRegistry(appRoot), [appComponentTypeToken]),
|
||||||
Pipeline,
|
Pipeline,
|
||||||
bind(LocationStrategy).toClass(HTML5LocationStrategy),
|
bind(LocationStrategy).toClass(HTML5LocationStrategy),
|
||||||
Location,
|
Location,
|
||||||
|
@ -239,7 +239,6 @@ export class ListWrapper {
|
|||||||
}
|
}
|
||||||
static toString<T>(l: List<T>): string { return l.toString(); }
|
static toString<T>(l: List<T>): string { return l.toString(); }
|
||||||
static toJSON<T>(l: List<T>): string { return JSON.stringify(l); }
|
static toJSON<T>(l: List<T>): string { return JSON.stringify(l); }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isListLikeIterable(obj): boolean {
|
export function isListLikeIterable(obj): boolean {
|
||||||
|
21
modules/angular2/src/router/async_route_handler.ts
Normal file
21
modules/angular2/src/router/async_route_handler.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import {RouteHandler} from './route_handler';
|
||||||
|
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
|
import {isPresent, Type} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
export class AsyncRouteHandler implements RouteHandler {
|
||||||
|
_resolvedComponent: Promise<any> = null;
|
||||||
|
componentType: Type;
|
||||||
|
|
||||||
|
constructor(private _loader: Function) {}
|
||||||
|
|
||||||
|
resolveComponentType(): Promise<any> {
|
||||||
|
if (isPresent(this._resolvedComponent)) {
|
||||||
|
return this._resolvedComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._resolvedComponent = this._loader().then((componentType) => {
|
||||||
|
this.componentType = componentType;
|
||||||
|
return componentType;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,9 @@ import {
|
|||||||
List,
|
List,
|
||||||
ListWrapper
|
ListWrapper
|
||||||
} from 'angular2/src/facade/collection';
|
} from 'angular2/src/facade/collection';
|
||||||
import {isPresent, normalizeBlank} from 'angular2/src/facade/lang';
|
import {isPresent, isBlank, normalizeBlank} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
import {PathRecognizer} from './path_recognizer';
|
||||||
|
|
||||||
export class RouteParams {
|
export class RouteParams {
|
||||||
constructor(public params: StringMap<string, string>) {}
|
constructor(public params: StringMap<string, string>) {}
|
||||||
@ -14,34 +16,24 @@ export class RouteParams {
|
|||||||
get(param: string): string { return normalizeBlank(StringMapWrapper.get(this.params, param)); }
|
get(param: string): string { return normalizeBlank(StringMapWrapper.get(this.params, param)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An `Instruction` represents the component hierarchy of the application based on a given route
|
* An `Instruction` represents the component hierarchy of the application based on a given route
|
||||||
*/
|
*/
|
||||||
export class Instruction {
|
export class Instruction {
|
||||||
component: any;
|
// "capturedUrl" is the part of the URL captured by this instruction
|
||||||
child: Instruction;
|
// "accumulatedUrl" is the part of the URL captured by this instruction and all children
|
||||||
|
|
||||||
// the part of the URL captured by this instruction
|
|
||||||
capturedUrl: string;
|
|
||||||
|
|
||||||
// the part of the URL captured by this instruction and all children
|
|
||||||
accumulatedUrl: string;
|
accumulatedUrl: string;
|
||||||
|
|
||||||
params: StringMap<string, string>;
|
reuse: boolean = false;
|
||||||
reuse: boolean;
|
|
||||||
specificity: number;
|
specificity: number;
|
||||||
|
|
||||||
constructor({params, component, child, matchedUrl, parentSpecificity}: {
|
private _params: StringMap<string, string>;
|
||||||
params?: StringMap<string, any>,
|
|
||||||
component?: any,
|
constructor(public component: any, public capturedUrl: string,
|
||||||
child?: Instruction,
|
private _recognizer: PathRecognizer, public child: Instruction = null) {
|
||||||
matchedUrl?: string,
|
this.accumulatedUrl = capturedUrl;
|
||||||
parentSpecificity?: number
|
this.specificity = _recognizer.specificity;
|
||||||
} = {}) {
|
|
||||||
this.reuse = false;
|
|
||||||
this.capturedUrl = matchedUrl;
|
|
||||||
this.accumulatedUrl = matchedUrl;
|
|
||||||
this.specificity = parentSpecificity;
|
|
||||||
if (isPresent(child)) {
|
if (isPresent(child)) {
|
||||||
this.child = child;
|
this.child = child;
|
||||||
this.specificity += child.specificity;
|
this.specificity += child.specificity;
|
||||||
@ -49,11 +41,14 @@ export class Instruction {
|
|||||||
if (isPresent(childUrl)) {
|
if (isPresent(childUrl)) {
|
||||||
this.accumulatedUrl += childUrl;
|
this.accumulatedUrl += childUrl;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.child = null;
|
|
||||||
}
|
}
|
||||||
this.component = component;
|
}
|
||||||
this.params = params;
|
|
||||||
|
params(): StringMap<string, string> {
|
||||||
|
if (isBlank(this._params)) {
|
||||||
|
this._params = this._recognizer.parseParams(this.capturedUrl);
|
||||||
|
}
|
||||||
|
return this._params;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasChild(): boolean { return isPresent(this.child); }
|
hasChild(): boolean { return isPresent(this.child); }
|
||||||
@ -73,5 +68,5 @@ export class Instruction {
|
|||||||
|
|
||||||
function shouldReuseComponent(instr1: Instruction, instr2: Instruction): boolean {
|
function shouldReuseComponent(instr1: Instruction, instr2: Instruction): boolean {
|
||||||
return instr1.component == instr2.component &&
|
return instr1.component == instr2.component &&
|
||||||
StringMapWrapper.equals(instr1.params, instr2.params);
|
StringMapWrapper.equals(instr1.params(), instr2.params());
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
BaseException,
|
BaseException,
|
||||||
normalizeBlank
|
normalizeBlank
|
||||||
} from 'angular2/src/facade/lang';
|
} from 'angular2/src/facade/lang';
|
||||||
|
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
import {
|
import {
|
||||||
Map,
|
Map,
|
||||||
MapWrapper,
|
MapWrapper,
|
||||||
@ -19,6 +20,7 @@ import {
|
|||||||
import {IMPLEMENTS} from 'angular2/src/facade/lang';
|
import {IMPLEMENTS} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
import {escapeRegex} from './url';
|
import {escapeRegex} from './url';
|
||||||
|
import {RouteHandler} from './route_handler';
|
||||||
|
|
||||||
// TODO(jeffbcross): implement as interface when ts2dart adds support:
|
// TODO(jeffbcross): implement as interface when ts2dart adds support:
|
||||||
// https://github.com/angular/ts2dart/issues/173
|
// https://github.com/angular/ts2dart/issues/173
|
||||||
@ -27,7 +29,7 @@ export class Segment {
|
|||||||
regex: string;
|
regex: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ContinuationSegment extends Segment {
|
class ContinuationSegment extends Segment {
|
||||||
generate(params): string { return ''; }
|
generate(params): string { return ''; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +54,7 @@ class DynamicSegment {
|
|||||||
generate(params: StringMap<string, string>): string {
|
generate(params: StringMap<string, string>): string {
|
||||||
if (!StringMapWrapper.contains(params, this.name)) {
|
if (!StringMapWrapper.contains(params, this.name)) {
|
||||||
throw new BaseException(
|
throw new BaseException(
|
||||||
`Route generator for '${this.name}' was not included in parameters passed.`)
|
`Route generator for '${this.name}' was not included in parameters passed.`);
|
||||||
}
|
}
|
||||||
return normalizeBlank(StringMapWrapper.get(params, this.name));
|
return normalizeBlank(StringMapWrapper.get(params, this.name));
|
||||||
}
|
}
|
||||||
@ -135,7 +137,7 @@ export class PathRecognizer {
|
|||||||
specificity: number;
|
specificity: number;
|
||||||
terminal: boolean = true;
|
terminal: boolean = true;
|
||||||
|
|
||||||
constructor(public path: string, public handler: any) {
|
constructor(public path: string, public handler: RouteHandler) {
|
||||||
var parsed = parsePathString(path);
|
var parsed = parsePathString(path);
|
||||||
var specificity = parsed['specificity'];
|
var specificity = parsed['specificity'];
|
||||||
var segments = parsed['segments'];
|
var segments = parsed['segments'];
|
||||||
@ -178,7 +180,9 @@ export class PathRecognizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generate(params: StringMap<string, string>): string {
|
generate(params: StringMap<string, string>): string {
|
||||||
return ListWrapper.join(
|
return ListWrapper.join(ListWrapper.map(this.segments, (segment) => segment.generate(params)),
|
||||||
ListWrapper.map(this.segments, (segment) => '/' + segment.generate(params)), '');
|
'/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolveComponentType(): Promise<any> { return this.handler.resolveComponentType(); }
|
||||||
}
|
}
|
||||||
|
7
modules/angular2/src/router/route_handler.ts
Normal file
7
modules/angular2/src/router/route_handler.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
|
import {Type} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
export interface RouteHandler {
|
||||||
|
componentType: Type;
|
||||||
|
resolveComponentType(): Promise<any>;
|
||||||
|
}
|
@ -2,7 +2,10 @@ import {
|
|||||||
RegExp,
|
RegExp,
|
||||||
RegExpWrapper,
|
RegExpWrapper,
|
||||||
StringWrapper,
|
StringWrapper,
|
||||||
|
isBlank,
|
||||||
isPresent,
|
isPresent,
|
||||||
|
isType,
|
||||||
|
isStringMap,
|
||||||
BaseException
|
BaseException
|
||||||
} from 'angular2/src/facade/lang';
|
} from 'angular2/src/facade/lang';
|
||||||
import {
|
import {
|
||||||
@ -14,7 +17,10 @@ import {
|
|||||||
StringMapWrapper
|
StringMapWrapper
|
||||||
} from 'angular2/src/facade/collection';
|
} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {PathRecognizer, ContinuationSegment} from './path_recognizer';
|
import {PathRecognizer} from './path_recognizer';
|
||||||
|
import {RouteHandler} from './route_handler';
|
||||||
|
import {AsyncRouteHandler} from './async_route_handler';
|
||||||
|
import {SyncRouteHandler} from './sync_route_handler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `RouteRecognizer` is responsible for recognizing routes for a single component.
|
* `RouteRecognizer` is responsible for recognizing routes for a single component.
|
||||||
@ -33,7 +39,8 @@ export class RouteRecognizer {
|
|||||||
this.redirects.set(path, target);
|
this.redirects.set(path, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
addConfig(path: string, handler: any, alias: string = null): boolean {
|
addConfig(path: string, handlerObj: any, alias: string = null): boolean {
|
||||||
|
var handler = configObjToHandler(handlerObj['component']);
|
||||||
var recognizer = new PathRecognizer(path, handler);
|
var recognizer = new PathRecognizer(path, handler);
|
||||||
MapWrapper.forEach(this.matchers, (matcher, _) => {
|
MapWrapper.forEach(this.matchers, (matcher, _) => {
|
||||||
if (recognizer.regex.toString() == matcher.regex.toString()) {
|
if (recognizer.regex.toString() == matcher.regex.toString()) {
|
||||||
@ -65,28 +72,21 @@ export class RouteRecognizer {
|
|||||||
if (path == url) {
|
if (path == url) {
|
||||||
url = target;
|
url = target;
|
||||||
}
|
}
|
||||||
} else if (StringWrapper.startsWith(url, path)) {
|
} else if (url.startsWith(path)) {
|
||||||
url = target + StringWrapper.substring(url, path.length);
|
url = target + url.substring(path.length);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
MapWrapper.forEach(this.matchers, (pathRecognizer, regex) => {
|
MapWrapper.forEach(this.matchers, (pathRecognizer, regex) => {
|
||||||
var match;
|
var match;
|
||||||
if (isPresent(match = RegExpWrapper.firstMatch(regex, url))) {
|
if (isPresent(match = RegExpWrapper.firstMatch(regex, url))) {
|
||||||
// TODO(btford): determine a good generic way to deal with terminal matches
|
|
||||||
var matchedUrl = '/';
|
var matchedUrl = '/';
|
||||||
var unmatchedUrl = '';
|
var unmatchedUrl = '';
|
||||||
if (url != '/') {
|
if (url != '/') {
|
||||||
matchedUrl = match[0];
|
matchedUrl = match[0];
|
||||||
unmatchedUrl = StringWrapper.substring(url, match[0].length);
|
unmatchedUrl = url.substring(match[0].length);
|
||||||
}
|
}
|
||||||
solutions.push(new RouteMatch({
|
solutions.push(new RouteMatch(pathRecognizer, matchedUrl, unmatchedUrl));
|
||||||
specificity: pathRecognizer.specificity,
|
|
||||||
handler: pathRecognizer.handler,
|
|
||||||
params: pathRecognizer.parseParams(url),
|
|
||||||
matchedUrl: matchedUrl,
|
|
||||||
unmatchedUrl: unmatchedUrl
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -95,30 +95,39 @@ export class RouteRecognizer {
|
|||||||
|
|
||||||
hasRoute(name: string): boolean { return this.names.has(name); }
|
hasRoute(name: string): boolean { return this.names.has(name); }
|
||||||
|
|
||||||
generate(name: string, params: any): string {
|
generate(name: string, params: any): StringMap<string, any> {
|
||||||
var pathRecognizer = this.names.get(name);
|
var pathRecognizer: PathRecognizer = this.names.get(name);
|
||||||
return isPresent(pathRecognizer) ? pathRecognizer.generate(params) : null;
|
if (isBlank(pathRecognizer)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var url = pathRecognizer.generate(params);
|
||||||
|
return {url, 'nextComponent': pathRecognizer.handler.componentType};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RouteMatch {
|
export class RouteMatch {
|
||||||
specificity: number;
|
constructor(public recognizer: PathRecognizer, public matchedUrl: string,
|
||||||
handler: StringMap<string, any>;
|
public unmatchedUrl: string) {}
|
||||||
params: StringMap<string, string>;
|
|
||||||
matchedUrl: string;
|
|
||||||
unmatchedUrl: string;
|
|
||||||
|
|
||||||
constructor({specificity, handler, params, matchedUrl, unmatchedUrl}: {
|
params(): StringMap<string, string> { return this.recognizer.parseParams(this.matchedUrl); }
|
||||||
specificity?: number,
|
}
|
||||||
handler?: StringMap<string, any>,
|
|
||||||
params?: StringMap<string, string>,
|
function configObjToHandler(config: any): RouteHandler {
|
||||||
matchedUrl?: string,
|
if (isType(config)) {
|
||||||
unmatchedUrl?: string
|
return new SyncRouteHandler(config);
|
||||||
} = {}) {
|
} else if (isStringMap(config)) {
|
||||||
this.specificity = specificity;
|
if (isBlank(config['type'])) {
|
||||||
this.handler = handler;
|
throw new BaseException(
|
||||||
this.params = params;
|
`Component declaration when provided as a map should include a 'type' property`);
|
||||||
this.matchedUrl = matchedUrl;
|
}
|
||||||
this.unmatchedUrl = unmatchedUrl;
|
var componentType = config['type'];
|
||||||
}
|
if (componentType == 'constructor') {
|
||||||
|
return new SyncRouteHandler(config['constructor']);
|
||||||
|
} else if (componentType == 'loader') {
|
||||||
|
return new AsyncRouteHandler(config['loader']);
|
||||||
|
} else {
|
||||||
|
throw new BaseException(`oops`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new BaseException(`Unexpected component "${config}".`);
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
isPresent,
|
isPresent,
|
||||||
isBlank,
|
isBlank,
|
||||||
isType,
|
isType,
|
||||||
|
isString,
|
||||||
isStringMap,
|
isStringMap,
|
||||||
isFunction,
|
isFunction,
|
||||||
StringWrapper,
|
StringWrapper,
|
||||||
@ -29,7 +30,9 @@ import {Injectable} from 'angular2/di';
|
|||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RouteRegistry {
|
export class RouteRegistry {
|
||||||
_rules: Map<any, RouteRecognizer> = new Map();
|
private _rules: Map<any, RouteRecognizer> = new Map();
|
||||||
|
|
||||||
|
constructor(private _rootHostComponent: any) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a component and a configuration object, add the route to this registry
|
* Given a component and a configuration object, add the route to this registry
|
||||||
@ -118,40 +121,80 @@ export class RouteRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_completeRouteMatch(candidate: RouteMatch): Promise<Instruction> {
|
_completeRouteMatch(partialMatch: RouteMatch): Promise<Instruction> {
|
||||||
return componentHandlerToComponentType(candidate.handler)
|
var recognizer = partialMatch.recognizer;
|
||||||
.then((componentType) => {
|
var handler = recognizer.handler;
|
||||||
this.configFromComponent(componentType);
|
return handler.resolveComponentType().then((componentType) => {
|
||||||
|
this.configFromComponent(componentType);
|
||||||
|
|
||||||
if (candidate.unmatchedUrl.length == 0) {
|
if (partialMatch.unmatchedUrl.length == 0) {
|
||||||
return new Instruction({
|
return new Instruction(componentType, partialMatch.matchedUrl, recognizer);
|
||||||
component: componentType,
|
}
|
||||||
params: candidate.params,
|
|
||||||
matchedUrl: candidate.matchedUrl,
|
|
||||||
parentSpecificity: candidate.specificity
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.recognize(candidate.unmatchedUrl, componentType)
|
return this.recognize(partialMatch.unmatchedUrl, componentType)
|
||||||
.then(childInstruction => {
|
.then(childInstruction => {
|
||||||
if (isBlank(childInstruction)) {
|
if (isBlank(childInstruction)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
} else {
|
||||||
return new Instruction({
|
return new Instruction(componentType, partialMatch.matchedUrl, recognizer,
|
||||||
component: componentType,
|
childInstruction);
|
||||||
child: childInstruction,
|
}
|
||||||
params: candidate.params,
|
});
|
||||||
matchedUrl: candidate.matchedUrl,
|
});
|
||||||
parentSpecificity: candidate.specificity
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
generate(name: string, params: StringMap<string, string>, hostComponent): string {
|
/**
|
||||||
// TODO: implement for hierarchical routes
|
* Given a list with component names and params like: `['./user', {id: 3 }]`
|
||||||
var componentRecognizer = this._rules.get(hostComponent);
|
* generates a url with a leading slash relative to the provided `parentComponent`.
|
||||||
return isPresent(componentRecognizer) ? componentRecognizer.generate(name, params) : null;
|
*/
|
||||||
|
generate(linkParams: List<any>, parentComponent): string {
|
||||||
|
let normalizedLinkParams = splitAndFlattenLinkParams(linkParams);
|
||||||
|
let url = '/';
|
||||||
|
|
||||||
|
let componentCursor = parentComponent;
|
||||||
|
|
||||||
|
// The first segment should be either '.' (generate from parent) or '' (generate from root).
|
||||||
|
// When we normalize above, we strip all the slashes, './' becomes '.' and '/' becomes ''.
|
||||||
|
if (normalizedLinkParams[0] == '') {
|
||||||
|
componentCursor = this._rootHostComponent;
|
||||||
|
} else if (normalizedLinkParams[0] != '.') {
|
||||||
|
throw new BaseException(
|
||||||
|
`Link "${ListWrapper.toJSON(linkParams)}" must start with "/" or "./"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedLinkParams[normalizedLinkParams.length - 1] == '') {
|
||||||
|
ListWrapper.removeLast(normalizedLinkParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedLinkParams.length < 2) {
|
||||||
|
throw new BaseException(
|
||||||
|
`Link "${ListWrapper.toJSON(linkParams)}" must include a route name.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < normalizedLinkParams.length; i += 1) {
|
||||||
|
let segment = normalizedLinkParams[i];
|
||||||
|
if (!isString(segment)) {
|
||||||
|
throw new BaseException(`Unexpected segment "${segment}" in link DSL. Expected a string.`);
|
||||||
|
}
|
||||||
|
let params = null;
|
||||||
|
if (i + 1 < normalizedLinkParams.length) {
|
||||||
|
let nextSegment = normalizedLinkParams[i + 1];
|
||||||
|
if (isStringMap(nextSegment)) {
|
||||||
|
params = nextSegment;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var componentRecognizer = this._rules.get(componentCursor);
|
||||||
|
if (isBlank(componentRecognizer)) {
|
||||||
|
throw new BaseException(`Could not find route config for "${segment}".`);
|
||||||
|
}
|
||||||
|
var response = componentRecognizer.generate(segment, params);
|
||||||
|
url += response['url'];
|
||||||
|
componentCursor = response['nextComponent'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,19 +243,6 @@ function normalizeComponentDeclaration(config: any): StringMap<string, any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function componentHandlerToComponentType(handler): Promise<any> {
|
|
||||||
var componentDeclaration = handler['component'], type = componentDeclaration['type'];
|
|
||||||
|
|
||||||
if (type == 'constructor') {
|
|
||||||
return PromiseWrapper.resolve(componentDeclaration['constructor']);
|
|
||||||
} else if (type == 'loader') {
|
|
||||||
var resolverFunction = componentDeclaration['loader'];
|
|
||||||
return resolverFunction();
|
|
||||||
} else {
|
|
||||||
throw new BaseException(`Cannot extract the component type from a '${type}' component`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Given a list of instructions, returns the most specific instruction
|
* Given a list of instructions, returns the most specific instruction
|
||||||
*/
|
*/
|
||||||
@ -244,3 +274,18 @@ function assertTerminalComponent(component, path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given: ['/a/b', {c: 2}]
|
||||||
|
* Returns: ['', 'a', 'b', {c: 2}]
|
||||||
|
*/
|
||||||
|
var SLASH = new RegExp('/');
|
||||||
|
function splitAndFlattenLinkParams(linkParams: List<any>): List<any> {
|
||||||
|
return ListWrapper.reduce(linkParams, (accumulation, item) => {
|
||||||
|
if (isString(item)) {
|
||||||
|
return ListWrapper.concat(accumulation, StringWrapper.split(item, SLASH));
|
||||||
|
}
|
||||||
|
accumulation.push(item);
|
||||||
|
return accumulation;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
@ -191,8 +191,8 @@ export class Router {
|
|||||||
* Generate a URL from a component name and optional map of parameters. The URL is relative to the
|
* Generate a URL from a component name and optional map of parameters. The URL is relative to the
|
||||||
* app's base href.
|
* app's base href.
|
||||||
*/
|
*/
|
||||||
generate(name: string, params: StringMap<string, string>): string {
|
generate(linkParams: List<any>): string {
|
||||||
return this._registry.generate(name, params, this.hostComponent);
|
return this._registry.generate(linkParams, this.hostComponent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,12 @@
|
|||||||
import {onAllChangesDone} from 'angular2/src/core/annotations/annotations';
|
|
||||||
import {Directive} from 'angular2/src/core/annotations/decorators';
|
import {Directive} from 'angular2/src/core/annotations/decorators';
|
||||||
import {ElementRef} from 'angular2/core';
|
import {List, StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
import {StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
|
|
||||||
|
|
||||||
import {isPresent} from 'angular2/src/facade/lang';
|
|
||||||
|
|
||||||
import {Router} from './router';
|
import {Router} from './router';
|
||||||
import {Location} from './location';
|
import {Location} from './location';
|
||||||
import {Renderer} from 'angular2/src/render/api';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The RouterLink directive lets you link to specific parts of your app.
|
* The RouterLink directive lets you link to specific parts of your app.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* Consider the following route configuration:
|
* Consider the following route configuration:
|
||||||
|
|
||||||
* ```
|
* ```
|
||||||
@ -22,48 +16,47 @@ import {Renderer} from 'angular2/src/render/api';
|
|||||||
* class MyComp {}
|
* class MyComp {}
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* When linking to a route, you can write:
|
* When linking to this `user` route, you can write:
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* <a router-link="user">link to user component</a>
|
* <a [router-link]="['./user']">link to user component</a>
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
|
* RouterLink expects the value to be an array of route names, followed by the params
|
||||||
|
* for that level of routing. For instance `['/team', {teamId: 1}, 'user', {userId: 2}]`
|
||||||
|
* means that we want to generate a link for the `team` route with params `{teamId: 1}`,
|
||||||
|
* and with a child route `user` with params `{userId: 2}`.
|
||||||
|
*
|
||||||
|
* The first route name should be prepended with either `./` or `/`.
|
||||||
|
* If the route begins with `/`, the router will look up the route from the root of the app.
|
||||||
|
* If the route begins with `./`, the router will instead look in the current component's
|
||||||
|
* children for the route.
|
||||||
|
*
|
||||||
* @exportedAs angular2/router
|
* @exportedAs angular2/router
|
||||||
*/
|
*/
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[router-link]',
|
selector: '[router-link]',
|
||||||
properties: ['route: routerLink', 'params: routerParams'],
|
properties: ['routeParams: routerLink'],
|
||||||
lifecycle: [onAllChangesDone],
|
host: {'(^click)': 'onClick()', '[attr.href]': 'visibleHref'}
|
||||||
host: {'(^click)': 'onClick()'}
|
|
||||||
})
|
})
|
||||||
export class RouterLink {
|
export class RouterLink {
|
||||||
private _route: string;
|
private _routeParams: List<any>;
|
||||||
private _params: StringMap<string, string> = StringMapWrapper.create();
|
|
||||||
|
|
||||||
// the url displayed on the anchor element.
|
// the url displayed on the anchor element.
|
||||||
_visibleHref: string;
|
visibleHref: string;
|
||||||
// the url passed to the router navigation.
|
// the url passed to the router navigation.
|
||||||
_navigationHref: string;
|
_navigationHref: string;
|
||||||
|
|
||||||
constructor(private _elementRef: ElementRef, private _router: Router, private _location: Location,
|
constructor(private _router: Router, private _location: Location) {}
|
||||||
private _renderer: Renderer) {}
|
|
||||||
|
|
||||||
set route(changes: string) { this._route = changes; }
|
set routeParams(changes: List<any>) {
|
||||||
|
this._routeParams = changes;
|
||||||
set params(changes: StringMap<string, string>) { this._params = changes; }
|
this._navigationHref = this._router.generate(this._routeParams);
|
||||||
|
this.visibleHref = this._location.normalizeAbsolutely(this._navigationHref);
|
||||||
|
}
|
||||||
|
|
||||||
onClick(): boolean {
|
onClick(): boolean {
|
||||||
this._router.navigate(this._navigationHref);
|
this._router.navigate(this._navigationHref);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onAllChangesDone(): void {
|
|
||||||
if (isPresent(this._route) && isPresent(this._params)) {
|
|
||||||
this._navigationHref = this._router.generate(this._route, this._params);
|
|
||||||
this._visibleHref = this._location.normalizeAbsolutely(this._navigationHref);
|
|
||||||
// Keeping the link on the element to support contextual menu `copy link`
|
|
||||||
// and other in-browser affordances.
|
|
||||||
this._renderer.setElementAttribute(this._elementRef, 'href', this._visibleHref);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ export class RouterOutlet {
|
|||||||
this._childRouter = this._parentRouter.childRouter(instruction.component);
|
this._childRouter = this._parentRouter.childRouter(instruction.component);
|
||||||
var outletInjector = this._injector.resolveAndCreateChild([
|
var outletInjector = this._injector.resolveAndCreateChild([
|
||||||
bind(RouteParams)
|
bind(RouteParams)
|
||||||
.toValue(new RouteParams(instruction.params)),
|
.toValue(new RouteParams(instruction.params())),
|
||||||
bind(routerMod.Router).toValue(this._childRouter)
|
bind(routerMod.Router).toValue(this._childRouter)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
13
modules/angular2/src/router/sync_route_handler.ts
Normal file
13
modules/angular2/src/router/sync_route_handler.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import {RouteHandler} from './route_handler';
|
||||||
|
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
|
import {Type} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
export class SyncRouteHandler implements RouteHandler {
|
||||||
|
_resolvedComponent: Promise<any> = null;
|
||||||
|
|
||||||
|
constructor(public componentType: Type) {
|
||||||
|
this._resolvedComponent = PromiseWrapper.resolve(componentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveComponentType(): Promise<any> { return this._resolvedComponent; }
|
||||||
|
}
|
@ -42,7 +42,7 @@ export function main() {
|
|||||||
|
|
||||||
beforeEachBindings(() => [
|
beforeEachBindings(() => [
|
||||||
Pipeline,
|
Pipeline,
|
||||||
RouteRegistry,
|
bind(RouteRegistry).toFactory(() => new RouteRegistry(MyComp)),
|
||||||
DirectiveResolver,
|
DirectiveResolver,
|
||||||
bind(Location).toClass(SpyLocation),
|
bind(Location).toClass(SpyLocation),
|
||||||
bind(Router)
|
bind(Router)
|
||||||
@ -129,7 +129,7 @@ export function main() {
|
|||||||
it('should generate absolute hrefs that include the base href',
|
it('should generate absolute hrefs that include the base href',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
location.setBaseHref('/my/base');
|
location.setBaseHref('/my/base');
|
||||||
compile('<a href="hello" router-link="user"></a>')
|
compile('<a href="hello" [router-link]="[\'./user\']"></a>')
|
||||||
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
|
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
|
||||||
.then((_) => rtr.navigate('/a/b'))
|
.then((_) => rtr.navigate('/a/b'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -141,7 +141,7 @@ export function main() {
|
|||||||
|
|
||||||
|
|
||||||
it('should generate link hrefs without params', inject([AsyncTestCompleter], (async) => {
|
it('should generate link hrefs without params', inject([AsyncTestCompleter], (async) => {
|
||||||
compile('<a href="hello" router-link="user"></a>')
|
compile('<a href="hello" [router-link]="[\'./user\']"></a>')
|
||||||
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
|
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
|
||||||
.then((_) => rtr.navigate('/a/b'))
|
.then((_) => rtr.navigate('/a/b'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -172,7 +172,7 @@ export function main() {
|
|||||||
|
|
||||||
|
|
||||||
it('should generate link hrefs with params', inject([AsyncTestCompleter], (async) => {
|
it('should generate link hrefs with params', inject([AsyncTestCompleter], (async) => {
|
||||||
compile('<a href="hello" router-link="user" [router-params]="{name: name}">{{name}}</a>')
|
compile('<a href="hello" [router-link]="[\'./user\', {name: name}]">{{name}}</a>')
|
||||||
.then((_) => rtr.config({'path': '/user/:name', 'component': UserCmp, 'as': 'user'}))
|
.then((_) => rtr.config({'path': '/user/:name', 'component': UserCmp, 'as': 'user'}))
|
||||||
.then((_) => rtr.navigate('/a/b'))
|
.then((_) => rtr.navigate('/a/b'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -194,10 +194,8 @@ export function main() {
|
|||||||
return dispatchedEvent;
|
return dispatchedEvent;
|
||||||
};
|
};
|
||||||
|
|
||||||
it('test', inject([AsyncTestCompleter], (async) => { async.done(); }));
|
|
||||||
|
|
||||||
it('should navigate to link hrefs without params', inject([AsyncTestCompleter], (async) => {
|
it('should navigate to link hrefs without params', inject([AsyncTestCompleter], (async) => {
|
||||||
compile('<a href="hello" router-link="user"></a>')
|
compile('<a href="hello" [router-link]="[\'./user\']"></a>')
|
||||||
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
|
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
|
||||||
.then((_) => rtr.navigate('/a/b'))
|
.then((_) => rtr.navigate('/a/b'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -218,7 +216,7 @@ export function main() {
|
|||||||
it('should navigate to link hrefs in presence of base href',
|
it('should navigate to link hrefs in presence of base href',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
location.setBaseHref('/base');
|
location.setBaseHref('/base');
|
||||||
compile('<a href="hello" router-link="user"></a>')
|
compile('<a href="hello" [router-link]="[\'./user\']"></a>')
|
||||||
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
|
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
|
||||||
.then((_) => rtr.navigate('/a/b'))
|
.then((_) => rtr.navigate('/a/b'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
|
@ -10,43 +10,44 @@ import {
|
|||||||
SpyObject
|
SpyObject
|
||||||
} from 'angular2/test_lib';
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
import {RouteRecognizer} from 'angular2/src/router/route_recognizer';
|
import {RouteRecognizer, RouteMatch} from 'angular2/src/router/route_recognizer';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('RouteRecognizer', () => {
|
describe('RouteRecognizer', () => {
|
||||||
var recognizer;
|
var recognizer;
|
||||||
var handler = {'components': {'a': 'b'}};
|
var handler = {'component': DummyCmpA};
|
||||||
var handler2 = {'components': {'b': 'c'}};
|
var handler2 = {'component': DummyCmpB};
|
||||||
|
|
||||||
beforeEach(() => { recognizer = new RouteRecognizer(); });
|
beforeEach(() => { recognizer = new RouteRecognizer(); });
|
||||||
|
|
||||||
|
|
||||||
it('should recognize a static segment', () => {
|
it('should recognize a static segment', () => {
|
||||||
recognizer.addConfig('/test', handler);
|
recognizer.addConfig('/test', handler);
|
||||||
expect(recognizer.recognize('/test')[0].handler).toEqual(handler);
|
var solution = recognizer.recognize('/test')[0];
|
||||||
|
expect(getComponentType(solution)).toEqual(handler['component']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should recognize a single slash', () => {
|
it('should recognize a single slash', () => {
|
||||||
recognizer.addConfig('/', handler);
|
recognizer.addConfig('/', handler);
|
||||||
var solution = recognizer.recognize('/')[0];
|
var solution = recognizer.recognize('/')[0];
|
||||||
expect(solution.handler).toEqual(handler);
|
expect(getComponentType(solution)).toEqual(handler['component']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should recognize a dynamic segment', () => {
|
it('should recognize a dynamic segment', () => {
|
||||||
recognizer.addConfig('/user/:name', handler);
|
recognizer.addConfig('/user/:name', handler);
|
||||||
var solution = recognizer.recognize('/user/brian')[0];
|
var solution = recognizer.recognize('/user/brian')[0];
|
||||||
expect(solution.handler).toEqual(handler);
|
expect(getComponentType(solution)).toEqual(handler['component']);
|
||||||
expect(solution.params).toEqual({'name': 'brian'});
|
expect(solution.params()).toEqual({'name': 'brian'});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should recognize a star segment', () => {
|
it('should recognize a star segment', () => {
|
||||||
recognizer.addConfig('/first/*rest', handler);
|
recognizer.addConfig('/first/*rest', handler);
|
||||||
var solution = recognizer.recognize('/first/second/third')[0];
|
var solution = recognizer.recognize('/first/second/third')[0];
|
||||||
expect(solution.handler).toEqual(handler);
|
expect(getComponentType(solution)).toEqual(handler['component']);
|
||||||
expect(solution.params).toEqual({'rest': 'second/third'});
|
expect(solution.params()).toEqual({'rest': 'second/third'});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -72,7 +73,7 @@ export function main() {
|
|||||||
expect(solutions.length).toBe(1);
|
expect(solutions.length).toBe(1);
|
||||||
|
|
||||||
var solution = solutions[0];
|
var solution = solutions[0];
|
||||||
expect(solution.handler).toEqual(handler);
|
expect(getComponentType(solution)).toEqual(handler['component']);
|
||||||
expect(solution.matchedUrl).toEqual('/b');
|
expect(solution.matchedUrl).toEqual('/b');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -83,7 +84,7 @@ export function main() {
|
|||||||
expect(solutions.length).toBe(1);
|
expect(solutions.length).toBe(1);
|
||||||
|
|
||||||
var solution = solutions[0];
|
var solution = solutions[0];
|
||||||
expect(solution.handler).toEqual(handler);
|
expect(getComponentType(solution)).toEqual(handler['component']);
|
||||||
expect(solution.matchedUrl).toEqual('/bar');
|
expect(solution.matchedUrl).toEqual('/bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -106,16 +107,23 @@ export function main() {
|
|||||||
expect(solutions[0].matchedUrl).toBe('/matias');
|
expect(solutions[0].matchedUrl).toBe('/matias');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate URLs', () => {
|
it('should generate URLs with params', () => {
|
||||||
recognizer.addConfig('/app/user/:name', handler, 'user');
|
recognizer.addConfig('/app/user/:name', handler, 'user');
|
||||||
expect(recognizer.generate('user', {'name': 'misko'})).toEqual('/app/user/misko');
|
expect(recognizer.generate('user', {'name': 'misko'})['url']).toEqual('app/user/misko');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should throw in the absence of required params URLs', () => {
|
it('should throw in the absence of required params URLs', () => {
|
||||||
recognizer.addConfig('/app/user/:name', handler, 'user');
|
recognizer.addConfig('app/user/:name', handler, 'user');
|
||||||
expect(() => recognizer.generate('user', {}))
|
expect(() => recognizer.generate('user', {})['url'])
|
||||||
.toThrowError('Route generator for \'name\' was not included in parameters passed.');
|
.toThrowError('Route generator for \'name\' was not included in parameters passed.');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getComponentType(routeMatch: RouteMatch): any {
|
||||||
|
return routeMatch.recognizer.handler.componentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DummyCmpA {}
|
||||||
|
class DummyCmpB {}
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
} from 'angular2/test_lib';
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
|
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
||||||
import {RouteConfig} from 'angular2/src/router/route_config_decorator';
|
import {RouteConfig} from 'angular2/src/router/route_config_decorator';
|
||||||
@ -19,7 +20,7 @@ export function main() {
|
|||||||
describe('RouteRegistry', () => {
|
describe('RouteRegistry', () => {
|
||||||
var registry, rootHostComponent = new Object();
|
var registry, rootHostComponent = new Object();
|
||||||
|
|
||||||
beforeEach(() => { registry = new RouteRegistry(); });
|
beforeEach(() => { registry = new RouteRegistry(rootHostComponent); });
|
||||||
|
|
||||||
it('should match the full URL', inject([AsyncTestCompleter], (async) => {
|
it('should match the full URL', inject([AsyncTestCompleter], (async) => {
|
||||||
registry.config(rootHostComponent, {'path': '/', 'component': DummyCompA});
|
registry.config(rootHostComponent, {'path': '/', 'component': DummyCompA});
|
||||||
@ -32,6 +33,68 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should generate URLs starting at the given component', () => {
|
||||||
|
registry.config(rootHostComponent,
|
||||||
|
{'path': '/first/...', 'component': DummyParentComp, 'as': 'firstCmp'});
|
||||||
|
|
||||||
|
expect(registry.generate(['./firstCmp/secondCmp'], rootHostComponent))
|
||||||
|
.toEqual('/first/second');
|
||||||
|
expect(registry.generate(['./secondCmp'], DummyParentComp)).toEqual('/second');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate URLs with params', () => {
|
||||||
|
registry.config(
|
||||||
|
rootHostComponent,
|
||||||
|
{'path': '/first/:param/...', 'component': DummyParentParamComp, 'as': 'firstCmp'});
|
||||||
|
|
||||||
|
var url = registry.generate(['./firstCmp', {param: 'one'}, 'secondCmp', {param: 'two'}],
|
||||||
|
rootHostComponent);
|
||||||
|
expect(url).toEqual('/first/one/second/two');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate URLs from the root component when the path starts with /', () => {
|
||||||
|
registry.config(rootHostComponent,
|
||||||
|
{'path': '/first/...', 'component': DummyParentComp, 'as': 'firstCmp'});
|
||||||
|
|
||||||
|
expect(registry.generate(['/firstCmp', 'secondCmp'], rootHostComponent))
|
||||||
|
.toEqual('/first/second');
|
||||||
|
expect(registry.generate(['/firstCmp', 'secondCmp'], DummyParentComp))
|
||||||
|
.toEqual('/first/second');
|
||||||
|
expect(registry.generate(['/firstCmp/secondCmp'], DummyParentComp)).toEqual('/first/second');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate URLs of loaded components after they are loaded',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
registry.config(rootHostComponent, {
|
||||||
|
'path': '/first/...',
|
||||||
|
'component': {'type': 'loader', 'loader': AsyncParentLoader},
|
||||||
|
'as': 'firstCmp'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => registry.generate(['/firstCmp/secondCmp'], rootHostComponent))
|
||||||
|
.toThrowError('Could not find route config for "secondCmp".');
|
||||||
|
|
||||||
|
registry.recognize('/first/second', rootHostComponent)
|
||||||
|
.then((_) => {
|
||||||
|
expect(registry.generate(['/firstCmp/secondCmp'], rootHostComponent))
|
||||||
|
.toEqual('/first/second');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should throw when linkParams does not start with a "/" or "./"', () => {
|
||||||
|
expect(() => registry.generate(['firstCmp', 'secondCmp'], rootHostComponent))
|
||||||
|
.toThrowError(
|
||||||
|
`Link "${ListWrapper.toJSON(['firstCmp', 'secondCmp'])}" must start with "/" or "./"`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when linkParams does not include a route name', () => {
|
||||||
|
expect(() => registry.generate(['./'], rootHostComponent))
|
||||||
|
.toThrowError(`Link "${ListWrapper.toJSON(['./'])}" must include a route name.`);
|
||||||
|
expect(() => registry.generate(['/'], rootHostComponent))
|
||||||
|
.toThrowError(`Link "${ListWrapper.toJSON(['/'])}" must include a route name.`);
|
||||||
|
});
|
||||||
|
|
||||||
it('should prefer static segments to dynamic', inject([AsyncTestCompleter], (async) => {
|
it('should prefer static segments to dynamic', inject([AsyncTestCompleter], (async) => {
|
||||||
registry.config(rootHostComponent, {'path': '/:site', 'component': DummyCompB});
|
registry.config(rootHostComponent, {'path': '/:site', 'component': DummyCompB});
|
||||||
registry.config(rootHostComponent, {'path': '/home', 'component': DummyCompA});
|
registry.config(rootHostComponent, {'path': '/home', 'component': DummyCompA});
|
||||||
@ -172,6 +235,11 @@ class DummyAsyncComp {
|
|||||||
class DummyCompA {}
|
class DummyCompA {}
|
||||||
class DummyCompB {}
|
class DummyCompB {}
|
||||||
|
|
||||||
@RouteConfig([{'path': '/second', 'component': DummyCompB}])
|
@RouteConfig([{'path': '/second', 'component': DummyCompB, 'as': 'secondCmp'}])
|
||||||
class DummyParentComp {
|
class DummyParentComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@RouteConfig([{'path': '/second/:param', 'component': DummyCompB, 'as': 'secondCmp'}])
|
||||||
|
class DummyParentParamComp {
|
||||||
|
}
|
||||||
|
@ -31,7 +31,7 @@ export function main() {
|
|||||||
|
|
||||||
beforeEachBindings(() => [
|
beforeEachBindings(() => [
|
||||||
Pipeline,
|
Pipeline,
|
||||||
RouteRegistry,
|
bind(RouteRegistry).toFactory(() => new RouteRegistry(AppCmp)),
|
||||||
DirectiveResolver,
|
DirectiveResolver,
|
||||||
bind(Location).toClass(SpyLocation),
|
bind(Location).toClass(SpyLocation),
|
||||||
bind(Router)
|
bind(Router)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user