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; _location;
_history; _history;
_baseHref:string; _baseHref:string;
constructor() { constructor() {
this._location = DOM.getLocation(); this._location = DOM.getLocation();
this._history = DOM.getHistory(); this._history = DOM.getHistory();
this._baseHref = DOM.getBaseHref(); this._baseHref = DOM.getBaseHref();
} }
onPopState(fn) { onPopState(fn: Function): void {
DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false); DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false);
} }
getBaseHref() { getBaseHref(): string {
return this._baseHref; return this._baseHref;
} }
path() { path(): string {
return this._location.pathname; return this._location.pathname;
} }
@ -26,11 +27,11 @@ export class BrowserLocation {
this._history.pushState(state, title, url); this._history.pushState(state, title, url);
} }
forward() { forward(): void {
this._history.forward(); this._history.forward();
} }
back() { back(): void {
this._history.back(); 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 {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 { export class RouteParams {
params:Map<string, string>; params:StringMap<string, string>;
constructor(params:StringMap) { constructor(params:StringMap) {
this.params = params; this.params = params;
} }
get(param:string) { get(param:string): string {
return StringMapWrapper.get(this.params, param); return normalizeBlank(StringMapWrapper.get(this.params, param));
} }
} }
export class Instruction { export class Instruction {
component:any; component:any;
_children:Map<string, Instruction>; _children:StringMap<string, Instruction>;
router:any; router:any;
matchedUrl:string; matchedUrl:string;
params:Map<string, string>; params:StringMap<string, string>;
reuse:boolean; reuse:boolean;
cost:number; 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.reuse = false;
this.matchedUrl = matchedUrl; this.matchedUrl = matchedUrl;
this.cost = parentCost; this.cost = parentCost;
@ -43,11 +44,11 @@ export class Instruction {
this.params = params; this.params = params;
} }
getChildInstruction(outletName:string) { getChildInstruction(outletName:string): Instruction {
return StringMapWrapper.get(this._children, outletName); return StringMapWrapper.get(this._children, outletName);
} }
forEachChild(fn:Function) { forEachChild(fn:Function): void {
StringMapWrapper.forEach(this._children, fn); StringMapWrapper.forEach(this._children, fn);
} }
@ -60,7 +61,7 @@ export class Instruction {
* Takes a function with signature: * Takes a function with signature:
* (parent:Instruction, child:Instruction) => {} * (parent:Instruction, child:Instruction) => {}
*/ */
traverseSync(fn:Function) { traverseSync(fn:Function): void {
this.forEachChild((childInstruction, _) => fn(this, childInstruction)); this.forEachChild((childInstruction, _) => fn(this, childInstruction));
this.forEachChild((childInstruction, _) => childInstruction.traverseSync(fn)); this.forEachChild((childInstruction, _) => childInstruction.traverseSync(fn));
} }
@ -70,7 +71,7 @@ export class Instruction {
* Takes a function with signature: * Takes a function with signature:
* (child:Instruction, parentOutletName:string) => {} * (child:Instruction, parentOutletName:string) => {}
*/ */
traverseAsync(fn:Function) { traverseAsync(fn:Function):Promise {
return this.mapChildrenAsync(fn) return this.mapChildrenAsync(fn)
.then((_) => this.mapChildrenAsync((childInstruction, _) => childInstruction.traverseAsync(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 * Takes a currently active instruction and sets a reuse flag on this instruction
*/ */
reuseComponentsFrom(oldInstruction:Instruction) { reuseComponentsFrom(oldInstruction:Instruction): void {
this.forEachChild((childInstruction, outletName) => { this.forEachChild((childInstruction, outletName) => {
var oldInstructionChild = oldInstruction.getChildInstruction(outletName); var oldInstructionChild = oldInstruction.getChildInstruction(outletName);
if (shouldReuseComponent(childInstruction, oldInstructionChild)) { 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 && return instr1.component == instr2.component &&
StringMapWrapper.equals(instr1.params, instr2.params); StringMapWrapper.equals(instr1.params, instr2.params);
} }
function mapObjAsync(obj:StringMap, fn) { function mapObjAsync(obj:StringMap, fn): Promise {
return PromiseWrapper.all(mapObj(obj, fn)); return PromiseWrapper.all(mapObj(obj, fn));
} }
function mapObj(obj:StringMap, fn):List { function mapObj(obj:StringMap, fn: Function):List {
var result = ListWrapper.create(); var result = ListWrapper.create();
StringMapWrapper.forEach(obj, (value, key) => ListWrapper.push(result, fn(value, key))); StringMapWrapper.forEach(obj, (value, key) => ListWrapper.push(result, fn(value, key)));
return result; return result;

View File

@ -13,62 +13,62 @@ export class Location {
this._browserLocation.onPopState((_) => this._onPopState(_)); this._browserLocation.onPopState((_) => this._onPopState(_));
} }
_onPopState(_) { _onPopState(_): void {
ObservableWrapper.callNext(this._subject, { ObservableWrapper.callNext(this._subject, {
'url': this.path() 'url': this.path()
}); });
} }
path() { path(): string {
return this.normalize(this._browserLocation.path()); return this.normalize(this._browserLocation.path());
} }
normalize(url) { normalize(url: string): string {
return this._stripBaseHref(stripIndexHtml(url)); return this._stripBaseHref(stripIndexHtml(url));
} }
normalizeAbsolutely(url) { normalizeAbsolutely(url: string): string {
if (url[0] != '/') { if (url[0] != '/') {
url = '/' + url; url = '/' + url;
} }
return this._addBaseHref(url); return this._addBaseHref(url);
} }
_stripBaseHref(url) { _stripBaseHref(url: string): string {
if (this._baseHref.length > 0 && StringWrapper.startsWith(url, this._baseHref)) { if (this._baseHref.length > 0 && StringWrapper.startsWith(url, this._baseHref)) {
return StringWrapper.substring(url, this._baseHref.length); return StringWrapper.substring(url, this._baseHref.length);
} }
return url; return url;
} }
_addBaseHref(url) { _addBaseHref(url: string): string {
if (!StringWrapper.startsWith(url, this._baseHref)) { if (!StringWrapper.startsWith(url, this._baseHref)) {
return this._baseHref + url; return this._baseHref + url;
} }
return url; return url;
} }
go(url:string) { go(url:string): void {
var finalUrl = this.normalizeAbsolutely(url); var finalUrl = this.normalizeAbsolutely(url);
this._browserLocation.pushState(null, '', finalUrl); this._browserLocation.pushState(null, '', finalUrl);
} }
forward() { forward(): void {
this._browserLocation.forward(); this._browserLocation.forward();
} }
back() { back(): void {
this._browserLocation.back(); this._browserLocation.back();
} }
subscribe(onNext, onThrow = null, onReturn = null) { subscribe(onNext, onThrow = null, onReturn = null): void {
ObservableWrapper.subscribe(this._subject, onNext, onThrow, onReturn); ObservableWrapper.subscribe(this._subject, onNext, onThrow, onReturn);
} }
} }
function stripIndexHtml(url) { function stripIndexHtml(url: string): string {
// '/index.html'.length == 11 // '/index.html'.length == 11
if (url.length > 10 && StringWrapper.substring(url, url.length - 11) == '/index.html') { if (url.length > 10 && StringWrapper.substring(url, url.length - 11) == '/index.html') {
return StringWrapper.substring(url, 0, url.length - 11); 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 {Map, MapWrapper, StringMap, StringMapWrapper, List, ListWrapper} from 'angular2/src/facade/collection';
import {escapeRegex} from './url'; import {escapeRegex} from './url';
@ -7,13 +7,14 @@ class StaticSegment {
string:string; string:string;
regex:string; regex:string;
name:string; name:string;
constructor(string:string) { constructor(string:string) {
this.string = string; this.string = string;
this.name = ''; this.name = '';
this.regex = escapeRegex(string); this.regex = escapeRegex(string);
} }
generate(params) { generate(params): string {
return this.string; return this.string;
} }
} }
@ -26,11 +27,11 @@ class DynamicSegment {
this.regex = "([^/]+)"; this.regex = "([^/]+)";
} }
generate(params:StringMap) { generate(params:StringMap<string, string>): string {
if (!StringMapWrapper.contains(params, this.name)) { if (!StringMapWrapper.contains(params, this.name)) {
throw new BaseException(`Route generator for '${this.name}' was not included in parameters passed.`) 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 = "(.+)"; this.regex = "(.+)";
} }
generate(params:StringMap) { generate(params:StringMap<string, string>): string {
return StringMapWrapper.get(params, this.name); return normalizeBlank(StringMapWrapper.get(params, this.name));
} }
} }
@ -82,9 +83,8 @@ function parsePathString(route:string) {
return {segments: results, cost}; return {segments: results, cost};
} }
var SLASH_RE = RegExpWrapper.create('/');
function splitBySlash (url:string):List<string> { 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) { constructor(path:string, handler:any) {
this.handler = handler; this.handler = handler;
this.segments = ListWrapper.create(); this.segments = [];
// TODO: use destructuring assignment // TODO: use destructuring assignment
// see https://github.com/angular/ts2dart/issues/158 // see https://github.com/angular/ts2dart/issues/158
@ -115,7 +115,7 @@ export class PathRecognizer {
this.cost = cost; this.cost = cost;
} }
parseParams(url:string):StringMap { parseParams(url:string):StringMap<string, string> {
var params = StringMapWrapper.create(); var params = StringMapWrapper.create();
var urlPart = url; var urlPart = url;
for(var i=0; i<this.segments.length; i++) { for(var i=0; i<this.segments.length; i++) {
@ -130,7 +130,7 @@ export class PathRecognizer {
return params; return params;
} }
generate(params:StringMap):string { generate(params:StringMap<string, string>):string {
return ListWrapper.join(ListWrapper.map(this.segments, (segment) => return ListWrapper.join(ListWrapper.map(this.segments, (segment) =>
'/' + segment.generate(params)), ''); '/' + segment.generate(params)), '');
} }

View File

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

View File

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

View File

@ -14,11 +14,11 @@ export class RouteRecognizer {
this.redirects = MapWrapper.create(); this.redirects = MapWrapper.create();
} }
addRedirect(path:string, target:string) { addRedirect(path:string, target:string): void {
MapWrapper.set(this.redirects, path, target); 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); var recognizer = new PathRecognizer(path, handler);
MapWrapper.set(this.matchers, recognizer.regex, recognizer); MapWrapper.set(this.matchers, recognizer.regex, recognizer);
if (isPresent(alias)) { if (isPresent(alias)) {
@ -59,12 +59,12 @@ export class RouteRecognizer {
return solutions; return solutions;
} }
hasRoute(name:string) { hasRoute(name:string): boolean {
return MapWrapper.contains(this.names, name); return MapWrapper.contains(this.names, name);
} }
generate(name:string, params:any) { generate(name:string, params:any): string {
var pathRecognizer = MapWrapper.get(this.names, name); 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(); this._rules = MapWrapper.create();
} }
config(parentComponent, config) { config(parentComponent, config: StringMap<string, any>): void {
if (!StringMapWrapper.contains(config, 'path')) { if (!StringMapWrapper.contains(config, 'path')) {
throw new BaseException('Route config does not contain "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"'); throw new BaseException('Route config does not contain "component," "components," or "redirectTo"');
} }
var recognizer:RouteRecognizer; var recognizer:RouteRecognizer = MapWrapper.get(this._rules, parentComponent);
if (MapWrapper.contains(this._rules, parentComponent)) {
recognizer = MapWrapper.get(this._rules, parentComponent); if (isBlank(recognizer)) {
} else {
recognizer = new RouteRecognizer(); recognizer = new RouteRecognizer();
MapWrapper.set(this._rules, parentComponent, recognizer); MapWrapper.set(this._rules, parentComponent, recognizer);
} }
@ -46,7 +45,7 @@ export class RouteRegistry {
recognizer.addConfig(config['path'], config, config['as']); recognizer.addConfig(config['path'], config, config['as']);
} }
configFromComponent(component) { configFromComponent(component): void {
if (!isType(component)) { if (!isType(component)) {
return; return;
} }
@ -71,14 +70,14 @@ export class RouteRegistry {
} }
recognize(url:string, parentComponent) { recognize(url:string, parentComponent): Instruction {
var componentRecognizer = MapWrapper.get(this._rules, parentComponent); var componentRecognizer = MapWrapper.get(this._rules, parentComponent);
if (isBlank(componentRecognizer)) { if (isBlank(componentRecognizer)) {
return null; return null;
} }
var componentSolutions = componentRecognizer.recognize(url); var componentSolutions = componentRecognizer.recognize(url);
var fullSolutions = ListWrapper.create(); var fullSolutions = [];
for(var i = 0; i < componentSolutions.length; i++) { for(var i = 0; i < componentSolutions.length; i++) {
var candidate = componentSolutions[i]; var candidate = componentSolutions[i];
@ -120,16 +119,14 @@ export class RouteRegistry {
return null; return null;
} }
generate(name:string, params:any, hostComponent) { generate(name:string, params:StringMap<string, string>, hostComponent): string {
//TODO: implement for hierarchical routes //TODO: implement for hierarchical routes
var componentRecognizer = MapWrapper.get(this._rules, hostComponent); var componentRecognizer = MapWrapper.get(this._rules, hostComponent);
if (isPresent(componentRecognizer)) { return isPresent(componentRecognizer) ? componentRecognizer.generate(name, params) : null;
return componentRecognizer.generate(name, params);
}
} }
} }
function handlerToLeafInstructions(context, parentComponent) { function handlerToLeafInstructions(context, parentComponent): Instruction {
var children = StringMapWrapper.create(); var children = StringMapWrapper.create();
StringMapWrapper.forEach(context['handler']['components'], (component, outletName) => { StringMapWrapper.forEach(context['handler']['components'], (component, outletName) => {
children[outletName] = new Instruction({ children[outletName] = new Instruction({
@ -150,7 +147,7 @@ function handlerToLeafInstructions(context, parentComponent) {
// { component: Foo } // { component: Foo }
// mutates the config to: // mutates the config to:
// { components: { default: Foo } } // { components: { default: Foo } }
function normalizeConfig(config:StringMap) { function normalizeConfig(config:StringMap<string, any>): StringMap<string, any> {
if (StringMapWrapper.contains(config, 'component')) { if (StringMapWrapper.contains(config, 'component')) {
var component = StringMapWrapper.get(config, 'component'); var component = StringMapWrapper.get(config, 'component');
var components = StringMapWrapper.create(); 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. * Constructs a child router. You probably don't need to use this unless you're writing a reusable component.
*/ */
childRouter(outletName = 'default') { childRouter(outletName = 'default'): Router {
if (!MapWrapper.contains(this._children, outletName)) { var router = MapWrapper.get(this._children, outletName);
MapWrapper.set(this._children, outletName, new ChildRouter(this, 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. * 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); MapWrapper.set(this._outlets, name, outlet);
if (isPresent(this._currentInstruction)) { if (isPresent(this._currentInstruction)) {
var childInstruction = this._currentInstruction.getChildInstruction(name); var childInstruction = this._currentInstruction.getChildInstruction(name);
@ -91,12 +95,12 @@ export class Router {
* ``` * ```
* *
*/ */
config(config:any) { config(config:any): Promise {
if (config instanceof List) { if (config instanceof List) {
config.forEach((configObject) => { config.forEach((configObject) => {
// TODO: this is a hack // TODO: this is a hack
this._registry.config(this.hostComponent, configObject); this._registry.config(this.hostComponent, configObject);
}) });
} else { } else {
this._registry.config(this.hostComponent, config); this._registry.config(this.hostComponent, config);
} }
@ -140,18 +144,18 @@ export class Router {
return result; return result;
} }
_startNavigating() { _startNavigating(): void {
this.navigating = true; this.navigating = true;
} }
_finishNavigating() { _finishNavigating(): void {
this.navigating = false; this.navigating = false;
} }
/** /**
* Subscribe to URL updates from the router * Subscribe to URL updates from the router
*/ */
subscribe(onNext) { subscribe(onNext): void {
ObservableWrapper.subscribe(this._subject, onNext); ObservableWrapper.subscribe(this._subject, onNext);
} }
@ -182,7 +186,7 @@ export class Router {
/** /**
* Given a URL, returns an instruction representing the component graph * Given a URL, returns an instruction representing the component graph
*/ */
recognize(url:string) { recognize(url:string): Instruction {
return this._registry.recognize(url, this.hostComponent); 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 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); 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)); return PromiseWrapper.all(mapObj(obj, fn));
} }

View File

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

View File

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

View File

@ -6,7 +6,7 @@ var specialCharacters = [
var escapeRe = RegExpWrapper.create('(\\' + specialCharacters.join('|\\') + ')', 'g'); 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 StringWrapper.replaceAllMapped(string, escapeRe, (match) => {
return "\\" + match; return "\\" + match;
}); });