2015-04-17 09:59:56 -07:00
|
|
|
import {Promise, PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
|
|
|
import {Map, MapWrapper, List, ListWrapper} from 'angular2/src/facade/collection';
|
2015-05-07 21:10:12 -07:00
|
|
|
import {isBlank, isPresent, Type} from 'angular2/src/facade/lang';
|
2015-04-17 09:59:56 -07:00
|
|
|
|
2015-05-01 05:53:38 -07:00
|
|
|
import {RouteRegistry} from './route_registry';
|
2015-04-17 09:59:56 -07:00
|
|
|
import {Pipeline} from './pipeline';
|
|
|
|
import {Instruction} from './instruction';
|
|
|
|
import {RouterOutlet} from './router_outlet';
|
2015-04-21 11:23:23 -07:00
|
|
|
import {Location} from './location';
|
2015-04-17 09:59:56 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* # Router
|
|
|
|
* The router is responsible for mapping URLs to components.
|
|
|
|
*
|
|
|
|
* You can see the state of the router by inspecting the read-only field `router.navigating`.
|
|
|
|
* This may be useful for showing a spinner, for instance.
|
|
|
|
*
|
|
|
|
* @exportedAs angular2/router
|
|
|
|
*/
|
|
|
|
export class Router {
|
2015-04-29 15:47:12 -07:00
|
|
|
hostComponent:any;
|
2015-04-17 09:59:56 -07:00
|
|
|
parent:Router;
|
|
|
|
navigating:boolean;
|
|
|
|
lastNavigationAttempt: string;
|
|
|
|
previousUrl:string;
|
|
|
|
|
2015-05-07 21:10:12 -07:00
|
|
|
_currentInstruction:Instruction;
|
|
|
|
|
2015-04-17 09:59:56 -07:00
|
|
|
_pipeline:Pipeline;
|
|
|
|
_registry:RouteRegistry;
|
|
|
|
_outlets:Map<any, RouterOutlet>;
|
|
|
|
_children:Map<any, Router>;
|
|
|
|
_subject:EventEmitter;
|
2015-04-21 11:23:23 -07:00
|
|
|
_location:Location;
|
2015-04-29 15:47:12 -07:00
|
|
|
|
|
|
|
constructor(registry:RouteRegistry, pipeline:Pipeline, location:Location, parent:Router, hostComponent) {
|
|
|
|
this.hostComponent = hostComponent;
|
2015-04-17 09:59:56 -07:00
|
|
|
this.navigating = false;
|
|
|
|
this.parent = parent;
|
|
|
|
this.previousUrl = null;
|
|
|
|
this._outlets = MapWrapper.create();
|
|
|
|
this._children = MapWrapper.create();
|
2015-04-21 11:23:23 -07:00
|
|
|
this._location = location;
|
2015-04-17 09:59:56 -07:00
|
|
|
this._registry = registry;
|
|
|
|
this._pipeline = pipeline;
|
|
|
|
this._subject = new EventEmitter();
|
2015-05-07 21:10:12 -07:00
|
|
|
this._currentInstruction = null;
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructs a child router. You probably don't need to use this unless you're writing a reusable component.
|
|
|
|
*/
|
2015-05-14 15:24:35 +02:00
|
|
|
childRouter(outletName = 'default'): Router {
|
|
|
|
var router = MapWrapper.get(this._children, outletName);
|
|
|
|
|
|
|
|
if (isBlank(router)) {
|
|
|
|
router = new ChildRouter(this, outletName);
|
|
|
|
MapWrapper.set(this._children, outletName, router);
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
2015-05-14 15:24:35 +02:00
|
|
|
|
|
|
|
return router;
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register an object to notify of route changes. You probably don't need to use this unless you're writing a reusable component.
|
|
|
|
*/
|
2015-05-14 15:24:35 +02:00
|
|
|
registerOutlet(outlet:RouterOutlet, name: string = 'default'):Promise {
|
2015-04-17 09:59:56 -07:00
|
|
|
MapWrapper.set(this._outlets, name, outlet);
|
2015-05-07 21:10:12 -07:00
|
|
|
if (isPresent(this._currentInstruction)) {
|
|
|
|
var childInstruction = this._currentInstruction.getChildInstruction(name);
|
|
|
|
return outlet.activate(childInstruction);
|
|
|
|
}
|
|
|
|
return PromiseWrapper.resolve(true);
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the routing configuration and trigger a navigation.
|
|
|
|
*
|
|
|
|
* # Usage
|
|
|
|
*
|
|
|
|
* ```
|
2015-04-29 15:47:12 -07:00
|
|
|
* router.config({ 'path': '/', 'component': IndexCmp});
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* Or:
|
|
|
|
*
|
2015-04-17 09:59:56 -07:00
|
|
|
* ```
|
2015-04-29 15:47:12 -07:00
|
|
|
* router.config([
|
|
|
|
* { 'path': '/', 'component': IndexComp },
|
|
|
|
* { 'path': '/user/:id', 'component': UserComp },
|
|
|
|
* ]);
|
|
|
|
* ```
|
|
|
|
*
|
2015-04-17 09:59:56 -07:00
|
|
|
*/
|
2015-05-14 15:24:35 +02:00
|
|
|
config(config:any): Promise {
|
2015-04-29 15:47:12 -07:00
|
|
|
if (config instanceof List) {
|
2015-05-01 05:53:38 -07:00
|
|
|
config.forEach((configObject) => {
|
2015-04-29 15:47:12 -07:00
|
|
|
// TODO: this is a hack
|
|
|
|
this._registry.config(this.hostComponent, configObject);
|
2015-05-14 15:24:35 +02:00
|
|
|
});
|
2015-04-29 15:47:12 -07:00
|
|
|
} else {
|
|
|
|
this._registry.config(this.hostComponent, config);
|
|
|
|
}
|
2015-04-17 09:59:56 -07:00
|
|
|
return this.renavigate();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Navigate to a URL. Returns a promise that resolves to the canonical URL for the route.
|
|
|
|
*/
|
|
|
|
navigate(url:string):Promise {
|
|
|
|
if (this.navigating) {
|
|
|
|
return PromiseWrapper.resolve(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.lastNavigationAttempt = url;
|
|
|
|
|
2015-05-07 21:10:12 -07:00
|
|
|
var matchedInstruction = this.recognize(url);
|
2015-04-17 09:59:56 -07:00
|
|
|
|
2015-05-07 21:10:12 -07:00
|
|
|
if (isBlank(matchedInstruction)) {
|
2015-04-17 09:59:56 -07:00
|
|
|
return PromiseWrapper.resolve(false);
|
|
|
|
}
|
|
|
|
|
2015-05-07 21:10:12 -07:00
|
|
|
if(isPresent(this._currentInstruction)) {
|
|
|
|
matchedInstruction.reuseComponentsFrom(this._currentInstruction);
|
|
|
|
}
|
|
|
|
|
|
|
|
matchedInstruction.router = this;
|
2015-04-17 09:59:56 -07:00
|
|
|
this._startNavigating();
|
|
|
|
|
2015-05-07 21:10:12 -07:00
|
|
|
var result = this._pipeline.process(matchedInstruction)
|
2015-04-17 09:59:56 -07:00
|
|
|
.then((_) => {
|
2015-05-07 21:10:12 -07:00
|
|
|
this._location.go(matchedInstruction.matchedUrl);
|
|
|
|
ObservableWrapper.callNext(this._subject, matchedInstruction.matchedUrl);
|
|
|
|
this._finishNavigating();
|
|
|
|
this._currentInstruction = matchedInstruction;
|
|
|
|
});
|
2015-04-17 09:59:56 -07:00
|
|
|
|
|
|
|
PromiseWrapper.catchError(result, (_) => this._finishNavigating());
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-05-14 15:24:35 +02:00
|
|
|
_startNavigating(): void {
|
2015-04-17 09:59:56 -07:00
|
|
|
this.navigating = true;
|
|
|
|
}
|
|
|
|
|
2015-05-14 15:24:35 +02:00
|
|
|
_finishNavigating(): void {
|
2015-04-17 09:59:56 -07:00
|
|
|
this.navigating = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Subscribe to URL updates from the router
|
|
|
|
*/
|
2015-05-14 15:24:35 +02:00
|
|
|
subscribe(onNext): void {
|
2015-04-17 09:59:56 -07:00
|
|
|
ObservableWrapper.subscribe(this._subject, onNext);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
activateOutlets(instruction:Instruction):Promise {
|
|
|
|
return this._queryOutlets((outlet, name) => {
|
2015-05-07 21:10:12 -07:00
|
|
|
var childInstruction = instruction.getChildInstruction(name);
|
|
|
|
if (childInstruction.reuse) {
|
|
|
|
return PromiseWrapper.resolve(true);
|
|
|
|
}
|
|
|
|
return outlet.activate(childInstruction);
|
2015-04-17 09:59:56 -07:00
|
|
|
})
|
|
|
|
.then((_) => instruction.mapChildrenAsync((instruction, _) => {
|
|
|
|
return instruction.router.activateOutlets(instruction);
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
traverseOutlets(fn):Promise {
|
|
|
|
return this._queryOutlets(fn)
|
|
|
|
.then((_) => mapObjAsync(this._children, (child, _) => child.traverseOutlets(fn)));
|
|
|
|
}
|
|
|
|
|
|
|
|
_queryOutlets(fn):Promise {
|
|
|
|
return mapObjAsync(this._outlets, fn);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a URL, returns an instruction representing the component graph
|
|
|
|
*/
|
2015-05-14 15:24:35 +02:00
|
|
|
recognize(url:string): Instruction {
|
2015-05-01 05:53:38 -07:00
|
|
|
return this._registry.recognize(url, this.hostComponent);
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Navigates to either the last URL successfully navigated to, or the last URL requested if the router has yet to successfully navigate.
|
|
|
|
*/
|
|
|
|
renavigate():Promise {
|
|
|
|
var destination = isBlank(this.previousUrl) ? this.lastNavigationAttempt : this.previousUrl;
|
|
|
|
if (this.navigating || isBlank(destination)) {
|
|
|
|
return PromiseWrapper.resolve(false);
|
|
|
|
}
|
|
|
|
return this.navigate(destination);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a URL from a component name and optional map of parameters. The URL is relative to the app's base href.
|
|
|
|
*/
|
2015-05-14 15:24:35 +02:00
|
|
|
generate(name:string, params:StringMap<string, string>): string {
|
2015-05-01 05:53:38 -07:00
|
|
|
return this._registry.generate(name, params, this.hostComponent);
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class RootRouter extends Router {
|
2015-05-01 05:53:38 -07:00
|
|
|
constructor(registry:RouteRegistry, pipeline:Pipeline, location:Location, hostComponent:Type) {
|
|
|
|
super(registry, pipeline, location, null, hostComponent);
|
2015-05-03 20:22:41 -07:00
|
|
|
this._location.subscribe((change) => this.navigate(change['url']));
|
2015-05-01 05:53:38 -07:00
|
|
|
this._registry.configFromComponent(hostComponent);
|
2015-04-29 15:47:12 -07:00
|
|
|
this.navigate(location.path());
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ChildRouter extends Router {
|
2015-04-29 15:47:12 -07:00
|
|
|
constructor(parent:Router, hostComponent) {
|
|
|
|
super(parent._registry, parent._pipeline, parent._location, parent, hostComponent);
|
2015-04-17 09:59:56 -07:00
|
|
|
this.parent = parent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-14 15:24:35 +02:00
|
|
|
function mapObjAsync(obj:Map, fn): Promise {
|
2015-04-17 09:59:56 -07:00
|
|
|
return PromiseWrapper.all(mapObj(obj, fn));
|
|
|
|
}
|
|
|
|
|
|
|
|
function mapObj(obj:Map, fn):List {
|
|
|
|
var result = ListWrapper.create();
|
|
|
|
MapWrapper.forEach(obj, (value, key) => ListWrapper.push(result, fn(value, key)));
|
|
|
|
return result;
|
|
|
|
}
|