feat(router): add interfaces for route definitions in RouteConfig
Closes #2261
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
import {RouteConfig as RouteConfigAnnotation} from './route_config_impl';
|
||||
import {makeDecorator} from 'angular2/src/util/decorators';
|
||||
|
||||
export {Route, Redirect, AsyncRoute, RouteDefinition} from './route_config_impl';
|
||||
export var RouteConfig = makeDecorator(RouteConfigAnnotation);
|
||||
|
@ -1,5 +1,7 @@
|
||||
import {CONST} from 'angular2/src/facade/lang';
|
||||
import {List, Map} from 'angular2/src/facade/collection';
|
||||
import {CONST, Type} from 'angular2/src/facade/lang';
|
||||
import {List} from 'angular2/src/facade/collection';
|
||||
import {RouteDefinition} from './route_definition';
|
||||
export {RouteDefinition} from './route_definition';
|
||||
|
||||
/**
|
||||
* You use the RouteConfig annotation to add routes to a component.
|
||||
@ -11,5 +13,41 @@ import {List, Map} from 'angular2/src/facade/collection';
|
||||
*/
|
||||
@CONST()
|
||||
export class RouteConfig {
|
||||
constructor(public configs: List<Map<any, any>>) {}
|
||||
constructor(public configs: List<RouteDefinition>) {}
|
||||
}
|
||||
|
||||
|
||||
@CONST()
|
||||
export class Route implements RouteDefinition {
|
||||
path: string;
|
||||
component: Type;
|
||||
as: string;
|
||||
constructor({path, component, as}: {path: string, component: Type, as?: string}) {
|
||||
this.path = path;
|
||||
this.component = component;
|
||||
this.as = as;
|
||||
}
|
||||
}
|
||||
|
||||
@CONST()
|
||||
export class AsyncRoute implements RouteDefinition {
|
||||
path: string;
|
||||
loader: Function;
|
||||
as: string;
|
||||
constructor({path, loader, as}: {path: string, loader: Function, as?: string}) {
|
||||
this.path = path;
|
||||
this.loader = loader;
|
||||
this.as = as;
|
||||
}
|
||||
}
|
||||
|
||||
@CONST()
|
||||
export class Redirect implements RouteDefinition {
|
||||
path: string;
|
||||
redirectTo: string;
|
||||
as: string = null;
|
||||
constructor({path, redirectTo}: {path: string, redirectTo: string}) {
|
||||
this.path = path;
|
||||
this.redirectTo = redirectTo;
|
||||
}
|
||||
}
|
||||
|
7
modules/angular2/src/router/route_config_nomalizer.dart
Normal file
7
modules/angular2/src/router/route_config_nomalizer.dart
Normal file
@ -0,0 +1,7 @@
|
||||
library angular2.src.router.route_config_normalizer;
|
||||
|
||||
import "route_config_decorator.dart";
|
||||
|
||||
RouteDefinition normalizeRouteConfig(RouteDefinition config) {
|
||||
return config;
|
||||
}
|
46
modules/angular2/src/router/route_config_nomalizer.ts
Normal file
46
modules/angular2/src/router/route_config_nomalizer.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import {AsyncRoute, Route, Redirect, RouteDefinition} from './route_config_decorator';
|
||||
import {ComponentDefinition} from './route_definition';
|
||||
import {Type, BaseException} from 'angular2/src/facade/lang';
|
||||
|
||||
/**
|
||||
* Given a JS Object that represents... returns a corresponding Route, AsyncRoute, or Redirect
|
||||
*/
|
||||
export function normalizeRouteConfig(config: RouteDefinition): RouteDefinition {
|
||||
if (config instanceof Route || config instanceof Redirect || config instanceof AsyncRoute) {
|
||||
return <RouteDefinition>config;
|
||||
}
|
||||
|
||||
if ((!config.component) == (!config.redirectTo)) {
|
||||
throw new BaseException(
|
||||
`Route config should contain exactly one 'component', or 'redirectTo' property`);
|
||||
}
|
||||
if (config.component) {
|
||||
if (typeof config.component == 'object') {
|
||||
let componentDefinitionObject = <ComponentDefinition>config.component;
|
||||
if (componentDefinitionObject.type == 'constructor') {
|
||||
return new Route({
|
||||
path: config.path,
|
||||
component:<Type>componentDefinitionObject.constructor,
|
||||
as: config.as
|
||||
});
|
||||
} else if (componentDefinitionObject.type == 'loader') {
|
||||
return new AsyncRoute(
|
||||
{path: config.path, loader: componentDefinitionObject.loader, as: config.as});
|
||||
} else {
|
||||
throw new BaseException(
|
||||
`Invalid component type '${componentDefinitionObject.type}'. Valid types are "constructor" and "loader".`);
|
||||
}
|
||||
}
|
||||
return new Route(<{
|
||||
path: string;
|
||||
component: Type;
|
||||
as?: string
|
||||
}>config);
|
||||
}
|
||||
|
||||
if (config.redirectTo) {
|
||||
return new Redirect({path: config.path, redirectTo: config.redirectTo});
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
7
modules/angular2/src/router/route_definition.dart
Normal file
7
modules/angular2/src/router/route_definition.dart
Normal file
@ -0,0 +1,7 @@
|
||||
library angular2.src.router.route_definition;
|
||||
|
||||
abstract class RouteDefinition {
|
||||
final String path;
|
||||
final String as;
|
||||
const RouteDefinition({this.path, this.as});
|
||||
}
|
15
modules/angular2/src/router/route_definition.ts
Normal file
15
modules/angular2/src/router/route_definition.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import {CONST, Type} from 'angular2/src/facade/lang';
|
||||
|
||||
export interface RouteDefinition {
|
||||
path: string;
|
||||
component?: Type | ComponentDefinition;
|
||||
loader?: Function;
|
||||
redirectTo?: string;
|
||||
as?: string;
|
||||
}
|
||||
|
||||
export interface ComponentDefinition {
|
||||
type: string;
|
||||
loader?: Function;
|
||||
component?: Type;
|
||||
}
|
@ -19,6 +19,7 @@ import {
|
||||
|
||||
import {PathRecognizer} from './path_recognizer';
|
||||
import {RouteHandler} from './route_handler';
|
||||
import {Route, AsyncRoute, Redirect, RouteDefinition} from './route_config_impl';
|
||||
import {AsyncRouteHandler} from './async_route_handler';
|
||||
import {SyncRouteHandler} from './sync_route_handler';
|
||||
|
||||
@ -32,25 +33,27 @@ export class RouteRecognizer {
|
||||
redirects: Map<string, string> = new Map();
|
||||
matchers: Map<RegExp, PathRecognizer> = new Map();
|
||||
|
||||
addRedirect(path: string, target: string): void {
|
||||
if (path == '/') {
|
||||
path = '';
|
||||
config(config: RouteDefinition): boolean {
|
||||
var handler;
|
||||
if (config instanceof Redirect) {
|
||||
let path = config.path == '/' ? '' : config.path;
|
||||
this.redirects.set(path, config.redirectTo);
|
||||
return true;
|
||||
} else if (config instanceof Route) {
|
||||
handler = new SyncRouteHandler(config.component);
|
||||
} else if (config instanceof AsyncRoute) {
|
||||
handler = new AsyncRouteHandler(config.loader);
|
||||
}
|
||||
this.redirects.set(path, target);
|
||||
}
|
||||
|
||||
addConfig(path: string, handlerObj: any, alias: string = null): boolean {
|
||||
var handler = configObjToHandler(handlerObj['component']);
|
||||
var recognizer = new PathRecognizer(path, handler);
|
||||
var recognizer = new PathRecognizer(config.path, handler);
|
||||
MapWrapper.forEach(this.matchers, (matcher, _) => {
|
||||
if (recognizer.regex.toString() == matcher.regex.toString()) {
|
||||
throw new BaseException(
|
||||
`Configuration '${path}' conflicts with existing route '${matcher.path}'`);
|
||||
`Configuration '${config.path}' conflicts with existing route '${matcher.path}'`);
|
||||
}
|
||||
});
|
||||
this.matchers.set(recognizer.regex, recognizer);
|
||||
if (isPresent(alias)) {
|
||||
this.names.set(alias, recognizer);
|
||||
if (isPresent(config.as)) {
|
||||
this.names.set(config.as, recognizer);
|
||||
}
|
||||
return recognizer.terminal;
|
||||
}
|
||||
|
@ -20,9 +20,10 @@ import {
|
||||
BaseException,
|
||||
getTypeNameForDebugging
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {RouteConfig} from './route_config_impl';
|
||||
import {RouteConfig, AsyncRoute, Route, Redirect, RouteDefinition} from './route_config_impl';
|
||||
import {reflector} from 'angular2/src/reflection/reflection';
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {normalizeRouteConfig} from './route_config_nomalizer';
|
||||
|
||||
/**
|
||||
* The RouteRegistry holds route configurations for each component in an Angular app.
|
||||
@ -36,8 +37,8 @@ export class RouteRegistry {
|
||||
/**
|
||||
* Given a component and a configuration object, add the route to this registry
|
||||
*/
|
||||
config(parentComponent, config: StringMap<string, any>): void {
|
||||
assertValidConfig(config);
|
||||
config(parentComponent, config: RouteDefinition): void {
|
||||
config = normalizeRouteConfig(config);
|
||||
|
||||
var recognizer: RouteRecognizer = this._rules.get(parentComponent);
|
||||
|
||||
@ -46,22 +47,13 @@ export class RouteRegistry {
|
||||
this._rules.set(parentComponent, recognizer);
|
||||
}
|
||||
|
||||
if (StringMapWrapper.contains(config, 'redirectTo')) {
|
||||
recognizer.addRedirect(config['path'], config['redirectTo']);
|
||||
return;
|
||||
}
|
||||
var terminal = recognizer.config(config);
|
||||
|
||||
config = StringMapWrapper.merge(
|
||||
config, {'component': normalizeComponentDeclaration(config['component'])});
|
||||
|
||||
var component = config['component'];
|
||||
var terminal = recognizer.addConfig(config['path'], config, config['as']);
|
||||
|
||||
if (component['type'] == 'constructor') {
|
||||
if (config instanceof Route) {
|
||||
if (terminal) {
|
||||
assertTerminalComponent(component['constructor'], config['path']);
|
||||
assertTerminalComponent(config.component, config.path);
|
||||
} else {
|
||||
this.configFromComponent(component['constructor']);
|
||||
this.configFromComponent(config.component);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -191,50 +183,6 @@ export class RouteRegistry {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* A config should have a "path" property, and exactly one of:
|
||||
* - `component`
|
||||
* - `redirectTo`
|
||||
*/
|
||||
var ALLOWED_TARGETS = ['component', 'redirectTo'];
|
||||
function assertValidConfig(config: StringMap<string, any>): void {
|
||||
if (!StringMapWrapper.contains(config, 'path')) {
|
||||
throw new BaseException(`Route config should contain a "path" property`);
|
||||
}
|
||||
var targets = 0;
|
||||
ListWrapper.forEach(ALLOWED_TARGETS, (target) => {
|
||||
if (StringMapWrapper.contains(config, target)) {
|
||||
targets += 1;
|
||||
}
|
||||
});
|
||||
if (targets != 1) {
|
||||
throw new BaseException(
|
||||
`Route config should contain exactly one 'component', or 'redirectTo' property`);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a StringMap like: `{ 'constructor': SomeType, 'type': 'constructor' }`
|
||||
*/
|
||||
var VALID_COMPONENT_TYPES = ['constructor', 'loader'];
|
||||
function normalizeComponentDeclaration(config: any): StringMap<string, any> {
|
||||
if (isType(config)) {
|
||||
return {'constructor': config, 'type': 'constructor'};
|
||||
} else if (isStringMap(config)) {
|
||||
if (isBlank(config['type'])) {
|
||||
throw new BaseException(
|
||||
`Component declaration when provided as a map should include a 'type' property`);
|
||||
}
|
||||
var componentType = config['type'];
|
||||
if (!ListWrapper.contains(VALID_COMPONENT_TYPES, componentType)) {
|
||||
throw new BaseException(`Invalid component type '${componentType}'`);
|
||||
}
|
||||
return config;
|
||||
} else {
|
||||
throw new BaseException(`Component declaration should be either a Map or a Type`);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a list of instructions, returns the most specific instruction
|
||||
*/
|
||||
|
@ -16,6 +16,7 @@ import {Instruction} from './instruction';
|
||||
import {RouterOutlet} from './router_outlet';
|
||||
import {Location} from './location';
|
||||
import {getCanActivateHook} from './route_lifecycle_reflector';
|
||||
import {RouteDefinition} from './route_config_impl';
|
||||
|
||||
let _resolveToTrue = PromiseWrapper.resolve(true);
|
||||
let _resolveToFalse = PromiseWrapper.resolve(false);
|
||||
@ -79,25 +80,15 @@ export class Router {
|
||||
* # Usage
|
||||
*
|
||||
* ```
|
||||
* router.config({ 'path': '/', 'component': IndexCmp});
|
||||
* ```
|
||||
*
|
||||
* Or:
|
||||
*
|
||||
* ```
|
||||
* router.config([
|
||||
* { 'path': '/', 'component': IndexComp },
|
||||
* { 'path': '/user/:id', 'component': UserComp },
|
||||
* ]);
|
||||
* ```
|
||||
*/
|
||||
config(config: StringMap<string, any>| List<StringMap<string, any>>): Promise<any> {
|
||||
if (isArray(config)) {
|
||||
(<List<any>>config)
|
||||
.forEach((configObject) => { this.registry.config(this.hostComponent, configObject); });
|
||||
} else {
|
||||
this.registry.config(this.hostComponent, config);
|
||||
}
|
||||
config(definitions: List<RouteDefinition>): Promise<any> {
|
||||
definitions.forEach(
|
||||
(routeDefinition) => { this.registry.config(this.hostComponent, routeDefinition); });
|
||||
return this.renavigate();
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user