feat(router): add support for basic events

This commit is contained in:
vsavkin 2016-06-03 14:25:18 -07:00
parent 2717bcc3af
commit 88920bfee1
3 changed files with 57 additions and 4 deletions

View File

@ -1,4 +1,4 @@
export { Router } from './router';
export { Router, Event, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from './router';
export { UrlSerializer, DefaultUrlSerializer } from './url_serializer';
export { RouterState, ActivatedRoute, RouterStateSnapshot, ActivatedRouteSnapshot } from './router_state';
export { UrlTree, UrlSegment} from './url_tree';

View File

@ -26,6 +26,12 @@ import {forkJoin} from 'rxjs/observable/forkJoin';
export interface NavigationExtras { relativeTo?: ActivatedRoute; queryParameters?: Params; fragment?: string; }
export class NavigationStart { constructor(public id:number, public url:UrlTree) {} }
export class NavigationEnd { constructor(public id:number, public url:UrlTree) {} }
export class NavigationCancel { constructor(public id:number, public url:UrlTree) {} }
export class NavigationError { constructor(public id:number, public url:UrlTree, public error:any) {} }
export type Event = NavigationStart | NavigationEnd | NavigationCancel | NavigationError;
/**
* The `Router` is responsible for mapping URLs to components.
*/
@ -34,12 +40,14 @@ export class Router {
private currentRouterState: RouterState;
private config: RouterConfig;
private locationSubscription: Subscription;
private routerEvents: Subject<Event>;
private navigationId: number = 0;
/**
* @internal
*/
constructor(private rootComponentType:Type, private resolver: ComponentResolver, private urlSerializer: UrlSerializer, private outletMap: RouterOutletMap, private location: Location, private injector: Injector) {
this.routerEvents = new Subject<Event>();
this.currentUrlTree = createEmptyUrlTree();
this.currentRouterState = createEmptyState(rootComponentType);
this.setUpLocationChangeListener();
@ -60,6 +68,10 @@ export class Router {
return this.currentUrlTree;
}
get events(): Observable<Event> {
return this.routerEvents;
}
/**
* Navigate based on the provided url. This navigation is always absolute.
*
@ -160,6 +172,7 @@ export class Router {
private scheduleNavigation(url: UrlTree, pop: boolean):Promise<boolean> {
const id = ++ this.navigationId;
this.routerEvents.next(new NavigationStart(id, url));
return Promise.resolve().then((_) => this.runNavigate(url, false, id));
}
@ -171,6 +184,7 @@ export class Router {
private runNavigate(url: UrlTree, pop: boolean, id: number):Promise<boolean> {
if (id !== this.navigationId) {
this.routerEvents.next(new NavigationCancel(id, url));
return Promise.resolve(false);
}
@ -190,7 +204,8 @@ export class Router {
}).forEach((shouldActivate) => {
if (!shouldActivate || id !== this.navigationId) {
return;
this.routerEvents.next(new NavigationCancel(id, url));
return Promise.resolve(false);
}
new ActivateRoutes(state, this.currentRouterState).activate(this.outletMap);
@ -200,7 +215,14 @@ export class Router {
if (!pop) {
this.location.go(this.urlSerializer.serialize(url));
}
}).then(() => resolvePromise(true), e => rejectPromise(e));
}).then(() => {
this.routerEvents.next(new NavigationEnd(id, url));
resolvePromise(true);
}, e => {
this.routerEvents.next(new NavigationError(id, url, e));
rejectPromise(e);
});
});
}
}

View File

@ -18,7 +18,7 @@ import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing'
import { ComponentResolver } from '@angular/core';
import { SpyLocation } from '@angular/common/testing';
import { UrlSerializer, DefaultUrlSerializer, RouterOutletMap, Router, ActivatedRoute, ROUTER_DIRECTIVES, Params,
RouterStateSnapshot, ActivatedRouteSnapshot, CanActivate, CanDeactivate } from '../src/index';
RouterStateSnapshot, ActivatedRouteSnapshot, CanActivate, CanDeactivate, Event, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '../src/index';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
@ -228,6 +228,9 @@ describe("Integration", () => {
{ path: '/user/:name', component: UserCmp }
]);
const recordedEvents = [];
router.events.forEach(e => recordedEvents.push(e));
const fixture = tcb.createFakeAsync(RootCmp);
router.navigateByUrl('/user/init');
advance(fixture);
@ -244,6 +247,17 @@ describe("Integration", () => {
expect(fixture.debugElement.nativeElement).toHaveText('user fedor');
expect(user.recordedParams).toEqual([{name: 'init'}, {name: 'fedor'}]);
expectEvents(router, recordedEvents.slice(1), [
[NavigationStart, '/user/init'],
[NavigationEnd, '/user/init'],
[NavigationStart, '/user/victor'],
[NavigationStart, '/user/fedor'],
[NavigationCancel, '/user/victor'],
[NavigationEnd, '/user/fedor']
]);
})));
it("should handle failed navigations gracefully",
@ -252,6 +266,9 @@ describe("Integration", () => {
{ path: '/user/:name', component: UserCmp }
]);
const recordedEvents = [];
router.events.forEach(e => recordedEvents.push(e));
const fixture = tcb.createFakeAsync(RootCmp);
advance(fixture);
@ -264,6 +281,13 @@ describe("Integration", () => {
advance(fixture);
expect(fixture.debugElement.nativeElement).toHaveText('user fedor');
expectEvents(router, recordedEvents.slice(1), [
[NavigationStart, '/invalid'],
[NavigationError, '/invalid'],
[NavigationStart, '/user/fedor'],
[NavigationEnd, '/user/fedor']
]);
})));
describe("router links", () => {
@ -482,6 +506,13 @@ describe("Integration", () => {
});
});
function expectEvents(router: Router, events:Event[], pairs: any[]) {
for (let i = 0; i < events.length; ++i) {
expect((<any>events[i].constructor).name).toBe(pairs[i][0].name);
expect(router.serializeUrl((<any>events[i]).url)).toBe(pairs[i][1]);
}
}
@Component({
selector: 'link-cmp',
template: `<a routerLink="/team/33/simple">link</a>`,