forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(router): add Router and RouterOutlet
Closes angular#8173
- Loading branch information
Showing
7 changed files
with
230 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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]); |
34 changes: 34 additions & 0 deletions
34
modules/angular2/src/alt_router/directives/router_outlet.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import {RouteSegment, Tree} from './segments'; | ||
|
||
export interface OnActivate { | ||
routerOnActivate(curr: RouteSegment, prev?: RouteSegment, currTree?: Tree<RouteSegment>, | ||
prevTree?: Tree<RouteSegment>): void; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import './interfaces.dart'; | ||
bool hasLifecycleHook(String name, Object obj) { | ||
if (name == "routerOnActivate") return obj is OnActivate; | ||
return false; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 { | ||
} |