2016-04-28 18:33:48 -07:00
|
|
|
import {OnInit, provide, ReflectiveInjector, ComponentResolver} from 'angular2/core';
|
2016-04-22 12:05:38 -07:00
|
|
|
import {RouterOutlet} from './directives/router_outlet';
|
|
|
|
|
import {Type, isBlank, isPresent} from 'angular2/src/facade/lang';
|
2016-04-29 18:04:55 -07:00
|
|
|
import {ListWrapper} from 'angular2/src/facade/collection';
|
2016-04-30 11:57:02 -07:00
|
|
|
import {
|
|
|
|
|
EventEmitter,
|
|
|
|
|
Observable,
|
|
|
|
|
PromiseWrapper,
|
|
|
|
|
ObservableWrapper
|
|
|
|
|
} from 'angular2/src/facade/async';
|
2016-04-25 16:57:27 -07:00
|
|
|
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
|
|
|
|
import {BaseException} from 'angular2/src/facade/exceptions';
|
2016-04-27 15:37:20 -07:00
|
|
|
import {RouterUrlSerializer} from './router_url_serializer';
|
2016-04-29 18:04:55 -07:00
|
|
|
import {CanDeactivate} from './interfaces';
|
2016-04-22 12:05:38 -07:00
|
|
|
import {recognize} from './recognize';
|
2016-04-28 18:33:48 -07:00
|
|
|
import {Location} from 'angular2/platform/common';
|
2016-04-29 18:04:55 -07:00
|
|
|
import {link} from './link';
|
|
|
|
|
|
2016-04-25 16:57:27 -07:00
|
|
|
import {
|
|
|
|
|
equalSegments,
|
|
|
|
|
routeSegmentComponentFactory,
|
|
|
|
|
RouteSegment,
|
2016-05-01 13:45:14 -07:00
|
|
|
UrlTree,
|
|
|
|
|
RouteTree,
|
2016-04-25 16:57:27 -07:00
|
|
|
rootNode,
|
2016-04-27 15:37:20 -07:00
|
|
|
TreeNode,
|
|
|
|
|
UrlSegment,
|
|
|
|
|
serializeRouteSegmentTree
|
2016-04-25 16:57:27 -07:00
|
|
|
} from './segments';
|
2016-04-22 12:05:38 -07:00
|
|
|
import {hasLifecycleHook} from './lifecycle_reflector';
|
2016-04-25 16:57:27 -07:00
|
|
|
import {DEFAULT_OUTLET_NAME} from './constants';
|
2016-04-22 12:05:38 -07:00
|
|
|
|
|
|
|
|
export class RouterOutletMap {
|
|
|
|
|
/** @internal */
|
|
|
|
|
_outlets: {[name: string]: RouterOutlet} = {};
|
|
|
|
|
registerOutlet(name: string, outlet: RouterOutlet): void { this._outlets[name] = outlet; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class Router {
|
2016-05-01 13:45:14 -07:00
|
|
|
private _prevTree: RouteTree;
|
|
|
|
|
private _urlTree: UrlTree;
|
2016-04-30 11:57:02 -07:00
|
|
|
private _locationSubscription: any;
|
2016-04-27 15:37:20 -07:00
|
|
|
private _changes: EventEmitter<void> = new EventEmitter<void>();
|
|
|
|
|
|
2016-04-29 18:04:55 -07:00
|
|
|
constructor(private _rootComponent: Object, private _rootComponentType: Type,
|
|
|
|
|
private _componentResolver: ComponentResolver,
|
2016-04-27 15:37:20 -07:00
|
|
|
private _urlSerializer: RouterUrlSerializer,
|
2016-04-28 18:33:48 -07:00
|
|
|
private _routerOutletMap: RouterOutletMap, private _location: Location) {
|
2016-05-01 14:33:53 -07:00
|
|
|
this._prevTree = this._createInitialTree();
|
2016-04-30 11:57:02 -07:00
|
|
|
this._setUpLocationChangeListener();
|
2016-04-28 18:33:48 -07:00
|
|
|
this.navigateByUrl(this._location.path());
|
2016-04-28 08:01:27 -07:00
|
|
|
}
|
2016-04-22 12:05:38 -07:00
|
|
|
|
2016-05-01 13:45:14 -07:00
|
|
|
get urlTree(): UrlTree { return this._urlTree; }
|
2016-04-27 15:37:20 -07:00
|
|
|
|
2016-04-29 18:04:55 -07:00
|
|
|
navigateByUrl(url: string): Promise<void> {
|
|
|
|
|
return this._navigate(this._urlSerializer.parse(url));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
navigate(changes: any[], segment?: RouteSegment): Promise<void> {
|
|
|
|
|
return this._navigate(this.createUrlTree(changes, segment));
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-30 11:57:02 -07:00
|
|
|
dispose(): void { ObservableWrapper.dispose(this._locationSubscription); }
|
|
|
|
|
|
2016-05-01 14:33:53 -07:00
|
|
|
private _createInitialTree(): RouteTree {
|
|
|
|
|
let root = new RouteSegment([new UrlSegment("", null, null)], null, DEFAULT_OUTLET_NAME,
|
|
|
|
|
this._rootComponentType, null);
|
|
|
|
|
return new RouteTree(new TreeNode<RouteSegment>(root, []));
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-30 11:57:02 -07:00
|
|
|
private _setUpLocationChangeListener(): void {
|
|
|
|
|
this._locationSubscription = this._location.subscribe(
|
|
|
|
|
(change) => { this._navigate(this._urlSerializer.parse(change['url'])); });
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-01 13:45:14 -07:00
|
|
|
private _navigate(url: UrlTree): Promise<void> {
|
2016-04-27 15:37:20 -07:00
|
|
|
this._urlTree = url;
|
2016-04-29 18:04:55 -07:00
|
|
|
return recognize(this._componentResolver, this._rootComponentType, url)
|
2016-04-22 12:05:38 -07:00
|
|
|
.then(currTree => {
|
2016-04-29 18:04:55 -07:00
|
|
|
return new _LoadSegments(currTree, this._prevTree)
|
|
|
|
|
.load(this._routerOutletMap, this._rootComponent)
|
2016-04-30 10:33:14 -07:00
|
|
|
.then(updated => {
|
|
|
|
|
if (updated) {
|
|
|
|
|
this._prevTree = currTree;
|
|
|
|
|
this._location.go(this._urlSerializer.serialize(this._urlTree));
|
|
|
|
|
this._changes.emit(null);
|
|
|
|
|
}
|
2016-04-29 18:04:55 -07:00
|
|
|
});
|
2016-04-22 12:05:38 -07:00
|
|
|
});
|
|
|
|
|
}
|
2016-04-27 15:37:20 -07:00
|
|
|
|
2016-05-01 13:45:14 -07:00
|
|
|
createUrlTree(changes: any[], segment?: RouteSegment): UrlTree {
|
2016-04-29 18:04:55 -07:00
|
|
|
if (isPresent(this._prevTree)) {
|
|
|
|
|
let s = isPresent(segment) ? segment : this._prevTree.root;
|
|
|
|
|
return link(s, this._prevTree, this.urlTree, changes);
|
|
|
|
|
} else {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2016-04-27 15:37:20 -07:00
|
|
|
}
|
|
|
|
|
|
2016-05-01 13:45:14 -07:00
|
|
|
serializeUrl(url: UrlTree): string { return this._urlSerializer.serialize(url); }
|
2016-04-29 18:04:55 -07:00
|
|
|
|
2016-04-27 15:37:20 -07:00
|
|
|
get changes(): Observable<void> { return this._changes; }
|
2016-04-29 18:04:55 -07:00
|
|
|
|
2016-05-01 13:45:14 -07:00
|
|
|
get routeTree(): RouteTree { return this._prevTree; }
|
2016-04-22 12:05:38 -07:00
|
|
|
}
|
|
|
|
|
|
2016-04-29 18:04:55 -07:00
|
|
|
|
2016-04-27 15:37:20 -07:00
|
|
|
class _LoadSegments {
|
2016-04-29 18:04:55 -07:00
|
|
|
private deactivations: Object[][] = [];
|
|
|
|
|
private performMutation: boolean = true;
|
|
|
|
|
|
2016-05-01 13:45:14 -07:00
|
|
|
constructor(private currTree: RouteTree, private prevTree: RouteTree) {}
|
2016-04-22 12:05:38 -07:00
|
|
|
|
2016-04-30 10:33:14 -07:00
|
|
|
load(parentOutletMap: RouterOutletMap, rootComponent: Object): Promise<boolean> {
|
2016-04-28 18:33:48 -07:00
|
|
|
let prevRoot = isPresent(this.prevTree) ? rootNode(this.prevTree) : null;
|
|
|
|
|
let currRoot = rootNode(this.currTree);
|
2016-04-29 18:04:55 -07:00
|
|
|
|
|
|
|
|
return this.canDeactivate(currRoot, prevRoot, parentOutletMap, rootComponent)
|
|
|
|
|
.then(res => {
|
|
|
|
|
this.performMutation = true;
|
|
|
|
|
if (res) {
|
|
|
|
|
this.loadChildSegments(currRoot, prevRoot, parentOutletMap, [rootComponent]);
|
|
|
|
|
}
|
2016-04-30 10:33:14 -07:00
|
|
|
return res;
|
2016-04-29 18:04:55 -07:00
|
|
|
});
|
2016-04-28 18:33:48 -07:00
|
|
|
}
|
|
|
|
|
|
2016-04-29 18:04:55 -07:00
|
|
|
private canDeactivate(currRoot: TreeNode<RouteSegment>, prevRoot: TreeNode<RouteSegment>,
|
|
|
|
|
outletMap: RouterOutletMap, rootComponent: Object): Promise<boolean> {
|
|
|
|
|
this.performMutation = false;
|
|
|
|
|
this.loadChildSegments(currRoot, prevRoot, outletMap, [rootComponent]);
|
2016-04-25 16:57:27 -07:00
|
|
|
|
2016-04-29 18:04:55 -07:00
|
|
|
let allPaths = PromiseWrapper.all(this.deactivations.map(r => this.checkCanDeactivatePath(r)));
|
|
|
|
|
return allPaths.then((values: boolean[]) => values.filter(v => v).length === values.length);
|
2016-04-25 16:57:27 -07:00
|
|
|
}
|
|
|
|
|
|
2016-04-29 18:04:55 -07:00
|
|
|
private checkCanDeactivatePath(path: Object[]): Promise<boolean> {
|
|
|
|
|
let curr = PromiseWrapper.resolve(true);
|
|
|
|
|
for (let p of ListWrapper.reversed(path)) {
|
|
|
|
|
curr = curr.then(_ => {
|
|
|
|
|
if (hasLifecycleHook("routerCanDeactivate", p)) {
|
|
|
|
|
return (<CanDeactivate>p).routerCanDeactivate(this.prevTree, this.currTree);
|
|
|
|
|
} else {
|
|
|
|
|
return _;
|
|
|
|
|
}
|
|
|
|
|
});
|
2016-04-25 16:57:27 -07:00
|
|
|
}
|
2016-04-29 18:04:55 -07:00
|
|
|
return curr;
|
2016-04-25 16:57:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private loadChildSegments(currNode: TreeNode<RouteSegment>, prevNode: TreeNode<RouteSegment>,
|
2016-04-29 18:04:55 -07:00
|
|
|
outletMap: RouterOutletMap, components: Object[]): void {
|
2016-04-25 16:57:27 -07:00
|
|
|
let prevChildren = isPresent(prevNode) ?
|
|
|
|
|
prevNode.children.reduce(
|
|
|
|
|
(m, c) => {
|
|
|
|
|
m[c.value.outlet] = c;
|
|
|
|
|
return m;
|
|
|
|
|
},
|
|
|
|
|
{}) :
|
|
|
|
|
{};
|
|
|
|
|
|
|
|
|
|
currNode.children.forEach(c => {
|
2016-04-29 18:04:55 -07:00
|
|
|
this.loadSegments(c, prevChildren[c.value.outlet], outletMap, components);
|
2016-04-25 16:57:27 -07:00
|
|
|
StringMapWrapper.delete(prevChildren, c.value.outlet);
|
|
|
|
|
});
|
|
|
|
|
|
2016-04-29 18:04:55 -07:00
|
|
|
StringMapWrapper.forEach(prevChildren,
|
|
|
|
|
(v, k) => this.unloadOutlet(outletMap._outlets[k], components));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadSegments(currNode: TreeNode<RouteSegment>, prevNode: TreeNode<RouteSegment>,
|
|
|
|
|
parentOutletMap: RouterOutletMap, components: Object[]): void {
|
|
|
|
|
let curr = currNode.value;
|
|
|
|
|
let prev = isPresent(prevNode) ? prevNode.value : null;
|
|
|
|
|
let outlet = this.getOutlet(parentOutletMap, currNode.value);
|
|
|
|
|
|
|
|
|
|
if (equalSegments(curr, prev)) {
|
|
|
|
|
this.loadChildSegments(currNode, prevNode, outlet.outletMap,
|
|
|
|
|
components.concat([outlet.loadedComponent]));
|
|
|
|
|
} else {
|
|
|
|
|
this.unloadOutlet(outlet, components);
|
|
|
|
|
if (this.performMutation) {
|
|
|
|
|
let outletMap = new RouterOutletMap();
|
|
|
|
|
let loadedComponent = this.loadNewSegment(outletMap, curr, prev, outlet);
|
|
|
|
|
this.loadChildSegments(currNode, prevNode, outletMap, components.concat([loadedComponent]));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private loadNewSegment(outletMap: RouterOutletMap, curr: RouteSegment, prev: RouteSegment,
|
|
|
|
|
outlet: RouterOutlet): Object {
|
|
|
|
|
let resolved = ReflectiveInjector.resolve(
|
|
|
|
|
[provide(RouterOutletMap, {useValue: outletMap}), provide(RouteSegment, {useValue: curr})]);
|
|
|
|
|
let ref = outlet.load(routeSegmentComponentFactory(curr), resolved, outletMap);
|
|
|
|
|
if (hasLifecycleHook("routerOnActivate", ref.instance)) {
|
|
|
|
|
ref.instance.routerOnActivate(curr, prev, this.currTree, this.prevTree);
|
|
|
|
|
}
|
|
|
|
|
return ref.instance;
|
2016-04-25 16:57:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getOutlet(outletMap: RouterOutletMap, segment: RouteSegment): RouterOutlet {
|
|
|
|
|
let outlet = outletMap._outlets[segment.outlet];
|
|
|
|
|
if (isBlank(outlet)) {
|
|
|
|
|
if (segment.outlet == DEFAULT_OUTLET_NAME) {
|
|
|
|
|
throw new BaseException(`Cannot find default outlet`);
|
|
|
|
|
} else {
|
|
|
|
|
throw new BaseException(`Cannot find the outlet ${segment.outlet}`);
|
|
|
|
|
}
|
2016-04-22 12:05:38 -07:00
|
|
|
}
|
2016-04-25 16:57:27 -07:00
|
|
|
return outlet;
|
2016-04-22 12:05:38 -07:00
|
|
|
}
|
|
|
|
|
|
2016-04-29 18:04:55 -07:00
|
|
|
private unloadOutlet(outlet: RouterOutlet, components: Object[]): void {
|
2016-04-30 10:33:14 -07:00
|
|
|
if (isPresent(outlet) && outlet.isLoaded) {
|
2016-04-29 18:04:55 -07:00
|
|
|
StringMapWrapper.forEach(outlet.outletMap._outlets,
|
|
|
|
|
(v, k) => this.unloadOutlet(v, components));
|
|
|
|
|
if (this.performMutation) {
|
|
|
|
|
outlet.unload();
|
|
|
|
|
} else {
|
|
|
|
|
this.deactivations.push(components.concat([outlet.loadedComponent]));
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-04-22 12:05:38 -07:00
|
|
|
}
|
|
|
|
|
}
|