refactor(router): add types

This commit is contained in:
Victor Berchet 2015-05-14 15:24:35 +02:00
parent 3644036693
commit fc13cdab3a
12 changed files with 89 additions and 84 deletions

View File

@ -4,21 +4,22 @@ export class BrowserLocation {
_location;
_history;
_baseHref:string;
constructor() {
this._location = DOM.getLocation();
this._history = DOM.getHistory();
this._baseHref = DOM.getBaseHref();
}
onPopState(fn) {
onPopState(fn: Function): void {
DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false);
}
getBaseHref() {
getBaseHref(): string {
return this._baseHref;
}
path() {
path(): string {
return this._location.pathname;
}
@ -26,11 +27,11 @@ export class BrowserLocation {
this._history.pushState(state, title, url);
}
forward() {
forward(): void {
this._history.forward();
}
back() {
back(): void {
this._history.back();
}
}

View File

@ -1,28 +1,29 @@
import {Map, MapWrapper, StringMap, StringMapWrapper, List, ListWrapper} from 'angular2/src/facade/collection';
import {Map, MapWrapper, StringMap, StringMapWrapper, List, ListWrapper} from 'angular2/src/facade/collection';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {isPresent} from 'angular2/src/facade/lang';
import {isPresent, normalizeBlank} from 'angular2/src/facade/lang';
export class RouteParams {
params:Map<string, string>;
params:StringMap<string, string>;
constructor(params:StringMap) {
this.params = params;
}
get(param:string) {
return StringMapWrapper.get(this.params, param);
get(param:string): string {
return normalizeBlank(StringMapWrapper.get(this.params, param));
}
}
export class Instruction {
component:any;
_children:Map<string, Instruction>;
_children:StringMap<string, Instruction>;
router:any;
matchedUrl:string;
params:Map<string, string>;
params:StringMap<string, string>;
reuse:boolean;
cost:number;
constructor({params, component, children, matchedUrl, parentCost}:{params:StringMap, component:any, children:Map, matchedUrl:string, cost:int} = {}) {
constructor({params, component, children, matchedUrl, parentCost}:{params:StringMap, component:any, children:StringMap, matchedUrl:string, cost:number} = {}) {
this.reuse = false;
this.matchedUrl = matchedUrl;
this.cost = parentCost;
@ -43,11 +44,11 @@ export class Instruction {
this.params = params;
}
getChildInstruction(outletName:string) {
getChildInstruction(outletName:string): Instruction {
return StringMapWrapper.get(this._children, outletName);
}
forEachChild(fn:Function) {
forEachChild(fn:Function): void {
StringMapWrapper.forEach(this._children, fn);
}
@ -60,7 +61,7 @@ export class Instruction {
* Takes a function with signature:
* (parent:Instruction, child:Instruction) => {}
*/
traverseSync(fn:Function) {
traverseSync(fn:Function): void {
this.forEachChild((childInstruction, _) => fn(this, childInstruction));
this.forEachChild((childInstruction, _) => childInstruction.traverseSync(fn));
}
@ -70,7 +71,7 @@ export class Instruction {
* Takes a function with signature:
* (child:Instruction, parentOutletName:string) => {}
*/
traverseAsync(fn:Function) {
traverseAsync(fn:Function):Promise {
return this.mapChildrenAsync(fn)
.then((_) => this.mapChildrenAsync((childInstruction, _) => childInstruction.traverseAsync(fn)));
}
@ -79,7 +80,7 @@ export class Instruction {
/**
* Takes a currently active instruction and sets a reuse flag on this instruction
*/
reuseComponentsFrom(oldInstruction:Instruction) {
reuseComponentsFrom(oldInstruction:Instruction): void {
this.forEachChild((childInstruction, outletName) => {
var oldInstructionChild = oldInstruction.getChildInstruction(outletName);
if (shouldReuseComponent(childInstruction, oldInstructionChild)) {
@ -89,16 +90,16 @@ export class Instruction {
}
}
function shouldReuseComponent(instr1:Instruction, instr2:Instruction) {
function shouldReuseComponent(instr1:Instruction, instr2:Instruction): boolean {
return instr1.component == instr2.component &&
StringMapWrapper.equals(instr1.params, instr2.params);
}
function mapObjAsync(obj:StringMap, fn) {
function mapObjAsync(obj:StringMap, fn): Promise {
return PromiseWrapper.all(mapObj(obj, fn));
}
function mapObj(obj:StringMap, fn):List {
function mapObj(obj:StringMap, fn: Function):List {
var result = ListWrapper.create();
StringMapWrapper.forEach(obj, (value, key) => ListWrapper.push(result, fn(value, key)));
return result;

View File

@ -13,62 +13,62 @@ export class Location {
this._browserLocation.onPopState((_) => this._onPopState(_));
}
_onPopState(_) {
_onPopState(_): void {
ObservableWrapper.callNext(this._subject, {
'url': this.path()
});
}
path() {
path(): string {
return this.normalize(this._browserLocation.path());
}
normalize(url) {
normalize(url: string): string {
return this._stripBaseHref(stripIndexHtml(url));
}
normalizeAbsolutely(url) {
normalizeAbsolutely(url: string): string {
if (url[0] != '/') {
url = '/' + url;
}
return this._addBaseHref(url);
}
_stripBaseHref(url) {
_stripBaseHref(url: string): string {
if (this._baseHref.length > 0 && StringWrapper.startsWith(url, this._baseHref)) {
return StringWrapper.substring(url, this._baseHref.length);
}
return url;
}
_addBaseHref(url) {
_addBaseHref(url: string): string {
if (!StringWrapper.startsWith(url, this._baseHref)) {
return this._baseHref + url;
}
return url;
}
go(url:string) {
go(url:string): void {
var finalUrl = this.normalizeAbsolutely(url);
this._browserLocation.pushState(null, '', finalUrl);
}
forward() {
forward(): void {
this._browserLocation.forward();
}
back() {
back(): void {
this._browserLocation.back();
}
subscribe(onNext, onThrow = null, onReturn = null) {
subscribe(onNext, onThrow = null, onReturn = null): void {
ObservableWrapper.subscribe(this._subject, onNext, onThrow, onReturn);
}
}
function stripIndexHtml(url) {
function stripIndexHtml(url: string): string {
// '/index.html'.length == 11
if (url.length > 10 && StringWrapper.substring(url, url.length - 11) == '/index.html') {
return StringWrapper.substring(url, 0, url.length - 11);

View File

@ -1,4 +1,4 @@
import {RegExp, RegExpWrapper, RegExpMatcherWrapper, StringWrapper, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {RegExp, RegExpWrapper, RegExpMatcherWrapper, StringWrapper, isPresent, isBlank, BaseException, normalizeBlank} from 'angular2/src/facade/lang';
import {Map, MapWrapper, StringMap, StringMapWrapper, List, ListWrapper} from 'angular2/src/facade/collection';
import {escapeRegex} from './url';
@ -7,13 +7,14 @@ class StaticSegment {
string:string;
regex:string;
name:string;
constructor(string:string) {
this.string = string;
this.name = '';
this.regex = escapeRegex(string);
}
generate(params) {
generate(params): string {
return this.string;
}
}
@ -26,11 +27,11 @@ class DynamicSegment {
this.regex = "([^/]+)";
}
generate(params:StringMap) {
generate(params:StringMap<string, string>): string {
if (!StringMapWrapper.contains(params, this.name)) {
throw new BaseException(`Route generator for '${this.name}' was not included in parameters passed.`)
}
return StringMapWrapper.get(params, this.name);
return normalizeBlank(StringMapWrapper.get(params, this.name));
}
}
@ -43,8 +44,8 @@ class StarSegment {
this.regex = "(.+)";
}
generate(params:StringMap) {
return StringMapWrapper.get(params, this.name);
generate(params:StringMap<string, string>): string {
return normalizeBlank(StringMapWrapper.get(params, this.name));
}
}
@ -82,9 +83,8 @@ function parsePathString(route:string) {
return {segments: results, cost};
}
var SLASH_RE = RegExpWrapper.create('/');
function splitBySlash (url:string):List<string> {
return StringWrapper.split(url, SLASH_RE);
return url.split('/');
}
@ -97,7 +97,7 @@ export class PathRecognizer {
constructor(path:string, handler:any) {
this.handler = handler;
this.segments = ListWrapper.create();
this.segments = [];
// TODO: use destructuring assignment
// see https://github.com/angular/ts2dart/issues/158
@ -115,7 +115,7 @@ export class PathRecognizer {
this.cost = cost;
}
parseParams(url:string):StringMap {
parseParams(url:string):StringMap<string, string> {
var params = StringMapWrapper.create();
var urlPart = url;
for(var i=0; i<this.segments.length; i++) {
@ -130,7 +130,7 @@ export class PathRecognizer {
return params;
}
generate(params:StringMap):string {
generate(params:StringMap<string, string>):string {
return ListWrapper.join(ListWrapper.map(this.segments, (segment) =>
'/' + segment.generate(params)), '');
}

View File

@ -7,7 +7,8 @@ import {Instruction} from './instruction';
* "Steps" are conceptually similar to "middleware"
*/
export class Pipeline {
steps:List;
steps:List<Function>;
constructor() {
this.steps = [
instruction => instruction.traverseSync((parentInstruction, childInstruction) => {

View File

@ -11,6 +11,7 @@ import {List, Map} from 'angular2/src/facade/collection';
*/
export class RouteConfig {
configs:List<Map>;
@CONST()
constructor(configs:List<Map>) {
this.configs = configs;

View File

@ -14,11 +14,11 @@ export class RouteRecognizer {
this.redirects = MapWrapper.create();
}
addRedirect(path:string, target:string) {
addRedirect(path:string, target:string): void {
MapWrapper.set(this.redirects, path, target);
}
addConfig(path:string, handler:any, alias:string = null) {
addConfig(path:string, handler:any, alias:string = null): void {
var recognizer = new PathRecognizer(path, handler);
MapWrapper.set(this.matchers, recognizer.regex, recognizer);
if (isPresent(alias)) {
@ -59,12 +59,12 @@ export class RouteRecognizer {
return solutions;
}
hasRoute(name:string) {
hasRoute(name:string): boolean {
return MapWrapper.contains(this.names, name);
}
generate(name:string, params:any) {
generate(name:string, params:any): string {
var pathRecognizer = MapWrapper.get(this.names, name);
return pathRecognizer.generate(params);
return isPresent(pathRecognizer) ? pathRecognizer.generate(params) : null;
}
}

View File

@ -12,7 +12,7 @@ export class RouteRegistry {
this._rules = MapWrapper.create();
}
config(parentComponent, config) {
config(parentComponent, config: StringMap<string, any>): void {
if (!StringMapWrapper.contains(config, 'path')) {
throw new BaseException('Route config does not contain "path"');
}
@ -23,10 +23,9 @@ export class RouteRegistry {
throw new BaseException('Route config does not contain "component," "components," or "redirectTo"');
}
var recognizer:RouteRecognizer;
if (MapWrapper.contains(this._rules, parentComponent)) {
recognizer = MapWrapper.get(this._rules, parentComponent);
} else {
var recognizer:RouteRecognizer = MapWrapper.get(this._rules, parentComponent);
if (isBlank(recognizer)) {
recognizer = new RouteRecognizer();
MapWrapper.set(this._rules, parentComponent, recognizer);
}
@ -46,7 +45,7 @@ export class RouteRegistry {
recognizer.addConfig(config['path'], config, config['as']);
}
configFromComponent(component) {
configFromComponent(component): void {
if (!isType(component)) {
return;
}
@ -71,14 +70,14 @@ export class RouteRegistry {
}
recognize(url:string, parentComponent) {
recognize(url:string, parentComponent): Instruction {
var componentRecognizer = MapWrapper.get(this._rules, parentComponent);
if (isBlank(componentRecognizer)) {
return null;
}
var componentSolutions = componentRecognizer.recognize(url);
var fullSolutions = ListWrapper.create();
var fullSolutions = [];
for(var i = 0; i < componentSolutions.length; i++) {
var candidate = componentSolutions[i];
@ -120,16 +119,14 @@ export class RouteRegistry {
return null;
}
generate(name:string, params:any, hostComponent) {
generate(name:string, params:StringMap<string, string>, hostComponent): string {
//TODO: implement for hierarchical routes
var componentRecognizer = MapWrapper.get(this._rules, hostComponent);
if (isPresent(componentRecognizer)) {
return componentRecognizer.generate(name, params);
}
return isPresent(componentRecognizer) ? componentRecognizer.generate(name, params) : null;
}
}
function handlerToLeafInstructions(context, parentComponent) {
function handlerToLeafInstructions(context, parentComponent): Instruction {
var children = StringMapWrapper.create();
StringMapWrapper.forEach(context['handler']['components'], (component, outletName) => {
children[outletName] = new Instruction({
@ -150,7 +147,7 @@ function handlerToLeafInstructions(context, parentComponent) {
// { component: Foo }
// mutates the config to:
// { components: { default: Foo } }
function normalizeConfig(config:StringMap) {
function normalizeConfig(config:StringMap<string, any>): StringMap<string, any> {
if (StringMapWrapper.contains(config, 'component')) {
var component = StringMapWrapper.get(config, 'component');
var components = StringMapWrapper.create();

View File

@ -51,18 +51,22 @@ export class Router {
/**
* Constructs a child router. You probably don't need to use this unless you're writing a reusable component.
*/
childRouter(outletName = 'default') {
if (!MapWrapper.contains(this._children, outletName)) {
MapWrapper.set(this._children, outletName, new ChildRouter(this, outletName));
childRouter(outletName = 'default'): Router {
var router = MapWrapper.get(this._children, outletName);
if (isBlank(router)) {
router = new ChildRouter(this, outletName);
MapWrapper.set(this._children, outletName, router);
}
return MapWrapper.get(this._children, outletName);
return router;
}
/**
* Register an object to notify of route changes. You probably don't need to use this unless you're writing a reusable component.
*/
registerOutlet(outlet:RouterOutlet, name = 'default'):Promise {
registerOutlet(outlet:RouterOutlet, name: string = 'default'):Promise {
MapWrapper.set(this._outlets, name, outlet);
if (isPresent(this._currentInstruction)) {
var childInstruction = this._currentInstruction.getChildInstruction(name);
@ -91,12 +95,12 @@ export class Router {
* ```
*
*/
config(config:any) {
config(config:any): Promise {
if (config instanceof List) {
config.forEach((configObject) => {
// TODO: this is a hack
this._registry.config(this.hostComponent, configObject);
})
});
} else {
this._registry.config(this.hostComponent, config);
}
@ -140,18 +144,18 @@ export class Router {
return result;
}
_startNavigating() {
_startNavigating(): void {
this.navigating = true;
}
_finishNavigating() {
_finishNavigating(): void {
this.navigating = false;
}
/**
* Subscribe to URL updates from the router
*/
subscribe(onNext) {
subscribe(onNext): void {
ObservableWrapper.subscribe(this._subject, onNext);
}
@ -182,7 +186,7 @@ export class Router {
/**
* Given a URL, returns an instruction representing the component graph
*/
recognize(url:string) {
recognize(url:string): Instruction {
return this._registry.recognize(url, this.hostComponent);
}
@ -202,7 +206,7 @@ export class Router {
/**
* Generate a URL from a component name and optional map of parameters. The URL is relative to the app's base href.
*/
generate(name:string, params:any) {
generate(name:string, params:StringMap<string, string>): string {
return this._registry.generate(name, params, this.hostComponent);
}
}
@ -223,7 +227,7 @@ class ChildRouter extends Router {
}
}
function mapObjAsync(obj:Map, fn) {
function mapObjAsync(obj:Map, fn): Promise {
return PromiseWrapper.all(mapObj(obj, fn));
}

View File

@ -40,7 +40,7 @@ import {Location} from './location';
export class RouterLink {
_domEl;
_route:string;
_params:any;
_params:StringMap<string, string>;
_router:Router;
_location:Location;
_href:string;
@ -56,15 +56,15 @@ export class RouterLink {
});
}
set route(changes) {
set route(changes: string) {
this._route = changes;
}
set params(changes) {
set params(changes: StringMap) {
this._params = changes;
}
onAllChangesDone() {
onAllChangesDone(): void {
if (isPresent(this._route) && isPresent(this._params)) {
var newHref = this._router.generate(this._route, this._params);
this._href = this._location.normalizeAbsolutely(newHref);

View File

@ -29,7 +29,7 @@ export class RouterOutlet {
this._router.registerOutlet(this, nameAttr);
}
activate(instruction:Instruction) {
activate(instruction:Instruction): Promise {
return this._compiler.compileInHost(instruction.component).then((pv) => {
var outletInjector = this._injector.resolveAndCreateChild([
bind(RouteParams).toValue(new RouteParams(instruction.params)),
@ -41,11 +41,11 @@ export class RouterOutlet {
});
}
canActivate(instruction:any) {
canActivate(instruction:Instruction): Promise<boolean> {
return PromiseWrapper.resolve(true);
}
canDeactivate(instruction:any) {
canDeactivate(instruction:Instruction): Promise<boolean> {
return PromiseWrapper.resolve(true);
}
}

View File

@ -6,7 +6,7 @@ var specialCharacters = [
var escapeRe = RegExpWrapper.create('(\\' + specialCharacters.join('|\\') + ')', 'g');
export function escapeRegex(string:string) {
export function escapeRegex(string:string): string {
return StringWrapper.replaceAllMapped(string, escapeRe, (match) => {
return "\\" + match;
});