feat(router): add location service

This commit is contained in:
Brian Ford 2015-04-21 11:23:23 -07:00
parent cf32213079
commit ea546f5069
5 changed files with 149 additions and 10 deletions

View File

@ -0,0 +1,58 @@
import {SpyObject, proxy} from 'angular2/test_lib';
import {isBlank, isPresent, IMPLEMENTS} from 'angular2/src/facade/lang';
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {Location} from 'angular2/src/router/location';
@proxy
@IMPLEMENTS(Location)
export class DummyLocation extends SpyObject {
urlChanges:List<string>;
_path:string;
_subject:EventEmitter;
constructor() {
super();
this._path = '/';
this.urlChanges = ListWrapper.create();
this._subject = new EventEmitter();
}
setInitialPath(url:string) {
this._path = url;
}
path():string {
return this._path;
}
simulateUrlPop(pathname:string) {
ObservableWrapper.callNext(this._subject, {
'url': pathname
});
}
go(url:string) {
if (this._path === url) {
return;
}
this._path = url;
ListWrapper.push(this.urlChanges, url);
}
forward() {
// TODO
}
back() {
// TODO
}
subscribe(onNext, onThrow = null, onReturn = null) {
ObservableWrapper.subscribe(this._subject, onNext, onThrow, onReturn);
}
noSuchMethod(m){return super.noSuchMethod(m);}
}

40
modules/angular2/src/router/location.js vendored Normal file
View File

@ -0,0 +1,40 @@
import {global} from 'angular2/src/facade/lang';
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
export class Location {
_location;
_subject:EventEmitter;
_history;
constructor() {
this._subject = new EventEmitter();
this._location = global.location;
this._history = global.history;
global.addEventListener('popstate', (_) => this._onPopState(_), false);
}
_onPopState(_) {
ObservableWrapper.callNext(this._subject, {
'url': this._location.pathname
});
}
path() {
return this._location.pathname;
}
go(url:string) {
this._history.pushState(null, null, url);
}
forward() {
this._history.forward();
}
back() {
this._history.back()
}
subscribe(onNext, onThrow = null, onReturn = null) {
ObservableWrapper.subscribe(this._subject, onNext, onThrow, onReturn);
}
}

View File

@ -6,6 +6,7 @@ import {RouteRegistry} from './route_registry';
import {Pipeline} from './pipeline'; import {Pipeline} from './pipeline';
import {Instruction} from './instruction'; import {Instruction} from './instruction';
import {RouterOutlet} from './router_outlet'; import {RouterOutlet} from './router_outlet';
import {Location} from './location';
/** /**
* # Router * # Router
@ -28,17 +29,21 @@ export class Router {
_outlets:Map<any, RouterOutlet>; _outlets:Map<any, RouterOutlet>;
_children:Map<any, Router>; _children:Map<any, Router>;
_subject:EventEmitter; _subject:EventEmitter;
_location:Location;
constructor(registry:RouteRegistry, pipeline:Pipeline, parent:Router = null, name = '/') {
constructor(registry:RouteRegistry, pipeline:Pipeline, location:Location, parent:Router = null, name = '/') {
this.name = name; this.name = name;
this.navigating = false; this.navigating = false;
this.parent = parent; this.parent = parent;
this.previousUrl = null; this.previousUrl = null;
this._outlets = MapWrapper.create(); this._outlets = MapWrapper.create();
this._children = MapWrapper.create(); this._children = MapWrapper.create();
this._location = location;
this._registry = registry; this._registry = registry;
this._pipeline = pipeline; this._pipeline = pipeline;
this._subject = new EventEmitter(); this._subject = new EventEmitter();
this._location.subscribe((url) => this.navigate(url));
this.navigate(location.path());
} }
@ -97,6 +102,9 @@ export class Router {
this._startNavigating(); this._startNavigating();
var result = this._pipeline.process(instruction) var result = this._pipeline.process(instruction)
.then((_) => {
this._location.go(instruction.matchedUrl);
})
.then((_) => { .then((_) => {
ObservableWrapper.callNext(this._subject, instruction.matchedUrl); ObservableWrapper.callNext(this._subject, instruction.matchedUrl);
}) })
@ -170,19 +178,19 @@ export class Router {
} }
static getRoot():Router { static getRoot():Router {
return new RootRouter(new Pipeline()); return new RootRouter(new Pipeline(), new Location());
} }
} }
export class RootRouter extends Router { export class RootRouter extends Router {
constructor(pipeline:Pipeline) { constructor(pipeline:Pipeline, location:Location) {
super(new RouteRegistry(), pipeline, null, '/'); super(new RouteRegistry(), pipeline, location, null, '/');
} }
} }
class ChildRouter extends Router { class ChildRouter extends Router {
constructor(parent, name) { constructor(parent, name) {
super(parent._registry, parent._pipeline, parent, name); super(parent._registry, parent._pipeline, parent._location, parent, name);
this.parent = parent; this.parent = parent;
} }
} }

View File

@ -25,6 +25,8 @@ import {Router, RouterOutlet, RouterLink, RouteConfig, RouteParams} from 'angula
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {DummyLocation} from 'angular2/src/mock/location_mock';
export function main() { export function main() {
describe('Outlet Directive', () => { describe('Outlet Directive', () => {
@ -36,7 +38,7 @@ export function main() {
})); }));
beforeEachBindings(() => { beforeEachBindings(() => {
router = new RootRouter(new Pipeline()); router = new RootRouter(new Pipeline(), new DummyLocation());
return [ return [
bind(Router).toValue(router) bind(Router).toValue(router)
]; ];

View File

@ -12,16 +12,47 @@ import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {RootRouter} from 'angular2/src/router/router'; import {RootRouter} from 'angular2/src/router/router';
import {Pipeline} from 'angular2/src/router/pipeline'; import {Pipeline} from 'angular2/src/router/pipeline';
import {RouterOutlet} from 'angular2/src/router/router_outlet'; import {RouterOutlet} from 'angular2/src/router/router_outlet';
import {DummyLocation} from 'angular2/src/mock/location_mock'
export function main() { export function main() {
describe('Router', () => { describe('Router', () => {
var router; var router,
location;
beforeEach(() => { beforeEach(() => {
router = new RootRouter(new Pipeline()); location = new DummyLocation();
router = new RootRouter(new Pipeline(), location);
}); });
it('should navigate based on the initial URL state', inject([AsyncTestCompleter], (async) => {
var outlet = makeDummyRef();
router.config('/', {'component': 'Index' })
.then((_) => router.registerOutlet(outlet))
.then((_) => {
expect(outlet.spy('activate')).toHaveBeenCalled();
expect(location.urlChanges).toEqual(['/']);
async.done();
});
}));
it('should activate viewports and update URL on navigate', inject([AsyncTestCompleter], (async) => {
var outlet = makeDummyRef();
router.registerOutlet(outlet)
.then((_) => {
return router.config('/a', {'component': 'A' });
})
.then((_) => router.navigate('/a'))
.then((_) => {
expect(outlet.spy('activate')).toHaveBeenCalled();
expect(location.urlChanges).toEqual(['/a']);
async.done();
});
}));
it('should navigate after being configured', inject([AsyncTestCompleter], (async) => { it('should navigate after being configured', inject([AsyncTestCompleter], (async) => {
var outlet = makeDummyRef(); var outlet = makeDummyRef();