parent
4c8e11a577
commit
ba07f39347
|
@ -14,7 +14,6 @@ export {RouteRegistry} from './src/router/route_registry';
|
||||||
export {BrowserLocation} from './src/router/browser_location';
|
export {BrowserLocation} from './src/router/browser_location';
|
||||||
export {Location} from './src/router/location';
|
export {Location} from './src/router/location';
|
||||||
export {Pipeline} from './src/router/pipeline';
|
export {Pipeline} from './src/router/pipeline';
|
||||||
export * from './src/router/route_config_annotation';
|
|
||||||
export * from './src/router/route_config_decorator';
|
export * from './src/router/route_config_decorator';
|
||||||
|
|
||||||
import {BrowserLocation} from './src/router/browser_location';
|
import {BrowserLocation} from './src/router/browser_location';
|
||||||
|
@ -27,18 +26,16 @@ import {Location} from './src/router/location';
|
||||||
import {appComponentTypeToken} from './src/core/application_tokens';
|
import {appComponentTypeToken} from './src/core/application_tokens';
|
||||||
import {bind} from './di';
|
import {bind} from './di';
|
||||||
import {CONST_EXPR} from './src/facade/lang';
|
import {CONST_EXPR} from './src/facade/lang';
|
||||||
|
import {List} from './src/facade/collection';
|
||||||
|
|
||||||
export const routerDirectives:List = CONST_EXPR([
|
export const routerDirectives: List<any> = CONST_EXPR([RouterOutlet, RouterLink]);
|
||||||
RouterOutlet,
|
|
||||||
RouterLink
|
|
||||||
]);
|
|
||||||
|
|
||||||
export var routerInjectables:List = [
|
export var routerInjectables: List<any> = [
|
||||||
RouteRegistry,
|
RouteRegistry,
|
||||||
Pipeline,
|
Pipeline,
|
||||||
BrowserLocation,
|
BrowserLocation,
|
||||||
Location,
|
Location,
|
||||||
bind(Router).toFactory((registry, pipeline, location, appRoot) => {
|
bind(Router).toFactory((registry, pipeline, location, appRoot) =>
|
||||||
return new RootRouter(registry, pipeline, location, appRoot);
|
{ return new RootRouter(registry, pipeline, location, appRoot);},
|
||||||
}, [RouteRegistry, Pipeline, Location, appComponentTypeToken])
|
[RouteRegistry, Pipeline, Location, appComponentTypeToken])
|
||||||
];
|
];
|
|
@ -110,10 +110,10 @@ export class DomAdapter {
|
||||||
cssToRules(css: string): List<any> { throw _abstract(); }
|
cssToRules(css: string): List<any> { throw _abstract(); }
|
||||||
supportsDOMEvents(): boolean { throw _abstract(); }
|
supportsDOMEvents(): boolean { throw _abstract(); }
|
||||||
supportsNativeShadowDOM(): boolean { throw _abstract(); }
|
supportsNativeShadowDOM(): boolean { throw _abstract(); }
|
||||||
getGlobalEventTarget(target: string) { throw _abstract(); }
|
getGlobalEventTarget(target: string): any { throw _abstract(); }
|
||||||
getHistory() { throw _abstract(); }
|
getHistory(): any { throw _abstract(); }
|
||||||
getLocation() { throw _abstract(); }
|
getLocation(): any { throw _abstract(); }
|
||||||
getBaseHref() { throw _abstract(); }
|
getBaseHref(): string { throw _abstract(); }
|
||||||
getUserAgent(): string { throw _abstract(); }
|
getUserAgent(): string { throw _abstract(); }
|
||||||
setData(element, name: string, value: string) { throw _abstract(); }
|
setData(element, name: string, value: string) { throw _abstract(); }
|
||||||
getData(element, name: string): string { throw _abstract(); }
|
getData(element, name: string): string { throw _abstract(); }
|
||||||
|
|
|
@ -15,7 +15,11 @@ export 'dart:html'
|
||||||
Node,
|
Node,
|
||||||
MouseEvent,
|
MouseEvent,
|
||||||
KeyboardEvent,
|
KeyboardEvent,
|
||||||
Event;
|
Event,
|
||||||
|
EventTarget,
|
||||||
|
History,
|
||||||
|
Location,
|
||||||
|
EventListener;
|
||||||
|
|
||||||
final _gc = context['gc'];
|
final _gc = context['gc'];
|
||||||
|
|
||||||
|
|
|
@ -10,3 +10,7 @@ export var gc = window['gc'] ? () => window['gc']() : () => null;
|
||||||
export const Event = Event;
|
export const Event = Event;
|
||||||
export const MouseEvent = MouseEvent;
|
export const MouseEvent = MouseEvent;
|
||||||
export const KeyboardEvent = KeyboardEvent;
|
export const KeyboardEvent = KeyboardEvent;
|
||||||
|
export const EventTarget = EventTarget;
|
||||||
|
export const History = History;
|
||||||
|
export const Location = Location;
|
||||||
|
export const EventListener = EventListener;
|
||||||
|
|
|
@ -9,10 +9,10 @@ import {Location} from 'angular2/src/router/location';
|
||||||
@proxy
|
@proxy
|
||||||
@IMPLEMENTS(Location)
|
@IMPLEMENTS(Location)
|
||||||
export class SpyLocation extends SpyObject {
|
export class SpyLocation extends SpyObject {
|
||||||
urlChanges:List<string>;
|
urlChanges: List<string>;
|
||||||
_path:string;
|
_path: string;
|
||||||
_subject:EventEmitter;
|
_subject: EventEmitter;
|
||||||
_baseHref:string;
|
_baseHref: string;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -22,29 +22,17 @@ export class SpyLocation extends SpyObject {
|
||||||
this._baseHref = '';
|
this._baseHref = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
setInitialPath(url:string) {
|
setInitialPath(url: string) { this._path = url; }
|
||||||
this._path = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
setBaseHref(url:string) {
|
setBaseHref(url: string) { this._baseHref = url; }
|
||||||
this._baseHref = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
path():string {
|
path(): string { return this._path; }
|
||||||
return this._path;
|
|
||||||
}
|
|
||||||
|
|
||||||
simulateUrlPop(pathname:string) {
|
simulateUrlPop(pathname: string) { ObservableWrapper.callNext(this._subject, {'url': pathname}); }
|
||||||
ObservableWrapper.callNext(this._subject, {
|
|
||||||
'url': pathname
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
normalizeAbsolutely(url) {
|
normalizeAbsolutely(url) { return this._baseHref + url; }
|
||||||
return this._baseHref + url;
|
|
||||||
}
|
|
||||||
|
|
||||||
go(url:string) {
|
go(url: string) {
|
||||||
url = this.normalizeAbsolutely(url);
|
url = this.normalizeAbsolutely(url);
|
||||||
if (this._path == url) {
|
if (this._path == url) {
|
||||||
return;
|
return;
|
||||||
|
@ -65,5 +53,5 @@ export class SpyLocation extends SpyObject {
|
||||||
ObservableWrapper.subscribe(this._subject, onNext, onThrow, onReturn);
|
ObservableWrapper.subscribe(this._subject, onNext, onThrow, onReturn);
|
||||||
}
|
}
|
||||||
|
|
||||||
noSuchMethod(m){return super.noSuchMethod(m);}
|
noSuchMethod(m) { return super.noSuchMethod(m); }
|
||||||
}
|
}
|
|
@ -1,37 +0,0 @@
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
||||||
|
|
||||||
export class BrowserLocation {
|
|
||||||
_location;
|
|
||||||
_history;
|
|
||||||
_baseHref:string;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this._location = DOM.getLocation();
|
|
||||||
this._history = DOM.getHistory();
|
|
||||||
this._baseHref = DOM.getBaseHref();
|
|
||||||
}
|
|
||||||
|
|
||||||
onPopState(fn: Function): void {
|
|
||||||
DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
getBaseHref(): string {
|
|
||||||
return this._baseHref;
|
|
||||||
}
|
|
||||||
|
|
||||||
path(): string {
|
|
||||||
return this._location.pathname;
|
|
||||||
}
|
|
||||||
|
|
||||||
pushState(state:any, title:string, url:string) {
|
|
||||||
this._history.pushState(state, title, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
forward(): void {
|
|
||||||
this._history.forward();
|
|
||||||
}
|
|
||||||
|
|
||||||
back(): void {
|
|
||||||
this._history.back();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
import {Injectable} from 'angular2/di';
|
||||||
|
import {EventListener, History, Location} from 'angular2/src/facade/browser';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BrowserLocation {
|
||||||
|
private _location: Location;
|
||||||
|
private _history: History;
|
||||||
|
private _baseHref: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._location = DOM.getLocation();
|
||||||
|
this._history = DOM.getHistory();
|
||||||
|
this._baseHref = DOM.getBaseHref();
|
||||||
|
}
|
||||||
|
|
||||||
|
onPopState(fn: EventListener): void {
|
||||||
|
DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
getBaseHref(): string { return this._baseHref; }
|
||||||
|
|
||||||
|
path(): string { return this._location.pathname; }
|
||||||
|
|
||||||
|
pushState(state: any, title: string, url: string) { this._history.pushState(state, title, url); }
|
||||||
|
|
||||||
|
forward(): void { this._history.forward(); }
|
||||||
|
|
||||||
|
back(): void { this._history.back(); }
|
||||||
|
}
|
|
@ -1,37 +1,44 @@
|
||||||
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, normalizeBlank} from 'angular2/src/facade/lang';
|
import {isPresent, normalizeBlank} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
export class RouteParams {
|
export class RouteParams {
|
||||||
params:StringMap<string, string>;
|
constructor(public params: StringMap<string, string>) {}
|
||||||
|
|
||||||
constructor(params:StringMap) {
|
get(param: string): string { return normalizeBlank(StringMapWrapper.get(this.params, param)); }
|
||||||
this.params = params;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
component: any;
|
||||||
_children:Map<string, Instruction>;
|
private _children: StringMap<string, Instruction>;
|
||||||
|
|
||||||
// the part of the URL captured by this instruction
|
// the part of the URL captured by this instruction
|
||||||
capturedUrl:string;
|
capturedUrl: string;
|
||||||
|
|
||||||
// the part of the URL captured by this instruction and all children
|
// the part of the URL captured by this instruction and all children
|
||||||
accumulatedUrl:string;
|
accumulatedUrl: string;
|
||||||
|
|
||||||
params:StringMap<string, string>;
|
params: StringMap<string, string>;
|
||||||
reuse:boolean;
|
reuse: boolean;
|
||||||
specificity:number;
|
specificity: number;
|
||||||
|
|
||||||
constructor({params, component, children, matchedUrl, parentSpecificity}:{params:StringMap, component:any, children:Map, matchedUrl:string, parentSpecificity:number} = {}) {
|
constructor({params, component, children, matchedUrl, parentSpecificity}: {
|
||||||
|
params?: StringMap<string, any>,
|
||||||
|
component?: any,
|
||||||
|
children?: StringMap<string, Instruction>,
|
||||||
|
matchedUrl?: string,
|
||||||
|
parentSpecificity?: number
|
||||||
|
} = {}) {
|
||||||
this.reuse = false;
|
this.reuse = false;
|
||||||
this.capturedUrl = matchedUrl;
|
this.capturedUrl = matchedUrl;
|
||||||
this.accumulatedUrl = matchedUrl;
|
this.accumulatedUrl = matchedUrl;
|
||||||
|
@ -53,39 +60,38 @@ export class Instruction {
|
||||||
this.params = params;
|
this.params = params;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasChild(outletName:string):boolean {
|
hasChild(outletName: string): boolean {
|
||||||
return StringMapWrapper.contains(this._children, outletName);
|
return StringMapWrapper.contains(this._children, outletName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the child instruction with the given outlet name
|
* Returns the child instruction with the given outlet name
|
||||||
*/
|
*/
|
||||||
getChild(outletName:string):Instruction {
|
getChild(outletName: string): Instruction {
|
||||||
return StringMapWrapper.get(this._children, outletName);
|
return StringMapWrapper.get(this._children, outletName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (child:Instruction, outletName:string) => {}
|
* (child:Instruction, outletName:string) => {}
|
||||||
*/
|
*/
|
||||||
forEachChild(fn:Function): void {
|
forEachChild(fn: Function): void { StringMapWrapper.forEach(this._children, fn); }
|
||||||
StringMapWrapper.forEach(this._children, fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does a synchronous, breadth-first traversal of the graph of instructions.
|
* Does a synchronous, breadth-first traversal of the graph of instructions.
|
||||||
* Takes a function with signature:
|
* Takes a function with signature:
|
||||||
* (child:Instruction, outletName:string) => {}
|
* (child:Instruction, outletName:string) => {}
|
||||||
*/
|
*/
|
||||||
traverseSync(fn:Function): void {
|
traverseSync(fn: Function): void {
|
||||||
this.forEachChild(fn);
|
this.forEachChild(fn);
|
||||||
this.forEachChild((childInstruction, _) => childInstruction.traverseSync(fn));
|
this.forEachChild((childInstruction, _) => childInstruction.traverseSync(fn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a currently active instruction and sets a reuse flag on each of this instruction's children
|
* Takes a currently active instruction and sets a reuse flag on each of this instruction's
|
||||||
|
* children
|
||||||
*/
|
*/
|
||||||
reuseComponentsFrom(oldInstruction:Instruction): void {
|
reuseComponentsFrom(oldInstruction: Instruction): void {
|
||||||
this.traverseSync((childInstruction, outletName) => {
|
this.traverseSync((childInstruction, outletName) => {
|
||||||
var oldInstructionChild = oldInstruction.getChild(outletName);
|
var oldInstructionChild = oldInstruction.getChild(outletName);
|
||||||
if (shouldReuseComponent(childInstruction, oldInstructionChild)) {
|
if (shouldReuseComponent(childInstruction, oldInstructionChild)) {
|
||||||
|
@ -95,16 +101,16 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapObjAsync(obj:StringMap, fn): Promise {
|
function mapObjAsync(obj: StringMap<string, any>, fn): Promise<List<any>> {
|
||||||
return PromiseWrapper.all(mapObj(obj, fn));
|
return PromiseWrapper.all(mapObj(obj, fn));
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapObj(obj:StringMap, fn: Function):List {
|
function mapObj(obj: StringMap<any, any>, fn: Function): List<any> {
|
||||||
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;
|
|
@ -1,31 +1,24 @@
|
||||||
import {BrowserLocation} from './browser_location';
|
import {BrowserLocation} from './browser_location';
|
||||||
import {StringWrapper} from 'angular2/src/facade/lang';
|
import {StringWrapper} from 'angular2/src/facade/lang';
|
||||||
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
||||||
|
import {Injectable} from 'angular2/di';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
export class Location {
|
export class Location {
|
||||||
_subject:EventEmitter;
|
private _subject: EventEmitter;
|
||||||
_browserLocation:BrowserLocation;
|
private _baseHref: string;
|
||||||
_baseHref:string;
|
|
||||||
constructor(browserLocation:BrowserLocation) {
|
constructor(public _browserLocation: BrowserLocation) {
|
||||||
this._subject = new EventEmitter();
|
this._subject = new EventEmitter();
|
||||||
this._browserLocation = browserLocation;
|
|
||||||
this._baseHref = stripIndexHtml(this._browserLocation.getBaseHref());
|
this._baseHref = stripIndexHtml(this._browserLocation.getBaseHref());
|
||||||
this._browserLocation.onPopState((_) => this._onPopState(_));
|
this._browserLocation.onPopState((_) => this._onPopState(_));
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPopState(_): void {
|
_onPopState(_): void { ObservableWrapper.callNext(this._subject, {'url': this.path()}); }
|
||||||
ObservableWrapper.callNext(this._subject, {
|
|
||||||
'url': this.path()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
path(): string {
|
path(): string { return this.normalize(this._browserLocation.path()); }
|
||||||
return this.normalize(this._browserLocation.path());
|
|
||||||
}
|
|
||||||
|
|
||||||
normalize(url: string): string {
|
normalize(url: string): string { return this._stripBaseHref(stripIndexHtml(url)); }
|
||||||
return this._stripBaseHref(stripIndexHtml(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
normalizeAbsolutely(url: string): string {
|
normalizeAbsolutely(url: string): string {
|
||||||
if (url[0] != '/') {
|
if (url[0] != '/') {
|
||||||
|
@ -48,18 +41,14 @@ export class Location {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
go(url:string): void {
|
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(): void {
|
forward(): void { this._browserLocation.forward(); }
|
||||||
this._browserLocation.forward();
|
|
||||||
}
|
|
||||||
|
|
||||||
back(): void {
|
back(): void { this._browserLocation.back(); }
|
||||||
this._browserLocation.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe(onNext, onThrow = null, onReturn = null): void {
|
subscribe(onNext, onThrow = null, onReturn = null): void {
|
||||||
ObservableWrapper.subscribe(this._subject, onNext, onThrow, onReturn);
|
ObservableWrapper.subscribe(this._subject, onNext, onThrow, onReturn);
|
|
@ -1,35 +1,54 @@
|
||||||
import {RegExp, RegExpWrapper, RegExpMatcherWrapper, StringWrapper, isPresent, isBlank, BaseException, normalizeBlank} from 'angular2/src/facade/lang';
|
import {
|
||||||
import {Map, MapWrapper, StringMap, StringMapWrapper, List, ListWrapper} from 'angular2/src/facade/collection';
|
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 {IMPLEMENTS} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
import {escapeRegex} from './url';
|
import {escapeRegex} from './url';
|
||||||
|
|
||||||
class StaticSegment {
|
// TODO(jeffbcross): implement as interface when ts2dart adds support:
|
||||||
string:string;
|
// https://github.com/angular/ts2dart/issues/173
|
||||||
regex:string;
|
export class Segment {
|
||||||
name:string;
|
name: string;
|
||||||
|
regex: string;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(string:string) {
|
class StaticSegment extends Segment {
|
||||||
this.string = string;
|
regex: string;
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
constructor(public string: string) {
|
||||||
|
super();
|
||||||
this.name = '';
|
this.name = '';
|
||||||
this.regex = escapeRegex(string);
|
this.regex = escapeRegex(string);
|
||||||
}
|
}
|
||||||
|
|
||||||
generate(params): string {
|
generate(params): string { return this.string; }
|
||||||
return this.string;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IMPLEMENTS(Segment)
|
||||||
class DynamicSegment {
|
class DynamicSegment {
|
||||||
name:string;
|
regex: string;
|
||||||
regex:string;
|
constructor(public name: string) { this.regex = "([^/]+)"; }
|
||||||
constructor(name:string) {
|
|
||||||
this.name = name;
|
|
||||||
this.regex = "([^/]+)";
|
|
||||||
}
|
|
||||||
|
|
||||||
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(`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 normalizeBlank(StringMapWrapper.get(params, this.name));
|
return normalizeBlank(StringMapWrapper.get(params, this.name));
|
||||||
}
|
}
|
||||||
|
@ -37,14 +56,10 @@ class DynamicSegment {
|
||||||
|
|
||||||
|
|
||||||
class StarSegment {
|
class StarSegment {
|
||||||
name:string;
|
regex: string;
|
||||||
regex:string;
|
constructor(public name: string) { this.regex = "(.+)"; }
|
||||||
constructor(name:string) {
|
|
||||||
this.name = name;
|
|
||||||
this.regex = "(.+)";
|
|
||||||
}
|
|
||||||
|
|
||||||
generate(params:StringMap<string, string>): string {
|
generate(params: StringMap<string, string>): string {
|
||||||
return normalizeBlank(StringMapWrapper.get(params, this.name));
|
return normalizeBlank(StringMapWrapper.get(params, this.name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +68,7 @@ class StarSegment {
|
||||||
var paramMatcher = RegExpWrapper.create("^:([^\/]+)$");
|
var paramMatcher = RegExpWrapper.create("^:([^\/]+)$");
|
||||||
var wildcardMatcher = RegExpWrapper.create("^\\*([^\/]+)$");
|
var wildcardMatcher = RegExpWrapper.create("^\\*([^\/]+)$");
|
||||||
|
|
||||||
function parsePathString(route:string) {
|
function parsePathString(route: string) {
|
||||||
// normalize route as not starting with a "/". Recognition will
|
// normalize route as not starting with a "/". Recognition will
|
||||||
// also normalize.
|
// also normalize.
|
||||||
if (route[0] === "/") {
|
if (route[0] === "/") {
|
||||||
|
@ -64,19 +79,22 @@ function parsePathString(route:string) {
|
||||||
var results = ListWrapper.create();
|
var results = ListWrapper.create();
|
||||||
var specificity = 0;
|
var specificity = 0;
|
||||||
|
|
||||||
// The "specificity" of a path is used to determine which route is used when multiple routes match a URL.
|
// The "specificity" of a path is used to determine which route is used when multiple routes match
|
||||||
// Static segments (like "/foo") are the most specific, followed by dynamic segments (like "/:id"). Star segments
|
// a URL.
|
||||||
|
// Static segments (like "/foo") are the most specific, followed by dynamic segments (like
|
||||||
|
// "/:id"). Star segments
|
||||||
// add no specificity. Segments at the start of the path are more specific than proceeding ones.
|
// add no specificity. Segments at the start of the path are more specific than proceeding ones.
|
||||||
// The code below uses place values to combine the different types of segments into a single integer that we can
|
// The code below uses place values to combine the different types of segments into a single
|
||||||
// sort later. Each static segment is worth hundreds of points of specificity (10000, 9900, ..., 200), and each
|
// integer that we can
|
||||||
|
// sort later. Each static segment is worth hundreds of points of specificity (10000, 9900, ...,
|
||||||
|
// 200), and each
|
||||||
// dynamic segment is worth single points of specificity (100, 99, ... 2).
|
// dynamic segment is worth single points of specificity (100, 99, ... 2).
|
||||||
if (segments.length > 98) {
|
if (segments.length > 98) {
|
||||||
throw new BaseException(`'${route}' has more than the maximum supported number of segments.`);
|
throw new BaseException(`'${route}' has more than the maximum supported number of segments.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i=0; i<segments.length; i++) {
|
for (var i = 0; i < segments.length; i++) {
|
||||||
var segment = segments[i],
|
var segment = segments[i], match;
|
||||||
match;
|
|
||||||
|
|
||||||
if (isPresent(match = RegExpWrapper.firstMatch(paramMatcher, segment))) {
|
if (isPresent(match = RegExpWrapper.firstMatch(paramMatcher, segment))) {
|
||||||
ListWrapper.push(results, new DynamicSegment(match[1]));
|
ListWrapper.push(results, new DynamicSegment(match[1]));
|
||||||
|
@ -92,22 +110,18 @@ function parsePathString(route:string) {
|
||||||
return {segments: results, specificity};
|
return {segments: results, specificity};
|
||||||
}
|
}
|
||||||
|
|
||||||
function splitBySlash (url:string):List<string> {
|
function splitBySlash(url: string): List<string> {
|
||||||
return url.split('/');
|
return url.split('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// represents something like '/foo/:bar'
|
// represents something like '/foo/:bar'
|
||||||
export class PathRecognizer {
|
export class PathRecognizer {
|
||||||
segments:List;
|
segments: List<Segment>;
|
||||||
regex:RegExp;
|
regex: RegExp;
|
||||||
handler:any;
|
specificity: number;
|
||||||
specificity:number;
|
|
||||||
path:string;
|
|
||||||
|
|
||||||
constructor(path:string, handler:any) {
|
constructor(public path: string, public handler: any) {
|
||||||
this.path = path;
|
|
||||||
this.handler = handler;
|
|
||||||
this.segments = [];
|
this.segments = [];
|
||||||
|
|
||||||
// TODO: use destructuring assignment
|
// TODO: use destructuring assignment
|
||||||
|
@ -117,19 +131,17 @@ export class PathRecognizer {
|
||||||
var segments = parsed['segments'];
|
var segments = parsed['segments'];
|
||||||
var regexString = '^';
|
var regexString = '^';
|
||||||
|
|
||||||
ListWrapper.forEach(segments, (segment) => {
|
ListWrapper.forEach(segments, (segment) => { regexString += '/' + segment.regex; });
|
||||||
regexString += '/' + segment.regex;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.regex = RegExpWrapper.create(regexString);
|
this.regex = RegExpWrapper.create(regexString);
|
||||||
this.segments = segments;
|
this.segments = segments;
|
||||||
this.specificity = specificity;
|
this.specificity = specificity;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseParams(url:string):StringMap<string, string> {
|
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++) {
|
||||||
var segment = this.segments[i];
|
var segment = this.segments[i];
|
||||||
var match = RegExpWrapper.firstMatch(RegExpWrapper.create('/' + segment.regex), urlPart);
|
var match = RegExpWrapper.firstMatch(RegExpWrapper.create('/' + segment.regex), urlPart);
|
||||||
urlPart = StringWrapper.substring(urlPart, match[0].length);
|
urlPart = StringWrapper.substring(urlPart, match[0].length);
|
||||||
|
@ -141,8 +153,8 @@ export class PathRecognizer {
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
generate(params:StringMap<string, string>):string {
|
generate(params: StringMap<string, string>): string {
|
||||||
return ListWrapper.join(ListWrapper.map(this.segments, (segment) =>
|
return ListWrapper.join(
|
||||||
'/' + segment.generate(params)), '');
|
ListWrapper.map(this.segments, (segment) => '/' + segment.generate(params)), '');
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,19 +7,14 @@ import {Instruction} from './instruction';
|
||||||
* "Steps" are conceptually similar to "middleware"
|
* "Steps" are conceptually similar to "middleware"
|
||||||
*/
|
*/
|
||||||
export class Pipeline {
|
export class Pipeline {
|
||||||
steps:List<Function>;
|
steps: List<Function>;
|
||||||
|
|
||||||
constructor() {
|
constructor() { this.steps = [instruction => instruction.router.activateOutlets(instruction)]; }
|
||||||
this.steps = [
|
|
||||||
instruction => instruction.router.activateOutlets(instruction)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
process(instruction:Instruction):Promise {
|
process(instruction: Instruction): Promise<any> {
|
||||||
var steps = this.steps,
|
var steps = this.steps, currentStep = 0;
|
||||||
currentStep = 0;
|
|
||||||
|
|
||||||
function processOne(result:any = true):Promise {
|
function processOne(result: any = true): Promise<any> {
|
||||||
if (currentStep >= steps.length) {
|
if (currentStep >= steps.length) {
|
||||||
return PromiseWrapper.resolve(result);
|
return PromiseWrapper.resolve(result);
|
||||||
}
|
}
|
|
@ -1,3 +0,0 @@
|
||||||
library angular2.router.route_config_annotations;
|
|
||||||
|
|
||||||
export './route_config_impl.dart';
|
|
|
@ -1 +0,0 @@
|
||||||
export {RouteConfig as RouteConfigAnnotation} from './route_config_impl';
|
|
|
@ -1,3 +1,3 @@
|
||||||
library angular2.router.route_config_decorator;
|
library angular2.router.route_config_decorator;
|
||||||
|
|
||||||
/** This file is intentionally empty, as Dart does not have decorators */
|
export './route_config_impl.dart';
|
||||||
|
|
|
@ -2,4 +2,3 @@ import {RouteConfig as RouteConfigAnnotation} from './route_config_impl';
|
||||||
import {makeDecorator} from 'angular2/src/util/decorators';
|
import {makeDecorator} from 'angular2/src/util/decorators';
|
||||||
|
|
||||||
export var RouteConfig = makeDecorator(RouteConfigAnnotation);
|
export var RouteConfig = makeDecorator(RouteConfigAnnotation);
|
||||||
|
|
|
@ -9,11 +9,7 @@ import {List, Map} from 'angular2/src/facade/collection';
|
||||||
* - `component`, `components`, `redirectTo` (requires exactly one of these)
|
* - `component`, `components`, `redirectTo` (requires exactly one of these)
|
||||||
* - `as` (optional)
|
* - `as` (optional)
|
||||||
*/
|
*/
|
||||||
|
@CONST()
|
||||||
export class RouteConfig {
|
export class RouteConfig {
|
||||||
configs:List<Map>;
|
constructor(public configs: List<Map<any, any>>) {}
|
||||||
|
|
||||||
@CONST()
|
|
||||||
constructor(configs:List<Map>) {
|
|
||||||
this.configs = configs;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,16 +1,30 @@
|
||||||
import {RegExp, RegExpWrapper, StringWrapper, isPresent, BaseException} from 'angular2/src/facade/lang';
|
import {
|
||||||
import {Map, MapWrapper, List, ListWrapper, StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
|
RegExp,
|
||||||
|
RegExpWrapper,
|
||||||
|
StringWrapper,
|
||||||
|
isPresent,
|
||||||
|
BaseException
|
||||||
|
} from 'angular2/src/facade/lang';
|
||||||
|
import {
|
||||||
|
Map,
|
||||||
|
MapWrapper,
|
||||||
|
List,
|
||||||
|
ListWrapper,
|
||||||
|
StringMap,
|
||||||
|
StringMapWrapper
|
||||||
|
} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {PathRecognizer} from './path_recognizer';
|
import {PathRecognizer} from './path_recognizer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `RouteRecognizer` is responsible for recognizing routes for a single component.
|
* `RouteRecognizer` is responsible for recognizing routes for a single component.
|
||||||
* It is consumed by `RouteRegistry`, which knows how to recognize an entire hierarchy of components.
|
* It is consumed by `RouteRegistry`, which knows how to recognize an entire hierarchy of
|
||||||
|
* components.
|
||||||
*/
|
*/
|
||||||
export class RouteRecognizer {
|
export class RouteRecognizer {
|
||||||
names:Map<string, PathRecognizer>;
|
names: Map<string, PathRecognizer>;
|
||||||
redirects:Map<string, string>;
|
redirects: Map<string, string>;
|
||||||
matchers:Map<RegExp, PathRecognizer>;
|
matchers: Map<RegExp, PathRecognizer>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.names = MapWrapper.create();
|
this.names = MapWrapper.create();
|
||||||
|
@ -18,15 +32,14 @@ export class RouteRecognizer {
|
||||||
this.redirects = MapWrapper.create();
|
this.redirects = MapWrapper.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
addRedirect(path:string, target:string): void {
|
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): void {
|
addConfig(path: string, handler: any, alias: string = null): void {
|
||||||
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()) {
|
||||||
throw new BaseException(`Configuration '${path}' conflicts with existing route '${matcher.path}'`);
|
throw new BaseException(
|
||||||
|
`Configuration '${path}' conflicts with existing route '${matcher.path}'`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
MapWrapper.set(this.matchers, recognizer.regex, recognizer);
|
MapWrapper.set(this.matchers, recognizer.regex, recognizer);
|
||||||
|
@ -40,11 +53,11 @@ export class RouteRecognizer {
|
||||||
* Given a URL, returns a list of `RouteMatch`es, which are partial recognitions for some route.
|
* Given a URL, returns a list of `RouteMatch`es, which are partial recognitions for some route.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
recognize(url:string):List<RouteMatch> {
|
recognize(url: string): List<RouteMatch> {
|
||||||
var solutions = ListWrapper.create();
|
var solutions = ListWrapper.create();
|
||||||
|
|
||||||
MapWrapper.forEach(this.redirects, (target, path) => {
|
MapWrapper.forEach(this.redirects, (target, path) => {
|
||||||
//TODO: "/" redirect case
|
// TODO: "/" redirect case
|
||||||
if (StringWrapper.startsWith(url, path)) {
|
if (StringWrapper.startsWith(url, path)) {
|
||||||
url = target + StringWrapper.substring(url, path.length);
|
url = target + StringWrapper.substring(url, path.length);
|
||||||
}
|
}
|
||||||
|
@ -53,7 +66,7 @@ export class RouteRecognizer {
|
||||||
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
|
// TODO(btford): determine a good generic way to deal with terminal matches
|
||||||
var matchedUrl = '/';
|
var matchedUrl = '/';
|
||||||
var unmatchedUrl = '';
|
var unmatchedUrl = '';
|
||||||
if (url != '/') {
|
if (url != '/') {
|
||||||
|
@ -61,37 +74,39 @@ export class RouteRecognizer {
|
||||||
unmatchedUrl = StringWrapper.substring(url, match[0].length);
|
unmatchedUrl = StringWrapper.substring(url, match[0].length);
|
||||||
}
|
}
|
||||||
ListWrapper.push(solutions, new RouteMatch({
|
ListWrapper.push(solutions, new RouteMatch({
|
||||||
specificity: pathRecognizer.specificity,
|
specificity: pathRecognizer.specificity,
|
||||||
handler: pathRecognizer.handler,
|
handler: pathRecognizer.handler,
|
||||||
params: pathRecognizer.parseParams(url),
|
params: pathRecognizer.parseParams(url),
|
||||||
matchedUrl: matchedUrl,
|
matchedUrl: matchedUrl,
|
||||||
unmatchedUrl: unmatchedUrl
|
unmatchedUrl: unmatchedUrl
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return solutions;
|
return solutions;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasRoute(name:string): boolean {
|
hasRoute(name: string): boolean { return MapWrapper.contains(this.names, name); }
|
||||||
return MapWrapper.contains(this.names, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
generate(name:string, params:any): string {
|
generate(name: string, params: any): string {
|
||||||
var pathRecognizer = MapWrapper.get(this.names, name);
|
var pathRecognizer = MapWrapper.get(this.names, name);
|
||||||
return isPresent(pathRecognizer) ? pathRecognizer.generate(params) : null;
|
return isPresent(pathRecognizer) ? pathRecognizer.generate(params) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RouteMatch {
|
export class RouteMatch {
|
||||||
specificity:number;
|
specificity: number;
|
||||||
handler:StringMap<string, any>;
|
handler: StringMap<string, any>;
|
||||||
params:StringMap<string, string>;
|
params: StringMap<string, string>;
|
||||||
matchedUrl:string;
|
matchedUrl: string;
|
||||||
unmatchedUrl:string;
|
unmatchedUrl: string;
|
||||||
constructor({specificity, handler, params, matchedUrl, unmatchedUrl}:
|
constructor({specificity, handler, params, matchedUrl, unmatchedUrl}: {
|
||||||
{specificity:number, handler:StringMap, params:StringMap, matchedUrl:string, unmatchedUrl:string} = {}) {
|
specificity?: number,
|
||||||
|
handler?: StringMap<string, any>,
|
||||||
|
params?: StringMap<string, string>,
|
||||||
|
matchedUrl?: string,
|
||||||
|
unmatchedUrl?: string
|
||||||
|
} = {}) {
|
||||||
this.specificity = specificity;
|
this.specificity = specificity;
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.params = params;
|
this.params = params;
|
|
@ -1,25 +1,31 @@
|
||||||
import {RouteRecognizer, RouteMatch} from './route_recognizer';
|
import {RouteRecognizer, RouteMatch} from './route_recognizer';
|
||||||
import {Instruction, noopInstruction} from './instruction';
|
import {Instruction} from './instruction';
|
||||||
import {List, ListWrapper, Map, MapWrapper, StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
|
import {
|
||||||
|
List,
|
||||||
|
ListWrapper,
|
||||||
|
Map,
|
||||||
|
MapWrapper,
|
||||||
|
StringMap,
|
||||||
|
StringMapWrapper
|
||||||
|
} from 'angular2/src/facade/collection';
|
||||||
import {isPresent, isBlank, isType, StringWrapper, BaseException} from 'angular2/src/facade/lang';
|
import {isPresent, isBlank, isType, StringWrapper, BaseException} from 'angular2/src/facade/lang';
|
||||||
import {RouteConfig} from './route_config_impl';
|
import {RouteConfig} from './route_config_impl';
|
||||||
import {reflector} from 'angular2/src/reflection/reflection';
|
import {reflector} from 'angular2/src/reflection/reflection';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The RouteRegistry holds route configurations for each component in an Angular app.
|
* The RouteRegistry holds route configurations for each component in an Angular app.
|
||||||
* It is responsible for creating Instructions from URLs, and generating URLs based on route and parameters.
|
* It is responsible for creating Instructions from URLs, and generating URLs based on route and
|
||||||
|
* parameters.
|
||||||
*/
|
*/
|
||||||
export class RouteRegistry {
|
export class RouteRegistry {
|
||||||
_rules:Map<any, RouteRecognizer>;
|
_rules: Map<any, RouteRecognizer>;
|
||||||
|
|
||||||
constructor() {
|
constructor() { this._rules = MapWrapper.create(); }
|
||||||
this._rules = MapWrapper.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
config(parentComponent, config:StringMap<string, any>): void {
|
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"');
|
||||||
}
|
}
|
||||||
|
@ -27,10 +33,11 @@ export class RouteRegistry {
|
||||||
if (!StringMapWrapper.contains(config, 'component') &&
|
if (!StringMapWrapper.contains(config, 'component') &&
|
||||||
!StringMapWrapper.contains(config, 'components') &&
|
!StringMapWrapper.contains(config, 'components') &&
|
||||||
!StringMapWrapper.contains(config, 'redirectTo')) {
|
!StringMapWrapper.contains(config, 'redirectTo')) {
|
||||||
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 = MapWrapper.get(this._rules, parentComponent);
|
var recognizer: RouteRecognizer = MapWrapper.get(this._rules, parentComponent);
|
||||||
|
|
||||||
if (isBlank(recognizer)) {
|
if (isBlank(recognizer)) {
|
||||||
recognizer = new RouteRecognizer();
|
recognizer = new RouteRecognizer();
|
||||||
|
@ -65,7 +72,7 @@ export class RouteRegistry {
|
||||||
}
|
}
|
||||||
var annotations = reflector.annotations(component);
|
var annotations = reflector.annotations(component);
|
||||||
if (isPresent(annotations)) {
|
if (isPresent(annotations)) {
|
||||||
for (var i=0; i<annotations.length; i++) {
|
for (var i = 0; i < annotations.length; i++) {
|
||||||
var annotation = annotations[i];
|
var annotation = annotations[i];
|
||||||
|
|
||||||
if (annotation instanceof RouteConfig) {
|
if (annotation instanceof RouteConfig) {
|
||||||
|
@ -80,7 +87,7 @@ export class RouteRegistry {
|
||||||
* Given a URL and a parent component, return the most specific instruction for navigating
|
* Given a URL and a parent component, return the most specific instruction for navigating
|
||||||
* the application into the state specified by the
|
* the application into the state specified by the
|
||||||
*/
|
*/
|
||||||
recognize(url:string, parentComponent): Instruction {
|
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;
|
||||||
|
@ -93,16 +100,15 @@ export class RouteRegistry {
|
||||||
var fullSolutions = ListWrapper.create();
|
var fullSolutions = ListWrapper.create();
|
||||||
|
|
||||||
for (var i = 0; i < possibleMatches.length; i++) {
|
for (var i = 0; i < possibleMatches.length; i++) {
|
||||||
var candidate : RouteMatch = possibleMatches[i];
|
var candidate: RouteMatch = possibleMatches[i];
|
||||||
|
|
||||||
// if the candidate captures all of the URL, add it to our list of solutions
|
// if the candidate captures all of the URL, add it to our list of solutions
|
||||||
if (candidate.unmatchedUrl.length == 0) {
|
if (candidate.unmatchedUrl.length == 0) {
|
||||||
ListWrapper.push(fullSolutions, routeMatchToInstruction(candidate, parentComponent));
|
ListWrapper.push(fullSolutions, routeMatchToInstruction(candidate, parentComponent));
|
||||||
} else {
|
} else {
|
||||||
|
// otherwise, recursively match the remaining part of the URL against the component's
|
||||||
// otherwise, recursively match the remaining part of the URL against the component's children
|
// children
|
||||||
var children = StringMapWrapper.create(),
|
var children = StringMapWrapper.create(), allChildrenMatch = true,
|
||||||
allChildrenMatch = true,
|
|
||||||
components = StringMapWrapper.get(candidate.handler, 'components');
|
components = StringMapWrapper.get(candidate.handler, 'components');
|
||||||
|
|
||||||
var componentNames = StringMapWrapper.keys(components);
|
var componentNames = StringMapWrapper.keys(components);
|
||||||
|
@ -122,11 +128,11 @@ export class RouteRegistry {
|
||||||
|
|
||||||
if (allChildrenMatch) {
|
if (allChildrenMatch) {
|
||||||
ListWrapper.push(fullSolutions, new Instruction({
|
ListWrapper.push(fullSolutions, new Instruction({
|
||||||
component: parentComponent,
|
component: parentComponent,
|
||||||
children: children,
|
children: children,
|
||||||
matchedUrl: candidate.matchedUrl,
|
matchedUrl: candidate.matchedUrl,
|
||||||
parentSpecificity: candidate.specificity
|
parentSpecificity: candidate.specificity
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,22 +152,19 @@ export class RouteRegistry {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
generate(name:string, params:StringMap<string, string>, hostComponent): string {
|
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);
|
||||||
return isPresent(componentRecognizer) ? componentRecognizer.generate(name, params) : null;
|
return isPresent(componentRecognizer) ? componentRecognizer.generate(name, params) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function routeMatchToInstruction(routeMatch:RouteMatch, parentComponent): Instruction {
|
function routeMatchToInstruction(routeMatch: RouteMatch, parentComponent): Instruction {
|
||||||
var children = StringMapWrapper.create();
|
var children = StringMapWrapper.create();
|
||||||
var components = StringMapWrapper.get(routeMatch.handler, 'components');
|
var components = StringMapWrapper.get(routeMatch.handler, 'components');
|
||||||
StringMapWrapper.forEach(components, (component, outletName) => {
|
StringMapWrapper.forEach(components, (component, outletName) => {
|
||||||
children[outletName] = new Instruction({
|
children[outletName] =
|
||||||
component: component,
|
new Instruction({component: component, params: routeMatch.params, parentSpecificity: 0});
|
||||||
params: routeMatch.params,
|
|
||||||
parentSpecificity: 0
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
return new Instruction({
|
return new Instruction({
|
||||||
component: parentComponent,
|
component: parentComponent,
|
||||||
|
@ -181,15 +184,11 @@ function routeMatchToInstruction(routeMatch:RouteMatch, parentComponent): Instru
|
||||||
* If the config object does not contain a `component` key, the original
|
* If the config object does not contain a `component` key, the original
|
||||||
* config object is returned.
|
* config object is returned.
|
||||||
*/
|
*/
|
||||||
function normalizeConfig(config:StringMap<string, any>): StringMap<string, any> {
|
function normalizeConfig(config: StringMap<string, any>): StringMap<string, any> {
|
||||||
if (!StringMapWrapper.contains(config, 'component')) {
|
if (!StringMapWrapper.contains(config, 'component')) {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
var newConfig = {
|
var newConfig = {'components': {'default': config['component']}};
|
||||||
'components': {
|
|
||||||
'default': config['component']
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
StringMapWrapper.forEach(config, (value, key) => {
|
StringMapWrapper.forEach(config, (value, key) => {
|
||||||
if (key != 'component' && key != 'components') {
|
if (key != 'component' && key != 'components') {
|
|
@ -21,51 +21,44 @@ import {Location} from './location';
|
||||||
* The router holds reference to a number of "outlets." An outlet is a placeholder that the
|
* The router holds reference to a number of "outlets." An outlet is a placeholder that the
|
||||||
* router dynamically fills in depending on the current URL.
|
* router dynamically fills in depending on the current URL.
|
||||||
*
|
*
|
||||||
* When the router navigates from a URL, it must first recognizes it and serialize it into an `Instruction`.
|
* When the router navigates from a URL, it must first recognizes it and serialize it into an
|
||||||
|
* `Instruction`.
|
||||||
* The router uses the `RouteRegistry` to get an `Instruction`.
|
* The router uses the `RouteRegistry` to get an `Instruction`.
|
||||||
*
|
*
|
||||||
* @exportedAs angular2/router
|
* @exportedAs angular2/router
|
||||||
*/
|
*/
|
||||||
export class Router {
|
export class Router {
|
||||||
hostComponent:any;
|
navigating: boolean;
|
||||||
parent:Router;
|
|
||||||
navigating:boolean;
|
|
||||||
lastNavigationAttempt: string;
|
lastNavigationAttempt: string;
|
||||||
previousUrl:string;
|
previousUrl: string;
|
||||||
|
|
||||||
_currentInstruction:Instruction;
|
private _currentInstruction: Instruction;
|
||||||
|
private _outlets: Map<any, RouterOutlet>;
|
||||||
_pipeline:Pipeline;
|
private _subject: EventEmitter;
|
||||||
_registry:RouteRegistry;
|
// todo(jeffbcross): rename _registry to registry since it is accessed from subclasses
|
||||||
_outlets:Map<any, RouterOutlet>;
|
// todo(jeffbcross): rename _pipeline to pipeline since it is accessed from subclasses
|
||||||
_subject:EventEmitter;
|
constructor(public _registry: RouteRegistry, public _pipeline: Pipeline, public parent: Router,
|
||||||
|
public hostComponent: any) {
|
||||||
|
|
||||||
constructor(registry:RouteRegistry, pipeline:Pipeline, parent:Router, hostComponent:any) {
|
|
||||||
this.hostComponent = hostComponent;
|
|
||||||
this.navigating = false;
|
this.navigating = false;
|
||||||
this.parent = parent;
|
|
||||||
this.previousUrl = null;
|
this.previousUrl = null;
|
||||||
this._outlets = MapWrapper.create();
|
this._outlets = MapWrapper.create();
|
||||||
this._registry = registry;
|
|
||||||
this._pipeline = pipeline;
|
|
||||||
this._subject = new EventEmitter();
|
this._subject = new EventEmitter();
|
||||||
this._currentInstruction = null;
|
this._currentInstruction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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(hostComponent:any): Router {
|
childRouter(hostComponent: any): Router { return new ChildRouter(this, hostComponent); }
|
||||||
return new ChildRouter(this, hostComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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: string = 'default'): Promise {
|
registerOutlet(outlet: RouterOutlet, name: string = 'default'): Promise<boolean> {
|
||||||
MapWrapper.set(this._outlets, name, outlet);
|
MapWrapper.set(this._outlets, name, outlet);
|
||||||
if (isPresent(this._currentInstruction)) {
|
if (isPresent(this._currentInstruction)) {
|
||||||
var childInstruction = this._currentInstruction.getChild(name);
|
var childInstruction = this._currentInstruction.getChild(name);
|
||||||
|
@ -94,11 +87,10 @@ export class Router {
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
config(config:any): Promise {
|
config(config: any): Promise<any> {
|
||||||
if (config instanceof List) {
|
if (config instanceof List) {
|
||||||
config.forEach((configObject) => {
|
config.forEach(
|
||||||
this._registry.config(this.hostComponent, configObject);
|
(configObject) => { this._registry.config(this.hostComponent, configObject); });
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this._registry.config(this.hostComponent, config);
|
this._registry.config(this.hostComponent, config);
|
||||||
}
|
}
|
||||||
|
@ -112,7 +104,7 @@ export class Router {
|
||||||
* If the given URL begins with a `/`, router will navigate absolutely.
|
* If the given URL begins with a `/`, router will navigate absolutely.
|
||||||
* If the given URL does not begin with `/`, the router will navigate relative to this component.
|
* If the given URL does not begin with `/`, the router will navigate relative to this component.
|
||||||
*/
|
*/
|
||||||
navigate(url:string):Promise {
|
navigate(url: string): Promise<any> {
|
||||||
if (this.navigating) {
|
if (this.navigating) {
|
||||||
return PromiseWrapper.resolve(true);
|
return PromiseWrapper.resolve(true);
|
||||||
}
|
}
|
||||||
|
@ -132,37 +124,31 @@ export class Router {
|
||||||
this._startNavigating();
|
this._startNavigating();
|
||||||
|
|
||||||
var result = this.commit(matchedInstruction)
|
var result = this.commit(matchedInstruction)
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
ObservableWrapper.callNext(this._subject, matchedInstruction.accumulatedUrl);
|
ObservableWrapper.callNext(this._subject, matchedInstruction.accumulatedUrl);
|
||||||
this._finishNavigating();
|
this._finishNavigating();
|
||||||
});
|
});
|
||||||
|
|
||||||
PromiseWrapper.catchError(result, (_) => this._finishNavigating());
|
PromiseWrapper.catchError(result, (_) => this._finishNavigating());
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
_startNavigating(): void {
|
_startNavigating(): void { this.navigating = true; }
|
||||||
this.navigating = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_finishNavigating(): void {
|
_finishNavigating(): void { this.navigating = false; }
|
||||||
this.navigating = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribe to URL updates from the router
|
* Subscribe to URL updates from the router
|
||||||
*/
|
*/
|
||||||
subscribe(onNext): void {
|
subscribe(onNext): void { ObservableWrapper.subscribe(this._subject, onNext); }
|
||||||
ObservableWrapper.subscribe(this._subject, onNext);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
commit(instruction:Instruction):Promise {
|
commit(instruction: Instruction): Promise<List<any>> {
|
||||||
this._currentInstruction = instruction;
|
this._currentInstruction = instruction;
|
||||||
|
|
||||||
// collect all outlets that do not have a corresponding child instruction
|
// collect all outlets that do not have a corresponding child instruction
|
||||||
|
@ -184,37 +170,32 @@ export class Router {
|
||||||
* Recursively remove all components contained by this router's outlets.
|
* Recursively remove all components contained by this router's outlets.
|
||||||
* Calls deactivate hooks on all descendant components
|
* Calls deactivate hooks on all descendant components
|
||||||
*/
|
*/
|
||||||
deactivate():Promise {
|
deactivate(): Promise<any> { return this._eachOutletAsync((outlet) => outlet.deactivate); }
|
||||||
return this._eachOutletAsync((outlet) => outlet.deactivate);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively activate.
|
* Recursively activate.
|
||||||
* Calls the "activate" hook on descendant components.
|
* Calls the "activate" hook on descendant components.
|
||||||
*/
|
*/
|
||||||
activate(instruction:Instruction):Promise {
|
activate(instruction: Instruction): Promise<any> {
|
||||||
return this._eachOutletAsync((outlet, name) => outlet.activate(instruction.getChild(name)));
|
return this._eachOutletAsync((outlet, name) => outlet.activate(instruction.getChild(name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_eachOutletAsync(fn):Promise {
|
_eachOutletAsync(fn): Promise<any> { return mapObjAsync(this._outlets, fn); }
|
||||||
return mapObjAsync(this._outlets, fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a URL, returns an instruction representing the component graph
|
* Given a URL, returns an instruction representing the component graph
|
||||||
*/
|
*/
|
||||||
recognize(url:string): Instruction {
|
recognize(url: string): Instruction { return this._registry.recognize(url, this.hostComponent); }
|
||||||
return this._registry.recognize(url, this.hostComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigates to either the last URL successfully navigated to, or the last URL requested if the router has yet to successfully navigate.
|
* Navigates to either the last URL successfully navigated to, or the last URL requested if the
|
||||||
|
* router has yet to successfully navigate.
|
||||||
*/
|
*/
|
||||||
renavigate():Promise {
|
renavigate(): Promise<any> {
|
||||||
var destination = isBlank(this.previousUrl) ? this.lastNavigationAttempt : this.previousUrl;
|
var destination = isBlank(this.previousUrl) ? this.lastNavigationAttempt : this.previousUrl;
|
||||||
if (this.navigating || isBlank(destination)) {
|
if (this.navigating || isBlank(destination)) {
|
||||||
return PromiseWrapper.resolve(false);
|
return PromiseWrapper.resolve(false);
|
||||||
|
@ -224,17 +205,19 @@ 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:StringMap<string, string>): string {
|
generate(name: string, params: StringMap<string, string>): string {
|
||||||
return this._registry.generate(name, params, this.hostComponent);
|
return this._registry.generate(name, params, this.hostComponent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RootRouter extends Router {
|
export class RootRouter extends Router {
|
||||||
_location:Location;
|
_location: Location;
|
||||||
|
|
||||||
constructor(registry:RouteRegistry, pipeline:Pipeline, location:Location, hostComponent:Type) {
|
constructor(registry: RouteRegistry, pipeline: Pipeline, location: Location,
|
||||||
|
hostComponent: Type) {
|
||||||
super(registry, pipeline, null, hostComponent);
|
super(registry, pipeline, null, hostComponent);
|
||||||
this._location = location;
|
this._location = location;
|
||||||
this._location.subscribe((change) => this.navigate(change['url']));
|
this._location.subscribe((change) => this.navigate(change['url']));
|
||||||
|
@ -242,25 +225,24 @@ export class RootRouter extends Router {
|
||||||
this.navigate(location.path());
|
this.navigate(location.path());
|
||||||
}
|
}
|
||||||
|
|
||||||
commit(instruction):Promise {
|
commit(instruction): Promise<any> {
|
||||||
return super.commit(instruction).then((_) => {
|
return super.commit(instruction)
|
||||||
this._location.go(instruction.accumulatedUrl);
|
.then((_) => { this._location.go(instruction.accumulatedUrl); });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChildRouter extends Router {
|
class ChildRouter extends Router {
|
||||||
constructor(parent:Router, hostComponent) {
|
constructor(parent: Router, hostComponent) {
|
||||||
super(parent._registry, parent._pipeline, parent, hostComponent);
|
super(parent._registry, parent._pipeline, parent, hostComponent);
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapObjAsync(obj:Map, fn): Promise {
|
function mapObjAsync(obj: Map<any, any>, fn: Function): Promise<any> {
|
||||||
return PromiseWrapper.all(mapObj(obj, fn));
|
return PromiseWrapper.all(mapObj(obj, fn));
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapObj(obj:Map, fn):List {
|
function mapObj(obj: Map<any, any>, fn: Function): List<any> {
|
||||||
var result = ListWrapper.create();
|
var result = ListWrapper.create();
|
||||||
MapWrapper.forEach(obj, (value, key) => ListWrapper.push(result, fn(value, key)));
|
MapWrapper.forEach(obj, (value, key) => ListWrapper.push(result, fn(value, key)));
|
||||||
return result;
|
return result;
|
|
@ -1,4 +1,5 @@
|
||||||
import {Directive, onAllChangesDone} from 'angular2/src/core/annotations_impl/annotations';
|
import {onAllChangesDone} from 'angular2/src/core/annotations/annotations';
|
||||||
|
import {Directive} from 'angular2/src/core/annotations/decorators';
|
||||||
import {ElementRef} from 'angular2/core';
|
import {ElementRef} from 'angular2/core';
|
||||||
import {StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
|
import {StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
|
@ -31,27 +32,21 @@ import {Location} from './location';
|
||||||
*/
|
*/
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[router-link]',
|
selector: '[router-link]',
|
||||||
properties: [
|
properties: ['route: routerLink', 'params: routerParams'],
|
||||||
'route: routerLink',
|
|
||||||
'params: routerParams'
|
|
||||||
],
|
|
||||||
lifecycle: [onAllChangesDone]
|
lifecycle: [onAllChangesDone]
|
||||||
})
|
})
|
||||||
export class RouterLink {
|
export class RouterLink {
|
||||||
_domEl;
|
private _domEl;
|
||||||
_route:string;
|
private _route: string;
|
||||||
_params:StringMap<string, string>;
|
private _params: StringMap<string, string>;
|
||||||
_router:Router;
|
|
||||||
_location:Location;
|
|
||||||
// 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(elementRef:ElementRef, router:Router, location:Location) {
|
constructor(elementRef: ElementRef, private _router: Router, private _location: Location) {
|
||||||
this._domEl = elementRef.domElement;
|
this._domEl = elementRef.domElement;
|
||||||
this._router = router;
|
|
||||||
this._location = location;
|
|
||||||
this._params = StringMapWrapper.create();
|
this._params = StringMapWrapper.create();
|
||||||
DOM.on(this._domEl, 'click', (evt) => {
|
DOM.on(this._domEl, 'click', (evt) => {
|
||||||
DOM.preventDefault(evt);
|
DOM.preventDefault(evt);
|
||||||
|
@ -59,13 +54,9 @@ export class RouterLink {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
set route(changes: string) {
|
set route(changes: string) { this._route = changes; }
|
||||||
this._route = changes;
|
|
||||||
}
|
|
||||||
|
|
||||||
set params(changes: StringMap) {
|
set params(changes: StringMap<string, string>) { this._params = changes; }
|
||||||
this._params = changes;
|
|
||||||
}
|
|
||||||
|
|
||||||
onAllChangesDone(): void {
|
onAllChangesDone(): void {
|
||||||
if (isPresent(this._route) && isPresent(this._params)) {
|
if (isPresent(this._route) && isPresent(this._params)) {
|
|
@ -1,8 +1,7 @@
|
||||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
import {Directive} from 'angular2/src/core/annotations_impl/annotations';
|
import {Directive, Attribute} from 'angular2/src/core/annotations/decorators';
|
||||||
import {Attribute} from 'angular2/src/core/annotations_impl/di';
|
|
||||||
import {DynamicComponentLoader, ComponentRef, ElementRef} from 'angular2/core';
|
import {DynamicComponentLoader, ComponentRef, ElementRef} from 'angular2/core';
|
||||||
import {Injector, bind} from 'angular2/di';
|
import {Injector, bind} from 'angular2/di';
|
||||||
|
|
||||||
|
@ -31,22 +30,19 @@ import {Instruction, RouteParams} from './instruction'
|
||||||
selector: 'router-outlet'
|
selector: 'router-outlet'
|
||||||
})
|
})
|
||||||
export class RouterOutlet {
|
export class RouterOutlet {
|
||||||
_injector:Injector;
|
private _childRouter: routerMod.Router;
|
||||||
_parentRouter:routerMod.Router;
|
private _componentRef: ComponentRef;
|
||||||
_childRouter:routerMod.Router;
|
private _elementRef: ElementRef;
|
||||||
_loader:DynamicComponentLoader;
|
private _currentInstruction: Instruction;
|
||||||
_componentRef:ComponentRef;
|
|
||||||
_elementRef:ElementRef;
|
|
||||||
_currentInstruction:Instruction;
|
|
||||||
|
|
||||||
constructor(elementRef:ElementRef, loader:DynamicComponentLoader, router:routerMod.Router, injector:Injector, @Attribute('name') nameAttr:String) {
|
constructor(elementRef: ElementRef, private _loader: DynamicComponentLoader,
|
||||||
|
private _parentRouter: routerMod.Router, private _injector: Injector,
|
||||||
|
@Attribute('name') nameAttr: string) {
|
||||||
if (isBlank(nameAttr)) {
|
if (isBlank(nameAttr)) {
|
||||||
nameAttr = 'default';
|
nameAttr = 'default';
|
||||||
}
|
}
|
||||||
this._loader = loader;
|
|
||||||
this._parentRouter = router;
|
|
||||||
this._elementRef = elementRef;
|
this._elementRef = elementRef;
|
||||||
this._injector = injector;
|
|
||||||
|
|
||||||
this._childRouter = null;
|
this._childRouter = null;
|
||||||
this._componentRef = null;
|
this._componentRef = null;
|
||||||
|
@ -57,17 +53,20 @@ export class RouterOutlet {
|
||||||
/**
|
/**
|
||||||
* Given an instruction, update the contents of this viewport.
|
* Given an instruction, update the contents of this viewport.
|
||||||
*/
|
*/
|
||||||
activate(instruction:Instruction): Promise {
|
activate(instruction: Instruction): Promise<any> {
|
||||||
// if we're able to reuse the component, we just have to pass along the instruction to the component's router
|
// if we're able to reuse the component, we just have to pass along the instruction to the
|
||||||
|
// component's router
|
||||||
// so it can propagate changes to its children
|
// so it can propagate changes to its children
|
||||||
if ((instruction == this._currentInstruction) || instruction.reuse && isPresent(this._childRouter)) {
|
if ((instruction == this._currentInstruction) ||
|
||||||
|
instruction.reuse && isPresent(this._childRouter)) {
|
||||||
return this._childRouter.commit(instruction);
|
return this._childRouter.commit(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._currentInstruction = instruction;
|
this._currentInstruction = instruction;
|
||||||
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).toValue(new RouteParams(instruction.params)),
|
bind(RouteParams)
|
||||||
|
.toValue(new RouteParams(instruction.params)),
|
||||||
bind(routerMod.Router).toValue(this._childRouter)
|
bind(routerMod.Router).toValue(this._childRouter)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -75,18 +74,21 @@ export class RouterOutlet {
|
||||||
this._componentRef.dispose();
|
this._componentRef.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._loader.loadNextToExistingLocation(instruction.component, this._elementRef, outletInjector).then((componentRef) => {
|
return this._loader.loadNextToExistingLocation(instruction.component, this._elementRef,
|
||||||
this._componentRef = componentRef;
|
outletInjector)
|
||||||
return this._childRouter.commit(instruction);
|
.then((componentRef) => {
|
||||||
});
|
this._componentRef = componentRef;
|
||||||
|
return this._childRouter.commit(instruction);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deactivate():Promise {
|
deactivate(): Promise<any> {
|
||||||
return (isPresent(this._childRouter) ? this._childRouter.deactivate() : PromiseWrapper.resolve(true))
|
return (isPresent(this._childRouter) ? this._childRouter.deactivate() :
|
||||||
.then((_) =>this._componentRef.dispose());
|
PromiseWrapper.resolve(true))
|
||||||
|
.then((_) => this._componentRef.dispose());
|
||||||
}
|
}
|
||||||
|
|
||||||
canDeactivate(instruction:Instruction): Promise<boolean> {
|
canDeactivate(instruction: Instruction): Promise<boolean> {
|
||||||
// TODO: how to get ahold of the component instance here?
|
// TODO: how to get ahold of the component instance here?
|
||||||
return PromiseWrapper.resolve(true);
|
return PromiseWrapper.resolve(true);
|
||||||
}
|
}
|
|
@ -1,13 +1,9 @@
|
||||||
import {RegExpWrapper, StringWrapper} from 'angular2/src/facade/lang';
|
import {RegExpWrapper, StringWrapper} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
var specialCharacters = [
|
var specialCharacters = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'];
|
||||||
'/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'
|
|
||||||
];
|
|
||||||
|
|
||||||
var escapeRe = RegExpWrapper.create('(\\' + specialCharacters.join('|\\') + ')', 'g');
|
var escapeRe = RegExpWrapper.create('(\\' + specialCharacters.join('|\\') + ')', 'g');
|
||||||
|
|
||||||
export function escapeRegex(string:string): string {
|
export function escapeRegex(string: string): string {
|
||||||
return StringWrapper.replaceAllMapped(string, escapeRe, (match) => {
|
return StringWrapper.replaceAllMapped(string, escapeRe, (match) => { return "\\" + match; });
|
||||||
return "\\" + match;
|
|
||||||
});
|
|
||||||
}
|
}
|
|
@ -2,10 +2,15 @@ import {
|
||||||
AsyncTestCompleter,
|
AsyncTestCompleter,
|
||||||
describe,
|
describe,
|
||||||
proxy,
|
proxy,
|
||||||
it, iit,
|
it,
|
||||||
ddescribe, expect,
|
iit,
|
||||||
inject, beforeEach, beforeEachBindings,
|
ddescribe,
|
||||||
SpyObject} from 'angular2/test_lib';
|
expect,
|
||||||
|
inject,
|
||||||
|
beforeEach,
|
||||||
|
beforeEachBindings,
|
||||||
|
SpyObject
|
||||||
|
} from 'angular2/test_lib';
|
||||||
import {IMPLEMENTS} from 'angular2/src/facade/lang';
|
import {IMPLEMENTS} from 'angular2/src/facade/lang';
|
||||||
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
||||||
|
|
||||||
|
@ -13,7 +18,6 @@ import {BrowserLocation} from 'angular2/src/router/browser_location';
|
||||||
import {Location} from 'angular2/src/router/location';
|
import {Location} from 'angular2/src/router/location';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
|
|
||||||
describe('Location', () => {
|
describe('Location', () => {
|
||||||
|
|
||||||
var browserLocation, location;
|
var browserLocation, location;
|
||||||
|
@ -27,28 +31,31 @@ export function main() {
|
||||||
|
|
||||||
it('should normalize relative urls on navigate', () => {
|
it('should normalize relative urls on navigate', () => {
|
||||||
location.go('user/btford');
|
location.go('user/btford');
|
||||||
expect(browserLocation.spy('pushState')).toHaveBeenCalledWith(null, '', '/my/app/user/btford');
|
expect(browserLocation.spy('pushState'))
|
||||||
|
.toHaveBeenCalledWith(null, '', '/my/app/user/btford');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not append urls with leading slash on navigate', () => {
|
it('should not append urls with leading slash on navigate', () => {
|
||||||
location.go('/my/app/user/btford');
|
location.go('/my/app/user/btford');
|
||||||
expect(browserLocation.spy('pushState')).toHaveBeenCalledWith(null, '', '/my/app/user/btford');
|
expect(browserLocation.spy('pushState'))
|
||||||
|
.toHaveBeenCalledWith(null, '', '/my/app/user/btford');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove index.html from base href', () => {
|
it('should remove index.html from base href', () => {
|
||||||
browserLocation.baseHref = '/my/app/index.html';
|
browserLocation.baseHref = '/my/app/index.html';
|
||||||
location = new Location(browserLocation);
|
location = new Location(browserLocation);
|
||||||
location.go('user/btford');
|
location.go('user/btford');
|
||||||
expect(browserLocation.spy('pushState')).toHaveBeenCalledWith(null, '', '/my/app/user/btford');
|
expect(browserLocation.spy('pushState'))
|
||||||
|
.toHaveBeenCalledWith(null, '', '/my/app/user/btford');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should normalize urls on popstate', inject([AsyncTestCompleter], (async) => {
|
it('should normalize urls on popstate', inject([AsyncTestCompleter], (async) => {
|
||||||
browserLocation.simulatePopState('/my/app/user/btford');
|
browserLocation.simulatePopState('/my/app/user/btford');
|
||||||
location.subscribe((ev) => {
|
location.subscribe((ev) => {
|
||||||
expect(ev['url']).toEqual('/user/btford');
|
expect(ev['url']).toEqual('/user/btford');
|
||||||
async.done();
|
async.done();
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should normalize location path', () => {
|
it('should normalize location path', () => {
|
||||||
browserLocation.internalPath = '/my/app/user/btford';
|
browserLocation.internalPath = '/my/app/user/btford';
|
||||||
|
@ -62,7 +69,7 @@ export function main() {
|
||||||
class DummyBrowserLocation extends SpyObject {
|
class DummyBrowserLocation extends SpyObject {
|
||||||
baseHref;
|
baseHref;
|
||||||
internalPath;
|
internalPath;
|
||||||
_subject:EventEmitter;
|
_subject: EventEmitter;
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.internalPath = '/';
|
this.internalPath = '/';
|
||||||
|
@ -74,17 +81,11 @@ class DummyBrowserLocation extends SpyObject {
|
||||||
ObservableWrapper.callNext(this._subject, null);
|
ObservableWrapper.callNext(this._subject, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
path() {
|
path() { return this.internalPath; }
|
||||||
return this.internalPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
onPopState(fn) {
|
onPopState(fn) { ObservableWrapper.subscribe(this._subject, fn); }
|
||||||
ObservableWrapper.subscribe(this._subject, fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
getBaseHref() {
|
getBaseHref() { return this.baseHref; }
|
||||||
return this.baseHref;
|
|
||||||
}
|
|
||||||
|
|
||||||
noSuchMethod(m){return super.noSuchMethod(m);}
|
noSuchMethod(m) { return super.noSuchMethod(m); }
|
||||||
}
|
}
|
|
@ -1,348 +0,0 @@
|
||||||
import {
|
|
||||||
AsyncTestCompleter,
|
|
||||||
beforeEach,
|
|
||||||
ddescribe,
|
|
||||||
xdescribe,
|
|
||||||
describe,
|
|
||||||
el,
|
|
||||||
expect,
|
|
||||||
iit,
|
|
||||||
inject,
|
|
||||||
beforeEachBindings,
|
|
||||||
it,
|
|
||||||
xit
|
|
||||||
} from 'angular2/test_lib';
|
|
||||||
|
|
||||||
import {TestBed} from 'angular2/test';
|
|
||||||
|
|
||||||
import {Injector, bind} from 'angular2/di';
|
|
||||||
import {Component} from 'angular2/src/core/annotations_impl/annotations';
|
|
||||||
import {View} from 'angular2/src/core/annotations_impl/view';
|
|
||||||
|
|
||||||
import {RootRouter} from 'angular2/src/router/router';
|
|
||||||
import {Pipeline} from 'angular2/src/router/pipeline';
|
|
||||||
import {Router, RouterOutlet, RouterLink, RouteParams} from 'angular2/router';
|
|
||||||
import {RouteConfig} from 'angular2/src/router/route_config_impl';
|
|
||||||
|
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
||||||
|
|
||||||
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
|
||||||
import {Location} from 'angular2/src/router/location';
|
|
||||||
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
|
||||||
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
|
|
||||||
|
|
||||||
var teamCmpCount;
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
describe('Outlet Directive', () => {
|
|
||||||
|
|
||||||
var ctx, tb, view, rtr, location;
|
|
||||||
|
|
||||||
beforeEachBindings(() => [
|
|
||||||
Pipeline,
|
|
||||||
RouteRegistry,
|
|
||||||
DirectiveResolver,
|
|
||||||
bind(Location).toClass(SpyLocation),
|
|
||||||
bind(Router).toFactory((registry, pipeline, location) => {
|
|
||||||
return new RootRouter(registry, pipeline, location, MyComp);
|
|
||||||
}, [RouteRegistry, Pipeline, Location])
|
|
||||||
]);
|
|
||||||
|
|
||||||
beforeEach(inject([TestBed, Router, Location], (testBed, router, loc) => {
|
|
||||||
tb = testBed;
|
|
||||||
ctx = new MyComp();
|
|
||||||
rtr = router;
|
|
||||||
location = loc;
|
|
||||||
teamCmpCount = 0;
|
|
||||||
}));
|
|
||||||
|
|
||||||
function compile(template:string = "<router-outlet></router-outlet>") {
|
|
||||||
tb.overrideView(MyComp, new View({template: ('<div>' + template + '</div>'), directives: [RouterOutlet, RouterLink]}));
|
|
||||||
return tb.createView(MyComp, {context: ctx}).then((v) => {
|
|
||||||
view = v;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should work in a simple case', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile()
|
|
||||||
.then((_) => rtr.config({'path': '/test', 'component': HelloCmp}))
|
|
||||||
.then((_) => rtr.navigate('/test'))
|
|
||||||
.then((_) => {
|
|
||||||
view.detectChanges();
|
|
||||||
expect(view.rootNodes).toHaveText('hello');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should navigate between components with different parameters', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile()
|
|
||||||
.then((_) => rtr.config({'path': '/user/:name', 'component': UserCmp}))
|
|
||||||
.then((_) => rtr.navigate('/user/brian'))
|
|
||||||
.then((_) => {
|
|
||||||
view.detectChanges();
|
|
||||||
expect(view.rootNodes).toHaveText('hello brian');
|
|
||||||
})
|
|
||||||
.then((_) => rtr.navigate('/user/igor'))
|
|
||||||
.then((_) => {
|
|
||||||
view.detectChanges();
|
|
||||||
expect(view.rootNodes).toHaveText('hello igor');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should work with child routers', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile('outer { <router-outlet></router-outlet> }')
|
|
||||||
.then((_) => rtr.config({'path': '/a', 'component': ParentCmp}))
|
|
||||||
.then((_) => rtr.navigate('/a/b'))
|
|
||||||
.then((_) => {
|
|
||||||
view.detectChanges();
|
|
||||||
expect(view.rootNodes).toHaveText('outer { inner { hello } }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should work with sibling routers', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile('left { <router-outlet name="left"></router-outlet> } | right { <router-outlet name="right"></router-outlet> }')
|
|
||||||
.then((_) => rtr.config({'path': '/ab', 'components': {'left': A, 'right': B} }))
|
|
||||||
.then((_) => rtr.config({'path': '/ba', 'components': {'left': B, 'right': A} }))
|
|
||||||
.then((_) => rtr.navigate('/ab'))
|
|
||||||
.then((_) => {
|
|
||||||
view.detectChanges();
|
|
||||||
expect(view.rootNodes).toHaveText('left { A } | right { B }');
|
|
||||||
})
|
|
||||||
.then((_) => rtr.navigate('/ba'))
|
|
||||||
.then((_) => {
|
|
||||||
view.detectChanges();
|
|
||||||
expect(view.rootNodes).toHaveText('left { B } | right { A }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should work with redirects', inject([AsyncTestCompleter, Location], (async, location) => {
|
|
||||||
compile()
|
|
||||||
.then((_) => rtr.config({'path': '/original', 'redirectTo': '/redirected' }))
|
|
||||||
.then((_) => rtr.config({'path': '/redirected', 'component': A }))
|
|
||||||
.then((_) => rtr.navigate('/original'))
|
|
||||||
.then((_) => {
|
|
||||||
view.detectChanges();
|
|
||||||
expect(view.rootNodes).toHaveText('A');
|
|
||||||
expect(location.urlChanges).toEqual(['/redirected']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
function getHref(view) {
|
|
||||||
return DOM.getAttribute(view.rootNodes[0].childNodes[0], 'href');
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should generate absolute hrefs that include the base href', inject([AsyncTestCompleter], (async) => {
|
|
||||||
location.setBaseHref('/my/base');
|
|
||||||
compile('<a href="hello" router-link="user"></a>')
|
|
||||||
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
|
|
||||||
.then((_) => rtr.navigate('/a/b'))
|
|
||||||
.then((_) => {
|
|
||||||
view.detectChanges();
|
|
||||||
expect(getHref(view)).toEqual('/my/base/user');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should generate link hrefs without params', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile('<a href="hello" router-link="user"></a>')
|
|
||||||
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
|
|
||||||
.then((_) => rtr.navigate('/a/b'))
|
|
||||||
.then((_) => {
|
|
||||||
view.detectChanges();
|
|
||||||
expect(getHref(view)).toEqual('/user');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should reuse common parent components', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile()
|
|
||||||
.then((_) => rtr.config({'path': '/team/:id', 'component': TeamCmp }))
|
|
||||||
.then((_) => rtr.navigate('/team/angular/user/rado'))
|
|
||||||
.then((_) => {
|
|
||||||
view.detectChanges();
|
|
||||||
expect(teamCmpCount).toBe(1);
|
|
||||||
expect(view.rootNodes).toHaveText('team angular { hello rado }');
|
|
||||||
})
|
|
||||||
.then((_) => rtr.navigate('/team/angular/user/victor'))
|
|
||||||
.then((_) => {
|
|
||||||
view.detectChanges();
|
|
||||||
expect(teamCmpCount).toBe(1);
|
|
||||||
expect(view.rootNodes).toHaveText('team angular { hello victor }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should generate link hrefs with params', inject([AsyncTestCompleter], (async) => {
|
|
||||||
ctx.name = 'brian';
|
|
||||||
compile('<a href="hello" router-link="user" [router-params]="{name: name}">{{name}}</a>')
|
|
||||||
.then((_) => rtr.config({'path': '/user/:name', 'component': UserCmp, 'as': 'user'}))
|
|
||||||
.then((_) => rtr.navigate('/a/b'))
|
|
||||||
.then((_) => {
|
|
||||||
view.detectChanges();
|
|
||||||
expect(view.rootNodes).toHaveText('brian');
|
|
||||||
expect(DOM.getAttribute(view.rootNodes[0].childNodes[0], 'href')).toEqual('/user/brian');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('when clicked', () => {
|
|
||||||
|
|
||||||
var clickOnElement = function(view) {
|
|
||||||
var anchorEl = view.rootNodes[0].childNodes[0];
|
|
||||||
var dispatchedEvent = DOM.createMouseEvent('click');
|
|
||||||
DOM.dispatchEvent(anchorEl, dispatchedEvent);
|
|
||||||
return dispatchedEvent;
|
|
||||||
};
|
|
||||||
|
|
||||||
it('test', inject([AsyncTestCompleter], (async) => {
|
|
||||||
async.done();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate to link hrefs without params', inject([AsyncTestCompleter], (async) => {
|
|
||||||
compile('<a href="hello" router-link="user"></a>')
|
|
||||||
.then((_) => rtr.config({
|
|
||||||
'path': '/user',
|
|
||||||
'component': UserCmp,
|
|
||||||
'as': 'user'
|
|
||||||
}))
|
|
||||||
.then((_) => rtr.navigate('/a/b'))
|
|
||||||
.then((_) => {
|
|
||||||
view.detectChanges();
|
|
||||||
|
|
||||||
var dispatchedEvent = clickOnElement(view);
|
|
||||||
expect(dispatchedEvent.defaultPrevented || !dispatchedEvent.returnValue).toBe(true);
|
|
||||||
|
|
||||||
// router navigation is async.
|
|
||||||
rtr.subscribe((_) => {
|
|
||||||
expect(location.urlChanges).toEqual(['/user']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate to link hrefs in presence of base href', inject([AsyncTestCompleter], (async) => {
|
|
||||||
location.setBaseHref('/base');
|
|
||||||
compile('<a href="hello" router-link="user"></a>')
|
|
||||||
.then((_) => rtr.config({
|
|
||||||
'path': '/user',
|
|
||||||
'component': UserCmp,
|
|
||||||
'as': 'user'
|
|
||||||
}))
|
|
||||||
.then((_) => rtr.navigate('/a/b'))
|
|
||||||
.then((_) => {
|
|
||||||
view.detectChanges();
|
|
||||||
|
|
||||||
var dispatchedEvent = clickOnElement(view);
|
|
||||||
expect(dispatchedEvent.defaultPrevented || !dispatchedEvent.returnValue).toBe(true);
|
|
||||||
|
|
||||||
// router navigation is async.
|
|
||||||
rtr.subscribe((_) => {
|
|
||||||
expect(location.urlChanges).toEqual(['/base/user']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'hello-cmp'
|
|
||||||
})
|
|
||||||
@View({
|
|
||||||
template: "{{greeting}}"
|
|
||||||
})
|
|
||||||
class HelloCmp {
|
|
||||||
greeting:string;
|
|
||||||
constructor() {
|
|
||||||
this.greeting = "hello";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'a-cmp'
|
|
||||||
})
|
|
||||||
@View({
|
|
||||||
template: "A"
|
|
||||||
})
|
|
||||||
class A {}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'b-cmp'
|
|
||||||
})
|
|
||||||
@View({
|
|
||||||
template: "B"
|
|
||||||
})
|
|
||||||
class B {}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'user-cmp'
|
|
||||||
})
|
|
||||||
@View({
|
|
||||||
template: "hello {{user}}"
|
|
||||||
})
|
|
||||||
class UserCmp {
|
|
||||||
user:string;
|
|
||||||
constructor(params:RouteParams) {
|
|
||||||
this.user = params.get('name');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'parent-cmp'
|
|
||||||
})
|
|
||||||
@View({
|
|
||||||
template: "inner { <router-outlet></router-outlet> }",
|
|
||||||
directives: [RouterOutlet]
|
|
||||||
})
|
|
||||||
@RouteConfig([{
|
|
||||||
path: '/b',
|
|
||||||
component: HelloCmp
|
|
||||||
}])
|
|
||||||
class ParentCmp {
|
|
||||||
constructor() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'team-cmp'
|
|
||||||
})
|
|
||||||
@View({
|
|
||||||
template: "team {{id}} { <router-outlet></router-outlet> }",
|
|
||||||
directives: [RouterOutlet]
|
|
||||||
})
|
|
||||||
@RouteConfig([{
|
|
||||||
path: '/user/:name',
|
|
||||||
component: UserCmp
|
|
||||||
}])
|
|
||||||
class TeamCmp {
|
|
||||||
id:string;
|
|
||||||
constructor(params:RouteParams) {
|
|
||||||
this.id = params.get('id');
|
|
||||||
teamCmpCount += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'my-comp'
|
|
||||||
})
|
|
||||||
class MyComp {
|
|
||||||
name;
|
|
||||||
}
|
|
|
@ -0,0 +1,314 @@
|
||||||
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
|
beforeEach,
|
||||||
|
ddescribe,
|
||||||
|
xdescribe,
|
||||||
|
describe,
|
||||||
|
el,
|
||||||
|
expect,
|
||||||
|
iit,
|
||||||
|
inject,
|
||||||
|
beforeEachBindings,
|
||||||
|
it,
|
||||||
|
xit
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {TestBed} from 'angular2/test';
|
||||||
|
|
||||||
|
import {Injector, bind} from 'angular2/di';
|
||||||
|
import {Component, View} from 'angular2/src/core/annotations/decorators';
|
||||||
|
import * as annotations from 'angular2/src/core/annotations_impl/view';
|
||||||
|
import {CONST} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
import {RootRouter} from 'angular2/src/router/router';
|
||||||
|
import {Pipeline} from 'angular2/src/router/pipeline';
|
||||||
|
import {Router, RouterOutlet, RouterLink, RouteParams} from 'angular2/router';
|
||||||
|
import {RouteConfig} from 'angular2/src/router/route_config_decorator';
|
||||||
|
|
||||||
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
|
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
||||||
|
import {Location} from 'angular2/src/router/location';
|
||||||
|
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
||||||
|
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
|
||||||
|
|
||||||
|
var teamCmpCount;
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('Outlet Directive', () => {
|
||||||
|
|
||||||
|
var ctx: MyComp;
|
||||||
|
var tb: TestBed;
|
||||||
|
var view, rtr, location;
|
||||||
|
|
||||||
|
beforeEachBindings(() => [
|
||||||
|
Pipeline,
|
||||||
|
RouteRegistry,
|
||||||
|
DirectiveResolver,
|
||||||
|
bind(Location).toClass(SpyLocation),
|
||||||
|
bind(Router).toFactory((registry, pipeline, location) =>
|
||||||
|
{ return new RootRouter(registry, pipeline, location, MyComp); },
|
||||||
|
[RouteRegistry, Pipeline, Location])
|
||||||
|
]);
|
||||||
|
|
||||||
|
beforeEach(inject([TestBed, Router, Location], (testBed, router, loc) => {
|
||||||
|
tb = testBed;
|
||||||
|
ctx = new MyComp();
|
||||||
|
rtr = router;
|
||||||
|
location = loc;
|
||||||
|
teamCmpCount = 0;
|
||||||
|
}));
|
||||||
|
|
||||||
|
function compile(template: string = "<router-outlet></router-outlet>") {
|
||||||
|
tb.overrideView(MyComp, new annotations.View({
|
||||||
|
template: ('<div>' + template + '</div>'),
|
||||||
|
directives: [RouterOutlet, RouterLink]
|
||||||
|
}));
|
||||||
|
return tb.createView(MyComp, {context: ctx}).then((v) => { view = v; });
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should work in a simple case', inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/test', 'component': HelloCmp}))
|
||||||
|
.then((_) => rtr.navigate('/test'))
|
||||||
|
.then((_) => {
|
||||||
|
view.detectChanges();
|
||||||
|
expect(view.rootNodes).toHaveText('hello');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should navigate between components with different parameters',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/user/:name', 'component': UserCmp}))
|
||||||
|
.then((_) => rtr.navigate('/user/brian'))
|
||||||
|
.then((_) =>
|
||||||
|
{
|
||||||
|
view.detectChanges();
|
||||||
|
expect(view.rootNodes).toHaveText('hello brian');
|
||||||
|
})
|
||||||
|
.then((_) => rtr.navigate('/user/igor'))
|
||||||
|
.then((_) => {
|
||||||
|
view.detectChanges();
|
||||||
|
expect(view.rootNodes).toHaveText('hello igor');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should work with child routers', inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile('outer { <router-outlet></router-outlet> }')
|
||||||
|
.then((_) => rtr.config({'path': '/a', 'component': ParentCmp}))
|
||||||
|
.then((_) => rtr.navigate('/a/b'))
|
||||||
|
.then((_) => {
|
||||||
|
view.detectChanges();
|
||||||
|
expect(view.rootNodes).toHaveText('outer { inner { hello } }');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should work with sibling routers', inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile(
|
||||||
|
'left { <router-outlet name="left"></router-outlet> } | right { <router-outlet name="right"></router-outlet> }')
|
||||||
|
.then((_) => rtr.config({'path': '/ab', 'components': {'left': A, 'right': B}}))
|
||||||
|
.then((_) => rtr.config({'path': '/ba', 'components': {'left': B, 'right': A}}))
|
||||||
|
.then((_) => rtr.navigate('/ab'))
|
||||||
|
.then((_) =>
|
||||||
|
{
|
||||||
|
view.detectChanges();
|
||||||
|
expect(view.rootNodes).toHaveText('left { A } | right { B }');
|
||||||
|
})
|
||||||
|
.then((_) => rtr.navigate('/ba'))
|
||||||
|
.then((_) => {
|
||||||
|
view.detectChanges();
|
||||||
|
expect(view.rootNodes).toHaveText('left { B } | right { A }');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should work with redirects', inject([AsyncTestCompleter, Location], (async, location) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/original', 'redirectTo': '/redirected'}))
|
||||||
|
.then((_) => rtr.config({'path': '/redirected', 'component': A}))
|
||||||
|
.then((_) => rtr.navigate('/original'))
|
||||||
|
.then((_) => {
|
||||||
|
view.detectChanges();
|
||||||
|
expect(view.rootNodes).toHaveText('A');
|
||||||
|
expect(location.urlChanges).toEqual(['/redirected']);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
function getHref(view) { return DOM.getAttribute(view.rootNodes[0].childNodes[0], 'href'); }
|
||||||
|
|
||||||
|
it('should generate absolute hrefs that include the base href',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
location.setBaseHref('/my/base');
|
||||||
|
compile('<a href="hello" router-link="user"></a>')
|
||||||
|
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
|
||||||
|
.then((_) => rtr.navigate('/a/b'))
|
||||||
|
.then((_) => {
|
||||||
|
view.detectChanges();
|
||||||
|
expect(getHref(view)).toEqual('/my/base/user');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should generate link hrefs without params', inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile('<a href="hello" router-link="user"></a>')
|
||||||
|
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
|
||||||
|
.then((_) => rtr.navigate('/a/b'))
|
||||||
|
.then((_) => {
|
||||||
|
view.detectChanges();
|
||||||
|
expect(getHref(view)).toEqual('/user');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should reuse common parent components', inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile()
|
||||||
|
.then((_) => rtr.config({'path': '/team/:id', 'component': TeamCmp}))
|
||||||
|
.then((_) => rtr.navigate('/team/angular/user/rado'))
|
||||||
|
.then((_) =>
|
||||||
|
{
|
||||||
|
view.detectChanges();
|
||||||
|
expect(teamCmpCount).toBe(1);
|
||||||
|
expect(view.rootNodes).toHaveText('team angular { hello rado }');
|
||||||
|
})
|
||||||
|
.then((_) => rtr.navigate('/team/angular/user/victor'))
|
||||||
|
.then((_) => {
|
||||||
|
view.detectChanges();
|
||||||
|
expect(teamCmpCount).toBe(1);
|
||||||
|
expect(view.rootNodes).toHaveText('team angular { hello victor }');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should generate link hrefs with params', inject([AsyncTestCompleter], (async) => {
|
||||||
|
ctx.name = 'brian';
|
||||||
|
compile('<a href="hello" router-link="user" [router-params]="{name: name}">{{name}}</a>')
|
||||||
|
.then((_) => rtr.config({'path': '/user/:name', 'component': UserCmp, 'as': 'user'}))
|
||||||
|
.then((_) => rtr.navigate('/a/b'))
|
||||||
|
.then((_) => {
|
||||||
|
view.detectChanges();
|
||||||
|
expect(view.rootNodes).toHaveText('brian');
|
||||||
|
expect(DOM.getAttribute(view.rootNodes[0].childNodes[0], 'href'))
|
||||||
|
.toEqual('/user/brian');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('when clicked', () => {
|
||||||
|
|
||||||
|
var clickOnElement = function(view) {
|
||||||
|
var anchorEl = view.rootNodes[0].childNodes[0];
|
||||||
|
var dispatchedEvent = DOM.createMouseEvent('click');
|
||||||
|
DOM.dispatchEvent(anchorEl, dispatchedEvent);
|
||||||
|
return dispatchedEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
it('test', inject([AsyncTestCompleter], (async) => { async.done(); }));
|
||||||
|
|
||||||
|
it('should navigate to link hrefs without params', inject([AsyncTestCompleter], (async) => {
|
||||||
|
compile('<a href="hello" router-link="user"></a>')
|
||||||
|
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
|
||||||
|
.then((_) => rtr.navigate('/a/b'))
|
||||||
|
.then((_) => {
|
||||||
|
view.detectChanges();
|
||||||
|
|
||||||
|
var dispatchedEvent = clickOnElement(view);
|
||||||
|
expect(dispatchedEvent.defaultPrevented || !dispatchedEvent.returnValue)
|
||||||
|
.toBe(true);
|
||||||
|
|
||||||
|
// router navigation is async.
|
||||||
|
rtr.subscribe((_) => {
|
||||||
|
expect(location.urlChanges).toEqual(['/user']);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should navigate to link hrefs in presence of base href',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
location.setBaseHref('/base');
|
||||||
|
compile('<a href="hello" router-link="user"></a>')
|
||||||
|
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
|
||||||
|
.then((_) => rtr.navigate('/a/b'))
|
||||||
|
.then((_) => {
|
||||||
|
view.detectChanges();
|
||||||
|
|
||||||
|
var dispatchedEvent = clickOnElement(view);
|
||||||
|
expect(dispatchedEvent.defaultPrevented || !dispatchedEvent.returnValue)
|
||||||
|
.toBe(true);
|
||||||
|
|
||||||
|
// router navigation is async.
|
||||||
|
rtr.subscribe((_) => {
|
||||||
|
expect(location.urlChanges).toEqual(['/base/user']);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component({selector: 'hello-cmp'})
|
||||||
|
@View({template: "{{greeting}}"})
|
||||||
|
class HelloCmp {
|
||||||
|
greeting: string;
|
||||||
|
constructor() { this.greeting = "hello"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component({selector: 'a-cmp'})
|
||||||
|
@View({template: "A"})
|
||||||
|
class A {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component({selector: 'b-cmp'})
|
||||||
|
@View({template: "B"})
|
||||||
|
class B {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component({selector: 'user-cmp'})
|
||||||
|
@View({template: "hello {{user}}"})
|
||||||
|
class UserCmp {
|
||||||
|
user: string;
|
||||||
|
constructor(params: RouteParams) { this.user = params.get('name'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component({selector: 'parent-cmp'})
|
||||||
|
@View({template: "inner { <router-outlet></router-outlet> }", directives: [RouterOutlet]})
|
||||||
|
@RouteConfig([{path: '/b', component: HelloCmp}])
|
||||||
|
class ParentCmp {
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component({selector: 'team-cmp'})
|
||||||
|
@View({template: "team {{id}} { <router-outlet></router-outlet> }", directives: [RouterOutlet]})
|
||||||
|
@RouteConfig([{path: '/user/:name', component: UserCmp}])
|
||||||
|
class TeamCmp {
|
||||||
|
id: string;
|
||||||
|
constructor(params: RouteParams) {
|
||||||
|
this.id = params.get('id');
|
||||||
|
teamCmpCount += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component({selector: 'my-comp'})
|
||||||
|
class MyComp {
|
||||||
|
name;
|
||||||
|
}
|
|
@ -1,26 +1,24 @@
|
||||||
import {
|
import {
|
||||||
AsyncTestCompleter,
|
AsyncTestCompleter,
|
||||||
describe,
|
describe,
|
||||||
it, iit,
|
it,
|
||||||
ddescribe, expect,
|
iit,
|
||||||
inject, beforeEach,
|
ddescribe,
|
||||||
SpyObject} from 'angular2/test_lib';
|
expect,
|
||||||
|
inject,
|
||||||
|
beforeEach,
|
||||||
|
SpyObject
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
import {RouteRecognizer} from 'angular2/src/router/route_recognizer';
|
import {RouteRecognizer} from 'angular2/src/router/route_recognizer';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('RouteRecognizer', () => {
|
describe('RouteRecognizer', () => {
|
||||||
var recognizer;
|
var recognizer;
|
||||||
var handler = {
|
var handler = {'components': {'a': 'b'}};
|
||||||
'components': { 'a': 'b' }
|
var handler2 = {'components': {'b': 'c'}};
|
||||||
};
|
|
||||||
var handler2 = {
|
|
||||||
'components': { 'b': 'c' }
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => { recognizer = new RouteRecognizer(); });
|
||||||
recognizer = new RouteRecognizer();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should recognize a static segment', () => {
|
it('should recognize a static segment', () => {
|
||||||
|
@ -40,7 +38,7 @@ export function main() {
|
||||||
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(solution.handler).toEqual(handler);
|
||||||
expect(solution.params).toEqual({ 'name': 'brian' });
|
expect(solution.params).toEqual({'name': 'brian'});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,23 +46,22 @@ export function main() {
|
||||||
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(solution.handler).toEqual(handler);
|
||||||
expect(solution.params).toEqual({ 'rest': 'second/third' });
|
expect(solution.params).toEqual({'rest': 'second/third'});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should throw when given two routes that start with the same static segment', () => {
|
it('should throw when given two routes that start with the same static segment', () => {
|
||||||
recognizer.addConfig('/hello', handler);
|
recognizer.addConfig('/hello', handler);
|
||||||
expect(() => recognizer.addConfig('/hello', handler2)).toThrowError(
|
expect(() => recognizer.addConfig('/hello', handler2))
|
||||||
'Configuration \'/hello\' conflicts with existing route \'/hello\''
|
.toThrowError('Configuration \'/hello\' conflicts with existing route \'/hello\'');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should throw when given two routes that have dynamic segments in the same order', () => {
|
it('should throw when given two routes that have dynamic segments in the same order', () => {
|
||||||
recognizer.addConfig('/hello/:person/how/:doyoudou', handler);
|
recognizer.addConfig('/hello/:person/how/:doyoudou', handler);
|
||||||
expect(() => recognizer.addConfig('/hello/:friend/how/:areyou', handler2)).toThrowError(
|
expect(() => recognizer.addConfig('/hello/:friend/how/:areyou', handler2))
|
||||||
'Configuration \'/hello/:friend/how/:areyou\' conflicts with existing route \'/hello/:person/how/:doyoudou\''
|
.toThrowError(
|
||||||
);
|
'Configuration \'/hello/:friend/how/:areyou\' conflicts with existing route \'/hello/:person/how/:doyoudou\'');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,14 +79,14 @@ export function main() {
|
||||||
|
|
||||||
it('should generate URLs', () => {
|
it('should generate URLs', () => {
|
||||||
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'})).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', {})).toThrowError(
|
expect(() => recognizer.generate('user', {}))
|
||||||
'Route generator for \'name\' was not included in parameters passed.');
|
.toThrowError('Route generator for \'name\' was not included in parameters passed.');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -1,22 +1,23 @@
|
||||||
import {
|
import {
|
||||||
AsyncTestCompleter,
|
AsyncTestCompleter,
|
||||||
describe,
|
describe,
|
||||||
it, iit,
|
it,
|
||||||
ddescribe, expect,
|
iit,
|
||||||
inject, beforeEach,
|
ddescribe,
|
||||||
SpyObject} from 'angular2/test_lib';
|
expect,
|
||||||
|
inject,
|
||||||
|
beforeEach,
|
||||||
|
SpyObject
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
||||||
import {RouteConfig} from 'angular2/src/router/route_config_impl';
|
import {RouteConfig} from 'angular2/src/router/route_config_decorator';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('RouteRegistry', () => {
|
describe('RouteRegistry', () => {
|
||||||
var registry,
|
var registry, rootHostComponent = new Object();
|
||||||
rootHostComponent = new Object();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => { registry = new RouteRegistry(); });
|
||||||
registry = new RouteRegistry();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should match the full URL', () => {
|
it('should match the full URL', () => {
|
||||||
registry.config(rootHostComponent, {'path': '/', 'component': DummyCompA});
|
registry.config(rootHostComponent, {'path': '/', 'component': DummyCompA});
|
||||||
|
@ -87,10 +88,9 @@ export function main() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@RouteConfig([
|
|
||||||
{'path': '/second', 'component': DummyCompB }
|
|
||||||
])
|
|
||||||
class DummyParentComp {}
|
|
||||||
|
|
||||||
class DummyCompA {}
|
class DummyCompA {}
|
||||||
class DummyCompB {}
|
class DummyCompB {}
|
||||||
|
|
||||||
|
@RouteConfig([{'path': '/second', 'component': DummyCompB}])
|
||||||
|
class DummyParentComp {
|
||||||
|
}
|
|
@ -1,78 +0,0 @@
|
||||||
import {
|
|
||||||
AsyncTestCompleter,
|
|
||||||
beforeEach,
|
|
||||||
ddescribe,
|
|
||||||
describe,
|
|
||||||
expect,
|
|
||||||
iit,
|
|
||||||
inject,
|
|
||||||
it,
|
|
||||||
xdescribe,
|
|
||||||
xit,
|
|
||||||
} from 'angular2/test_lib';
|
|
||||||
|
|
||||||
import {bootstrap} from 'angular2/src/core/application';
|
|
||||||
import {Component, Directive} from 'angular2/src/core/annotations_impl/annotations';
|
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
||||||
import {bind} from 'angular2/di';
|
|
||||||
import {View} from 'angular2/src/core/annotations_impl/view';
|
|
||||||
import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
|
|
||||||
import {RouteConfig} from 'angular2/src/router/route_config_impl';
|
|
||||||
import {routerInjectables, Router, RouteParams, RouterOutlet} from 'angular2/router';
|
|
||||||
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
|
||||||
import {Location} from 'angular2/src/router/location';
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
describe('router injectables', () => {
|
|
||||||
var fakeDoc, el, testBindings;
|
|
||||||
beforeEach(() => {
|
|
||||||
fakeDoc = DOM.createHtmlDocument();
|
|
||||||
el = DOM.createElement('app-cmp', fakeDoc);
|
|
||||||
DOM.appendChild(fakeDoc.body, el);
|
|
||||||
testBindings = [
|
|
||||||
routerInjectables,
|
|
||||||
bind(Location).toClass(SpyLocation),
|
|
||||||
bind(DOCUMENT_TOKEN).toValue(fakeDoc)
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support bootstrap a simple app', inject([AsyncTestCompleter], (async) => {
|
|
||||||
bootstrap(AppCmp, testBindings).then((applicationRef) => {
|
|
||||||
var router = applicationRef.hostComponent.router;
|
|
||||||
router.subscribe((_) => {
|
|
||||||
expect(el).toHaveText('outer { hello }');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
//TODO: add a test in which the child component has bindings
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'hello-cmp'
|
|
||||||
})
|
|
||||||
@View({
|
|
||||||
template: "hello"
|
|
||||||
})
|
|
||||||
class HelloCmp {}
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-cmp'
|
|
||||||
})
|
|
||||||
@View({
|
|
||||||
template: "outer { <router-outlet></router-outlet> }",
|
|
||||||
directives: [RouterOutlet]
|
|
||||||
})
|
|
||||||
@RouteConfig([{
|
|
||||||
path: '/', component: HelloCmp
|
|
||||||
}])
|
|
||||||
class AppCmp {
|
|
||||||
router:Router;
|
|
||||||
constructor(router:Router) {
|
|
||||||
this.router = router;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
|
beforeEach,
|
||||||
|
ddescribe,
|
||||||
|
describe,
|
||||||
|
expect,
|
||||||
|
iit,
|
||||||
|
inject,
|
||||||
|
it,
|
||||||
|
xdescribe,
|
||||||
|
xit,
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {bootstrap} from 'angular2/src/core/application';
|
||||||
|
import {Component, Directive, View} from 'angular2/src/core/annotations/decorators';
|
||||||
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
import {bind} from 'angular2/di';
|
||||||
|
import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
|
||||||
|
import {RouteConfig} from 'angular2/src/router/route_config_decorator';
|
||||||
|
import {routerInjectables, Router} from 'angular2/router';
|
||||||
|
import {RouterOutlet} from 'angular2/src/router/router_outlet';
|
||||||
|
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
||||||
|
import {Location} from 'angular2/src/router/location';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('router injectables', () => {
|
||||||
|
var fakeDoc, el, testBindings;
|
||||||
|
beforeEach(() => {
|
||||||
|
fakeDoc = DOM.createHtmlDocument();
|
||||||
|
el = DOM.createElement('app-cmp', fakeDoc);
|
||||||
|
DOM.appendChild(fakeDoc.body, el);
|
||||||
|
testBindings = [
|
||||||
|
routerInjectables,
|
||||||
|
bind(Location).toClass(SpyLocation),
|
||||||
|
bind(DOCUMENT_TOKEN).toValue(fakeDoc)
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support bootstrap a simple app', inject([AsyncTestCompleter], (async) => {
|
||||||
|
bootstrap(AppCmp, testBindings)
|
||||||
|
.then((applicationRef) => {
|
||||||
|
var router = applicationRef.hostComponent.router;
|
||||||
|
router.subscribe((_) => {
|
||||||
|
expect(el).toHaveText('outer { hello }');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
// TODO: add a test in which the child component has bindings
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component({selector: 'hello-cmp'})
|
||||||
|
@View({template: "hello"})
|
||||||
|
class HelloCmp {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component({selector: 'app-cmp'})
|
||||||
|
@View({template: "outer { <router-outlet></router-outlet> }", directives: [RouterOutlet]})
|
||||||
|
@RouteConfig([{path: '/', component: HelloCmp}])
|
||||||
|
class AppCmp {
|
||||||
|
router: Router;
|
||||||
|
constructor(router: Router) { this.router = router; }
|
||||||
|
}
|
|
@ -1,103 +0,0 @@
|
||||||
import {
|
|
||||||
AsyncTestCompleter,
|
|
||||||
describe,
|
|
||||||
proxy,
|
|
||||||
it, iit,
|
|
||||||
ddescribe, expect,
|
|
||||||
inject, beforeEach, beforeEachBindings,
|
|
||||||
SpyObject} from 'angular2/test_lib';
|
|
||||||
import {IMPLEMENTS} from 'angular2/src/facade/lang';
|
|
||||||
|
|
||||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
|
||||||
import {Router, RootRouter} from 'angular2/src/router/router';
|
|
||||||
import {Pipeline} from 'angular2/src/router/pipeline';
|
|
||||||
import {RouterOutlet} from 'angular2/src/router/router_outlet';
|
|
||||||
import {SpyLocation} from 'angular2/src/mock/location_mock'
|
|
||||||
import {Location} from 'angular2/src/router/location';
|
|
||||||
|
|
||||||
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
|
||||||
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
|
|
||||||
|
|
||||||
import {bind} from 'angular2/di';
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
describe('Router', () => {
|
|
||||||
var router,
|
|
||||||
location;
|
|
||||||
|
|
||||||
beforeEachBindings(() => [
|
|
||||||
Pipeline,
|
|
||||||
RouteRegistry,
|
|
||||||
DirectiveResolver,
|
|
||||||
bind(Location).toClass(SpyLocation),
|
|
||||||
bind(Router).toFactory((registry, pipeline, location) => {
|
|
||||||
return new RootRouter(registry, pipeline, location, AppCmp);
|
|
||||||
}, [RouteRegistry, Pipeline, Location])
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
|
||||||
beforeEach(inject([Router, Location], (rtr, loc) => {
|
|
||||||
router = rtr;
|
|
||||||
location = loc;
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should navigate based on the initial URL state', inject([AsyncTestCompleter], (async) => {
|
|
||||||
var outlet = makeDummyOutlet();
|
|
||||||
|
|
||||||
router.config({'path': '/', 'component': 'Index' })
|
|
||||||
.then((_) => router.registerOutlet(outlet))
|
|
||||||
.then((_) => {
|
|
||||||
expect(outlet.spy('activate')).toHaveBeenCalled();
|
|
||||||
expect(location.urlChanges).toEqual([]);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('should activate viewports and update URL on navigate', inject([AsyncTestCompleter], (async) => {
|
|
||||||
var outlet = makeDummyOutlet();
|
|
||||||
|
|
||||||
router.registerOutlet(outlet)
|
|
||||||
.then((_) => {
|
|
||||||
return router.config({'path': '/a', 'component': 'A' });
|
|
||||||
})
|
|
||||||
.then((_) => router.navigate('/a'))
|
|
||||||
.then((_) => {
|
|
||||||
expect(outlet.spy('activate')).toHaveBeenCalled();
|
|
||||||
expect(location.urlChanges).toEqual(['/a']);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should navigate after being configured', inject([AsyncTestCompleter], (async) => {
|
|
||||||
var outlet = makeDummyOutlet();
|
|
||||||
|
|
||||||
router.registerOutlet(outlet)
|
|
||||||
.then((_) => router.navigate('/a'))
|
|
||||||
.then((_) => {
|
|
||||||
expect(outlet.spy('activate')).not.toHaveBeenCalled();
|
|
||||||
return router.config({'path': '/a', 'component': 'A' });
|
|
||||||
})
|
|
||||||
.then((_) => {
|
|
||||||
expect(outlet.spy('activate')).toHaveBeenCalled();
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@proxy
|
|
||||||
@IMPLEMENTS(RouterOutlet)
|
|
||||||
class DummyOutlet extends SpyObject {noSuchMethod(m){return super.noSuchMethod(m)}}
|
|
||||||
|
|
||||||
function makeDummyOutlet() {
|
|
||||||
var ref = new DummyOutlet();
|
|
||||||
ref.spy('activate').andCallFake((_) => PromiseWrapper.resolve(true));
|
|
||||||
ref.spy('canActivate').andCallFake((_) => PromiseWrapper.resolve(true));
|
|
||||||
ref.spy('canDeactivate').andCallFake((_) => PromiseWrapper.resolve(true));
|
|
||||||
ref.spy('deactivate').andCallFake((_) => PromiseWrapper.resolve(true));
|
|
||||||
return ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppCmp {}
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
|
describe,
|
||||||
|
proxy,
|
||||||
|
it,
|
||||||
|
iit,
|
||||||
|
ddescribe,
|
||||||
|
expect,
|
||||||
|
inject,
|
||||||
|
beforeEach,
|
||||||
|
beforeEachBindings,
|
||||||
|
SpyObject
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
import {IMPLEMENTS} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
|
import {Router, RootRouter} from 'angular2/src/router/router';
|
||||||
|
import {Pipeline} from 'angular2/src/router/pipeline';
|
||||||
|
import {RouterOutlet} from 'angular2/src/router/router_outlet';
|
||||||
|
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
||||||
|
import {Location} from 'angular2/src/router/location';
|
||||||
|
|
||||||
|
import {RouteRegistry} from 'angular2/src/router/route_registry';
|
||||||
|
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
|
||||||
|
|
||||||
|
import {bind} from 'angular2/di';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('Router', () => {
|
||||||
|
var router, location;
|
||||||
|
|
||||||
|
beforeEachBindings(() => [
|
||||||
|
Pipeline,
|
||||||
|
RouteRegistry,
|
||||||
|
DirectiveResolver,
|
||||||
|
bind(Location).toClass(SpyLocation),
|
||||||
|
bind(Router).toFactory((registry, pipeline, location) =>
|
||||||
|
{ return new RootRouter(registry, pipeline, location, AppCmp); },
|
||||||
|
[RouteRegistry, Pipeline, Location])
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
beforeEach(inject([Router, Location], (rtr, loc) => {
|
||||||
|
router = rtr;
|
||||||
|
location = loc;
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should navigate based on the initial URL state', inject([AsyncTestCompleter], (async) => {
|
||||||
|
var outlet = makeDummyOutlet();
|
||||||
|
|
||||||
|
router.config({'path': '/', 'component': 'Index'})
|
||||||
|
.then((_) => router.registerOutlet(outlet))
|
||||||
|
.then((_) => {
|
||||||
|
expect(outlet.spy('activate')).toHaveBeenCalled();
|
||||||
|
expect(location.urlChanges).toEqual([]);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should activate viewports and update URL on navigate',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
var outlet = makeDummyOutlet();
|
||||||
|
|
||||||
|
router.registerOutlet(outlet)
|
||||||
|
.then((_) => { return router.config({'path': '/a', 'component': 'A'}); })
|
||||||
|
.then((_) => router.navigate('/a'))
|
||||||
|
.then((_) => {
|
||||||
|
expect(outlet.spy('activate')).toHaveBeenCalled();
|
||||||
|
expect(location.urlChanges).toEqual(['/a']);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should navigate after being configured', inject([AsyncTestCompleter], (async) => {
|
||||||
|
var outlet = makeDummyOutlet();
|
||||||
|
|
||||||
|
router.registerOutlet(outlet)
|
||||||
|
.then((_) => router.navigate('/a'))
|
||||||
|
.then((_) =>
|
||||||
|
{
|
||||||
|
expect(outlet.spy('activate')).not.toHaveBeenCalled();
|
||||||
|
return router.config({'path': '/a', 'component': 'A'});
|
||||||
|
})
|
||||||
|
.then((_) => {
|
||||||
|
expect(outlet.spy('activate')).toHaveBeenCalled();
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@proxy
|
||||||
|
@IMPLEMENTS(RouterOutlet)
|
||||||
|
class DummyOutlet extends SpyObject {
|
||||||
|
noSuchMethod(m) { return super.noSuchMethod(m) }
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeDummyOutlet() {
|
||||||
|
var ref = new DummyOutlet();
|
||||||
|
ref.spy('activate').andCallFake((_) => PromiseWrapper.resolve(true));
|
||||||
|
ref.spy('canActivate').andCallFake((_) => PromiseWrapper.resolve(true));
|
||||||
|
ref.spy('canDeactivate').andCallFake((_) => PromiseWrapper.resolve(true));
|
||||||
|
ref.spy('deactivate').andCallFake((_) => PromiseWrapper.resolve(true));
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppCmp {}
|
Loading…
Reference in New Issue