parent
ef67a0c57f
commit
5a897cf299
|
@ -0,0 +1,17 @@
|
||||||
|
/**
|
||||||
|
* @module
|
||||||
|
* @description
|
||||||
|
* Alternative implementation of the router. Experimental.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export {Router, RouterOutletMap} from './src/alt_router/router';
|
||||||
|
export {RouteSegment} from './src/alt_router/segments';
|
||||||
|
export {Routes} from './src/alt_router/metadata/decorators';
|
||||||
|
export {Route} from './src/alt_router/metadata/metadata';
|
||||||
|
export {RouterUrlParser, DefaultRouterUrlParser} from './src/alt_router/router_url_parser';
|
||||||
|
export {OnActivate} from './src/alt_router/interfaces';
|
||||||
|
|
||||||
|
import {RouterOutlet} from './src/alt_router/directives/router_outlet';
|
||||||
|
import {CONST_EXPR} from './src/facade/lang';
|
||||||
|
|
||||||
|
export const ROUTER_DIRECTIVES: any[] = CONST_EXPR([RouterOutlet]);
|
|
@ -0,0 +1,34 @@
|
||||||
|
import {
|
||||||
|
ResolvedReflectiveProvider,
|
||||||
|
Directive,
|
||||||
|
DynamicComponentLoader,
|
||||||
|
ViewContainerRef,
|
||||||
|
Input,
|
||||||
|
ComponentRef,
|
||||||
|
ComponentFactory,
|
||||||
|
ReflectiveInjector
|
||||||
|
} from 'angular2/core';
|
||||||
|
import {RouterOutletMap} from '../router';
|
||||||
|
import {isPresent} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
@Directive({selector: 'router-outlet'})
|
||||||
|
export class RouterOutlet {
|
||||||
|
private _loaded: ComponentRef;
|
||||||
|
public outletMap: RouterOutletMap;
|
||||||
|
@Input() name: string = "";
|
||||||
|
|
||||||
|
constructor(parentOutletMap: RouterOutletMap, private _location: ViewContainerRef) {
|
||||||
|
parentOutletMap.registerOutlet("", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
load(factory: ComponentFactory, providers: ResolvedReflectiveProvider[],
|
||||||
|
outletMap: RouterOutletMap): ComponentRef {
|
||||||
|
if (isPresent(this._loaded)) {
|
||||||
|
this._loaded.destroy();
|
||||||
|
}
|
||||||
|
this.outletMap = outletMap;
|
||||||
|
let inj = ReflectiveInjector.fromResolvedProviders(providers, this._location.parentInjector);
|
||||||
|
this._loaded = this._location.createComponent(factory, this._location.length, inj, []);
|
||||||
|
return this._loaded;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import {RouteSegment, Tree} from './segments';
|
||||||
|
|
||||||
|
export interface OnActivate {
|
||||||
|
routerOnActivate(curr: RouteSegment, prev?: RouteSegment, currTree?: Tree<RouteSegment>,
|
||||||
|
prevTree?: Tree<RouteSegment>): void;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import './interfaces.dart';
|
||||||
|
bool hasLifecycleHook(String name, Object obj) {
|
||||||
|
if (name == "routerOnActivate") return obj is OnActivate;
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import {Type} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
export function hasLifecycleHook(name: string, obj: Object): boolean {
|
||||||
|
let type = obj.constructor;
|
||||||
|
if (!(type instanceof Type)) return false;
|
||||||
|
return name in(<any>type).prototype;
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
import {provide, ReflectiveInjector, ComponentResolver} from 'angular2/core';
|
||||||
|
import {RouterOutlet} from './directives/router_outlet';
|
||||||
|
import {Type, isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||||
|
import {RouterUrlParser} from './router_url_parser';
|
||||||
|
import {recognize} from './recognize';
|
||||||
|
import {equalSegments, routeSegmentComponentFactory, RouteSegment, Tree} from './segments';
|
||||||
|
import {hasLifecycleHook} from './lifecycle_reflector';
|
||||||
|
|
||||||
|
export class RouterOutletMap {
|
||||||
|
/** @internal */
|
||||||
|
_outlets: {[name: string]: RouterOutlet} = {};
|
||||||
|
registerOutlet(name: string, outlet: RouterOutlet): void { this._outlets[name] = outlet; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Router {
|
||||||
|
private prevTree: Tree<RouteSegment>;
|
||||||
|
constructor(private _componentType: Type, private _componentResolver: ComponentResolver,
|
||||||
|
private _urlParser: RouterUrlParser, private _routerOutletMap: RouterOutletMap) {}
|
||||||
|
|
||||||
|
navigateByUrl(url: string): Promise<void> {
|
||||||
|
let urlSegmentTree = this._urlParser.parse(url.substring(1));
|
||||||
|
return recognize(this._componentResolver, this._componentType, urlSegmentTree)
|
||||||
|
.then(currTree => {
|
||||||
|
let prevRoot = isPresent(this.prevTree) ? this.prevTree.root : null;
|
||||||
|
_loadSegments(currTree, currTree.root, this.prevTree, prevRoot, this,
|
||||||
|
this._routerOutletMap);
|
||||||
|
this.prevTree = currTree;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _loadSegments(currTree: Tree<RouteSegment>, curr: RouteSegment,
|
||||||
|
prevTree: Tree<RouteSegment>, prev: RouteSegment, router: Router,
|
||||||
|
parentOutletMap: RouterOutletMap): void {
|
||||||
|
let outlet = parentOutletMap._outlets[curr.outlet];
|
||||||
|
|
||||||
|
let outletMap;
|
||||||
|
if (equalSegments(curr, prev)) {
|
||||||
|
outletMap = outlet.outletMap;
|
||||||
|
} else {
|
||||||
|
outletMap = new RouterOutletMap();
|
||||||
|
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, currTree, prevTree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPresent(currTree.firstChild(curr))) {
|
||||||
|
let cc = currTree.firstChild(curr);
|
||||||
|
let pc = isBlank(prevTree) ? null : prevTree.firstChild(prev);
|
||||||
|
_loadSegments(currTree, cc, prevTree, pc, router, outletMap);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
import {
|
||||||
|
ComponentFixture,
|
||||||
|
AsyncTestCompleter,
|
||||||
|
TestComponentBuilder,
|
||||||
|
beforeEach,
|
||||||
|
ddescribe,
|
||||||
|
xdescribe,
|
||||||
|
describe,
|
||||||
|
el,
|
||||||
|
expect,
|
||||||
|
iit,
|
||||||
|
inject,
|
||||||
|
beforeEachProviders,
|
||||||
|
it,
|
||||||
|
xit
|
||||||
|
} from 'angular2/testing_internal';
|
||||||
|
import {provide, Component, ComponentResolver} from 'angular2/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Router,
|
||||||
|
RouterOutletMap,
|
||||||
|
RouteSegment,
|
||||||
|
Route,
|
||||||
|
ROUTER_DIRECTIVES,
|
||||||
|
Routes,
|
||||||
|
RouterUrlParser,
|
||||||
|
DefaultRouterUrlParser,
|
||||||
|
OnActivate
|
||||||
|
} from 'angular2/alt_router';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('navigation', () => {
|
||||||
|
beforeEachProviders(() => [
|
||||||
|
provide(RouterUrlParser, {useClass: DefaultRouterUrlParser}),
|
||||||
|
RouterOutletMap,
|
||||||
|
provide(Router,
|
||||||
|
{
|
||||||
|
useFactory: (resolver, urlParser, outletMap) =>
|
||||||
|
new Router(RootCmp, resolver, urlParser, outletMap),
|
||||||
|
deps: [ComponentResolver, RouterUrlParser, RouterOutletMap]
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
it('should support nested routes',
|
||||||
|
inject([AsyncTestCompleter, Router, TestComponentBuilder], (async, router, tcb) => {
|
||||||
|
let fixture;
|
||||||
|
compileRoot(tcb)
|
||||||
|
.then((rtc) => {fixture = rtc})
|
||||||
|
.then((_) => router.navigateByUrl('/team/22/user/victor'))
|
||||||
|
.then((_) => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.debugElement.nativeElement).toHaveText('team 22 { hello victor }');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should update nested routes when url changes',
|
||||||
|
inject([AsyncTestCompleter, Router, TestComponentBuilder], (async, router, tcb) => {
|
||||||
|
let fixture;
|
||||||
|
let team1;
|
||||||
|
let team2;
|
||||||
|
compileRoot(tcb)
|
||||||
|
.then((rtc) => {fixture = rtc})
|
||||||
|
.then((_) => router.navigateByUrl('/team/22/user/victor'))
|
||||||
|
.then((_) => { team1 = fixture.debugElement.children[1].componentInstance; })
|
||||||
|
.then((_) => router.navigateByUrl('/team/22/user/fedor'))
|
||||||
|
.then((_) => { team2 = fixture.debugElement.children[1].componentInstance; })
|
||||||
|
.then((_) => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(team1).toBe(team2);
|
||||||
|
expect(fixture.debugElement.nativeElement).toHaveText('team 22 { hello fedor }');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileRoot(tcb: TestComponentBuilder): Promise<ComponentFixture> {
|
||||||
|
return tcb.createAsync(RootCmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'user-cmp', template: `hello {{user}}`})
|
||||||
|
class UserCmp implements OnActivate {
|
||||||
|
user: string;
|
||||||
|
routerOnActivate(s: RouteSegment, a?, b?, c?) { this.user = s.getParam('name'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'team-cmp',
|
||||||
|
template: `team {{id}} { <router-outlet></router-outlet> }`,
|
||||||
|
directives: [ROUTER_DIRECTIVES]
|
||||||
|
})
|
||||||
|
@Routes([new Route({path: 'user/:name', component: UserCmp})])
|
||||||
|
class TeamCmp implements OnActivate {
|
||||||
|
id: string;
|
||||||
|
routerOnActivate(s: RouteSegment, a?, b?, c?) { this.id = s.getParam('id'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'root-cmp',
|
||||||
|
template: `<router-outlet></router-outlet>`,
|
||||||
|
directives: [ROUTER_DIRECTIVES]
|
||||||
|
})
|
||||||
|
@Routes([new Route({path: 'team/:id', component: TeamCmp})])
|
||||||
|
class RootCmp {
|
||||||
|
}
|
Loading…
Reference in New Issue