import {Promise, PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; import {Map, MapWrapper, List, ListWrapper} from 'angular2/src/facade/collection'; import {isBlank, isPresent, Type} from 'angular2/src/facade/lang'; import {RouteRegistry} from './route_registry'; import {Pipeline} from './pipeline'; import {Instruction} from './instruction'; import {RouterOutlet} from './router_outlet'; import {Location} from './location'; /** * # 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. * * ## Concepts * Routers and component instances have a 1:1 correspondence. * * 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. * * 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`. * * @exportedAs angular2/router */ export class Router { navigating: boolean; lastNavigationAttempt: string; previousUrl: string; private _currentInstruction: Instruction; private _currentNavigation: Promise; private _outlet: RouterOutlet; private _subject: EventEmitter; // todo(jeffbcross): rename _registry to registry since it is accessed from subclasses // todo(jeffbcross): rename _pipeline to pipeline since it is accessed from subclasses constructor(public _registry: RouteRegistry, public _pipeline: Pipeline, public parent: Router, public hostComponent: any) { this.navigating = false; this.previousUrl = null; this._outlet = null; this._subject = new EventEmitter(); this._currentInstruction = null; this._currentNavigation = PromiseWrapper.resolve(true); } /** * Constructs a child router. You probably don't need to use this unless you're writing a reusable * component. */ childRouter(hostComponent: any): Router { 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. */ registerOutlet(outlet: RouterOutlet): Promise { // TODO: sibling routes this._outlet = outlet; if (isPresent(this._currentInstruction)) { return outlet.activate(this._currentInstruction); } return PromiseWrapper.resolve(true); } /** * Dynamically update the routing configuration and trigger a navigation. * * # Usage * * ``` * router.config({ 'path': '/', 'component': IndexCmp}); * ``` * * Or: * * ``` * router.config([ * { 'path': '/', 'component': IndexComp }, * { 'path': '/user/:id', 'component': UserComp }, * ]); * ``` */ config(config: any): Promise { if (config instanceof List) { (>config) .forEach((configObject) => { this._registry.config(this.hostComponent, configObject); }); } else { this._registry.config(this.hostComponent, config); } return this.renavigate(); } /** * Navigate to a URL. Returns a promise that resolves when navigation is complete. * * 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. */ navigate(url: string): Promise { if (this.navigating) { return this._currentNavigation; } this.lastNavigationAttempt = url; return this._currentNavigation = this.recognize(url).then((matchedInstruction) => { if (isBlank(matchedInstruction)) { return PromiseWrapper.resolve(false); } if (isPresent(this._currentInstruction)) { matchedInstruction.reuseComponentsFrom(this._currentInstruction); } this._startNavigating(); var result = this.commit(matchedInstruction) .then((_) => { this._finishNavigating(); ObservableWrapper.callNext(this._subject, matchedInstruction.accumulatedUrl); }); PromiseWrapper.catchError(result, (_) => this._finishNavigating()); return result; }); } _startNavigating(): void { this.navigating = true; } _finishNavigating(): void { this.navigating = false; } /** * Subscribe to URL updates from the router */ subscribe(onNext): void { ObservableWrapper.subscribe(this._subject, onNext); } /** * Updates this router and all descendant routers according to the given instruction */ commit(instruction: Instruction): Promise { this._currentInstruction = instruction; if (isPresent(this._outlet)) { return this._outlet.activate(instruction); } return PromiseWrapper.resolve(true); } /** * Removes the contents of this router's outlet and all descendant outlets */ deactivate(): Promise { if (isPresent(this._outlet)) { return this._outlet.deactivate(); } return PromiseWrapper.resolve(true); } /** * Given a URL, returns an instruction representing the component graph */ recognize(url: string): Promise { 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. */ renavigate(): Promise { var destination = isBlank(this.previousUrl) ? this.lastNavigationAttempt : this.previousUrl; if (isBlank(destination)) { return this._currentNavigation; } 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. */ generate(name: string, params: StringMap): string { return this._registry.generate(name, params, this.hostComponent); } } export class RootRouter extends Router { _location: Location; constructor(registry: RouteRegistry, pipeline: Pipeline, location: Location, hostComponent: Type) { super(registry, pipeline, null, hostComponent); this._location = location; this._location.subscribe((change) => this.navigate(change['url'])); this._registry.configFromComponent(hostComponent); this.navigate(location.path()); } commit(instruction): Promise { return super.commit(instruction) .then((_) => { this._location.go(instruction.accumulatedUrl); }); } } class ChildRouter extends Router { constructor(parent: Router, hostComponent) { super(parent._registry, parent._pipeline, parent, hostComponent); this.parent = parent; } }