diff --git a/modules/angular2/src/router/route_config.js b/modules/angular2/src/router/route_config.js index 73b4dc0d24..688a843123 100644 --- a/modules/angular2/src/router/route_config.js +++ b/modules/angular2/src/router/route_config.js @@ -1,13 +1,18 @@ import {CONST} from 'angular2/src/facade/lang'; -import {List} from 'angular2/src/facade/collection'; +import {List, Map} from 'angular2/src/facade/collection'; /** - * You use the RouteConfig annotation to ... + * You use the RouteConfig annotation to add routes to a component. + * + * Supported keys: + * - `path` (required) + * - `component` or `components` (requires exactly one of these) + * - `as` (optional) */ export class RouteConfig { - configs; + configs:List; @CONST() - constructor(configs:List) { + constructor(configs:List) { this.configs = configs; } } diff --git a/modules/angular2/src/router/route_registry.js b/modules/angular2/src/router/route_registry.js index b56e90c962..d1c7075dc7 100644 --- a/modules/angular2/src/router/route_registry.js +++ b/modules/angular2/src/router/route_registry.js @@ -1,12 +1,10 @@ import {RouteRecognizer} from './route_recognizer'; import {Instruction, noopInstruction} from './instruction'; import {List, ListWrapper, Map, MapWrapper, StringMap, StringMapWrapper} from 'angular2/src/facade/collection'; -import {isPresent, isBlank, isType, StringWrapper, CONST} from 'angular2/src/facade/lang'; +import {isPresent, isBlank, isType, StringWrapper, BaseException} from 'angular2/src/facade/lang'; import {RouteConfig} from './route_config'; import {reflector} from 'angular2/src/reflection/reflection'; -export const rootHostComponent = 'ROOT_HOST'; - export class RouteRegistry { _rules:Map; @@ -28,13 +26,13 @@ export class RouteRegistry { var components = StringMapWrapper.get(config, 'components'); StringMapWrapper.forEach(components, (component, _) => { - this._configFromComponent(component); + this.configFromComponent(component); }); recognizer.addConfig(config['path'], config, config['alias']); } - _configFromComponent(component) { + configFromComponent(component) { if (!isType(component)) { return; } @@ -59,9 +57,7 @@ export class RouteRegistry { } - // TODO: make recognized context a class - // TODO: change parentComponent into parentContext - recognize(url:string, parentComponent = rootHostComponent) { + recognize(url:string, parentComponent) { var componentRecognizer = MapWrapper.get(this._rules, parentComponent); if (isBlank(componentRecognizer)) { return null; @@ -103,9 +99,9 @@ export class RouteRegistry { return null; } - generate(name:string, params:any) { + generate(name:string, params:any, hostComponent) { //TODO: implement for hierarchical routes - var componentRecognizer = MapWrapper.get(this._rules, rootHostComponent); + var componentRecognizer = MapWrapper.get(this._rules, hostComponent); if (isPresent(componentRecognizer)) { return componentRecognizer.generate(name, params); } @@ -148,7 +144,7 @@ function normalizeConfig(config:StringMap) { return newConfig; } else if (!StringMapWrapper.contains(config, 'components')) { - throw new Error('Config does not include a "component" or "components" key.'); + throw new BaseException('Config does not include a "component" or "components" key.'); } return config; } diff --git a/modules/angular2/src/router/router.js b/modules/angular2/src/router/router.js index abdebd6ad0..8983b4df51 100644 --- a/modules/angular2/src/router/router.js +++ b/modules/angular2/src/router/router.js @@ -1,8 +1,8 @@ import {Promise, PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; import {Map, MapWrapper, List, ListWrapper} from 'angular2/src/facade/collection'; -import {isBlank} from 'angular2/src/facade/lang'; +import {isBlank, Type} from 'angular2/src/facade/lang'; -import {RouteRegistry, rootHostComponent} from './route_registry'; +import {RouteRegistry} from './route_registry'; import {Pipeline} from './pipeline'; import {Instruction} from './instruction'; import {RouterOutlet} from './router_outlet'; @@ -42,7 +42,6 @@ export class Router { this._registry = registry; this._pipeline = pipeline; this._subject = new EventEmitter(); - //this._location.subscribe((url) => this.navigate(url)); } @@ -86,10 +85,8 @@ export class Router { * */ config(config:any) { - - //TODO: use correct check if (config instanceof List) { - path.forEach((configObject) => { + config.forEach((configObject) => { // TODO: this is a hack this._registry.config(this.hostComponent, configObject); }) @@ -172,7 +169,7 @@ export class Router { * Given a URL, returns an instruction representing the component graph */ recognize(url:string) { - return this._registry.recognize(url); + return this._registry.recognize(url, this.hostComponent); } @@ -192,17 +189,15 @@ export class Router { * 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:any) { - return this._registry.generate(name, params); - } - - static getRoot():Router { - return new RootRouter(new Pipeline(), new Location()); + return this._registry.generate(name, params, this.hostComponent); } } export class RootRouter extends Router { - constructor(pipeline:Pipeline, location:Location) { - super(new RouteRegistry(), pipeline, location, null, rootHostComponent); + constructor(registry:RouteRegistry, pipeline:Pipeline, location:Location, hostComponent:Type) { + super(registry, pipeline, location, null, hostComponent); + this._location.subscribe((url) => this.navigate(url)); + this._registry.configFromComponent(hostComponent); this.navigate(location.path()); } } diff --git a/modules/angular2/test/router/outlet_spec.js b/modules/angular2/test/router/outlet_spec.js index 6c8ab5425a..682fd6679d 100644 --- a/modules/angular2/test/router/outlet_spec.js +++ b/modules/angular2/test/router/outlet_spec.js @@ -26,24 +26,31 @@ import {Router, RouterOutlet, RouterLink, RouteConfig, RouteParams} from 'angula import {DOM} from 'angular2/src/dom/dom_adapter'; import {DummyLocation} from 'angular2/src/mock/location_mock'; +import {Location} from 'angular2/src/router/location'; +import {RouteRegistry} from 'angular2/src/router/route_registry'; +import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; export function main() { describe('Outlet Directive', () => { - var ctx, tb, view, router; + var ctx, tb, view, rtr; - beforeEach(inject([TestBed], (testBed) => { + beforeEachBindings(() => [ + Pipeline, + RouteRegistry, + DirectiveMetadataReader, + bind(Location).toClass(DummyLocation), + bind(Router).toFactory((registry, pipeline, location) => { + return new RootRouter(registry, pipeline, location, MyComp); + }, [RouteRegistry, Pipeline, Location]) + ]); + + beforeEach(inject([TestBed, Router], (testBed, router) => { tb = testBed; ctx = new MyComp(); + rtr = router; })); - beforeEachBindings(() => { - router = new RootRouter(new Pipeline(), new DummyLocation()); - return [ - bind(Router).toValue(router) - ]; - }); - function compile(template:string = "") { tb.overrideView(MyComp, new View({template: ('
' + template + '
'), directives: [RouterOutlet, RouterLink]})); return tb.createView(MyComp, {context: ctx}).then((v) => { @@ -53,8 +60,8 @@ export function main() { it('should work in a simple case', inject([AsyncTestCompleter], (async) => { compile() - .then((_) => router.config({'path': '/test', 'component': HelloCmp})) - .then((_) => router.navigate('/test')) + .then((_) => rtr.config({'path': '/test', 'component': HelloCmp})) + .then((_) => rtr.navigate('/test')) .then((_) => { view.detectChanges(); expect(view.rootNodes).toHaveText('hello'); @@ -65,13 +72,13 @@ export function main() { it('should navigate between components with different parameters', inject([AsyncTestCompleter], (async) => { compile() - .then((_) => router.config({'path': '/user/:name', 'component': UserCmp})) - .then((_) => router.navigate('/user/brian')) + .then((_) => rtr.config({'path': '/user/:name', 'component': UserCmp})) + .then((_) => rtr.navigate('/user/brian')) .then((_) => { view.detectChanges(); expect(view.rootNodes).toHaveText('hello brian'); }) - .then((_) => router.navigate('/user/igor')) + .then((_) => rtr.navigate('/user/igor')) .then((_) => { view.detectChanges(); expect(view.rootNodes).toHaveText('hello igor'); @@ -82,8 +89,8 @@ export function main() { it('should work with child routers', inject([AsyncTestCompleter], (async) => { compile('outer { }') - .then((_) => router.config({'path': '/a', 'component': ParentCmp})) - .then((_) => router.navigate('/a/b')) + .then((_) => rtr.config({'path': '/a', 'component': ParentCmp})) + .then((_) => rtr.navigate('/a/b')) .then((_) => { view.detectChanges(); expect(view.rootNodes).toHaveText('outer { inner { hello } }'); @@ -95,8 +102,8 @@ export function main() { it('should generate link hrefs', inject([AsyncTestCompleter], (async) => { ctx.name = 'brian'; compile('{{name}}') - .then((_) => router.config({'path': '/user/:name', 'component': UserCmp, 'alias': 'user'})) - .then((_) => router.navigate('/a/b')) + .then((_) => rtr.config({'path': '/user/:name', 'component': UserCmp, 'alias': 'user'})) + .then((_) => rtr.navigate('/a/b')) .then((_) => { view.detectChanges(); expect(view.rootNodes).toHaveText('brian'); diff --git a/modules/angular2/test/router/route_registry_spec.js b/modules/angular2/test/router/route_registry_spec.js index d6f0178382..8d11c73960 100644 --- a/modules/angular2/test/router/route_registry_spec.js +++ b/modules/angular2/test/router/route_registry_spec.js @@ -6,12 +6,13 @@ import { inject, beforeEach, SpyObject} from 'angular2/test_lib'; -import {RouteRegistry, rootHostComponent} from 'angular2/src/router/route_registry'; +import {RouteRegistry} from 'angular2/src/router/route_registry'; import {RouteConfig} from 'angular2/src/router/route_config'; export function main() { describe('RouteRegistry', () => { - var registry; + var registry, + rootHostComponent = new Object(); beforeEach(() => { registry = new RouteRegistry(); @@ -21,7 +22,7 @@ export function main() { registry.config(rootHostComponent, {'path': '/', 'component': DummyCompA}); registry.config(rootHostComponent, {'path': '/test', 'component': DummyCompB}); - var instruction = registry.recognize('/test'); + var instruction = registry.recognize('/test', rootHostComponent); expect(instruction.getChildInstruction('default').component).toBe(DummyCompB); }); @@ -29,7 +30,7 @@ export function main() { it('should match the full URL recursively', () => { registry.config(rootHostComponent, {'path': '/first', 'component': DummyParentComp}); - var instruction = registry.recognize('/first/second'); + var instruction = registry.recognize('/first/second', rootHostComponent); var parentInstruction = instruction.getChildInstruction('default'); var childInstruction = parentInstruction.getChildInstruction('default'); diff --git a/modules/angular2/test/router/router_spec.js b/modules/angular2/test/router/router_spec.js index 8144360a4f..7052e3d150 100644 --- a/modules/angular2/test/router/router_spec.js +++ b/modules/angular2/test/router/router_spec.js @@ -4,25 +4,42 @@ import { proxy, it, iit, ddescribe, expect, - inject, beforeEach, + inject, beforeEach, beforeEachBindings, SpyObject} from 'angular2/test_lib'; import {IMPLEMENTS} from 'angular2/src/facade/lang'; import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; -import {RootRouter} from 'angular2/src/router/router'; +import {Router, RootRouter} from 'angular2/src/router/router'; import {Pipeline} from 'angular2/src/router/pipeline'; import {RouterOutlet} from 'angular2/src/router/router_outlet'; import {DummyLocation} from 'angular2/src/mock/location_mock' +import {Location} from 'angular2/src/router/location'; + +import {RouteRegistry} from 'angular2/src/router/route_registry'; +import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; + +import {bind} from 'angular2/di'; export function main() { describe('Router', () => { var router, location; - beforeEach(() => { - location = new DummyLocation(); - router = new RootRouter(new Pipeline(), location); - }); + beforeEachBindings(() => [ + Pipeline, + RouteRegistry, + DirectiveMetadataReader, + bind(Location).toClass(DummyLocation), + bind(Router).toFactory((registry, pipeline, location) => { + return new RootRouter(registry, pipeline, location, AppCmp); + }, [RouteRegistry, Pipeline, Location]) + ]); + + + beforeEach(inject([Router, Location], (rtr, loc) => { + router = rtr; + location = loc; + })); it('should navigate based on the initial URL state', inject([AsyncTestCompleter], (async) => { @@ -82,3 +99,5 @@ function makeDummyRef() { ref.spy('deactivate').andCallFake((_) => PromiseWrapper.resolve(true)); return ref; } + +class AppCmp {}