feat(router): add support for basic events
This commit is contained in:
parent
2717bcc3af
commit
88920bfee1
@ -1,4 +1,4 @@
|
|||||||
export { Router } from './router';
|
export { Router, Event, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from './router';
|
||||||
export { UrlSerializer, DefaultUrlSerializer } from './url_serializer';
|
export { UrlSerializer, DefaultUrlSerializer } from './url_serializer';
|
||||||
export { RouterState, ActivatedRoute, RouterStateSnapshot, ActivatedRouteSnapshot } from './router_state';
|
export { RouterState, ActivatedRoute, RouterStateSnapshot, ActivatedRouteSnapshot } from './router_state';
|
||||||
export { UrlTree, UrlSegment} from './url_tree';
|
export { UrlTree, UrlSegment} from './url_tree';
|
||||||
|
@ -26,6 +26,12 @@ import {forkJoin} from 'rxjs/observable/forkJoin';
|
|||||||
|
|
||||||
export interface NavigationExtras { relativeTo?: ActivatedRoute; queryParameters?: Params; fragment?: string; }
|
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.
|
* The `Router` is responsible for mapping URLs to components.
|
||||||
*/
|
*/
|
||||||
@ -34,12 +40,14 @@ export class Router {
|
|||||||
private currentRouterState: RouterState;
|
private currentRouterState: RouterState;
|
||||||
private config: RouterConfig;
|
private config: RouterConfig;
|
||||||
private locationSubscription: Subscription;
|
private locationSubscription: Subscription;
|
||||||
|
private routerEvents: Subject<Event>;
|
||||||
private navigationId: number = 0;
|
private navigationId: number = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
constructor(private rootComponentType:Type, private resolver: ComponentResolver, private urlSerializer: UrlSerializer, private outletMap: RouterOutletMap, private location: Location, private injector: Injector) {
|
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.currentUrlTree = createEmptyUrlTree();
|
||||||
this.currentRouterState = createEmptyState(rootComponentType);
|
this.currentRouterState = createEmptyState(rootComponentType);
|
||||||
this.setUpLocationChangeListener();
|
this.setUpLocationChangeListener();
|
||||||
@ -60,6 +68,10 @@ export class Router {
|
|||||||
return this.currentUrlTree;
|
return this.currentUrlTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get events(): Observable<Event> {
|
||||||
|
return this.routerEvents;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigate based on the provided url. This navigation is always absolute.
|
* 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> {
|
private scheduleNavigation(url: UrlTree, pop: boolean):Promise<boolean> {
|
||||||
const id = ++ this.navigationId;
|
const id = ++ this.navigationId;
|
||||||
|
this.routerEvents.next(new NavigationStart(id, url));
|
||||||
return Promise.resolve().then((_) => this.runNavigate(url, false, id));
|
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> {
|
private runNavigate(url: UrlTree, pop: boolean, id: number):Promise<boolean> {
|
||||||
if (id !== this.navigationId) {
|
if (id !== this.navigationId) {
|
||||||
|
this.routerEvents.next(new NavigationCancel(id, url));
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +204,8 @@ export class Router {
|
|||||||
|
|
||||||
}).forEach((shouldActivate) => {
|
}).forEach((shouldActivate) => {
|
||||||
if (!shouldActivate || id !== this.navigationId) {
|
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);
|
new ActivateRoutes(state, this.currentRouterState).activate(this.outletMap);
|
||||||
@ -200,7 +215,14 @@ export class Router {
|
|||||||
if (!pop) {
|
if (!pop) {
|
||||||
this.location.go(this.urlSerializer.serialize(url));
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing'
|
|||||||
import { ComponentResolver } from '@angular/core';
|
import { ComponentResolver } from '@angular/core';
|
||||||
import { SpyLocation } from '@angular/common/testing';
|
import { SpyLocation } from '@angular/common/testing';
|
||||||
import { UrlSerializer, DefaultUrlSerializer, RouterOutletMap, Router, ActivatedRoute, ROUTER_DIRECTIVES, Params,
|
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 { Observable } from 'rxjs/Observable';
|
||||||
import 'rxjs/add/operator/map';
|
import 'rxjs/add/operator/map';
|
||||||
|
|
||||||
@ -228,6 +228,9 @@ describe("Integration", () => {
|
|||||||
{ path: '/user/:name', component: UserCmp }
|
{ path: '/user/:name', component: UserCmp }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const recordedEvents = [];
|
||||||
|
router.events.forEach(e => recordedEvents.push(e));
|
||||||
|
|
||||||
const fixture = tcb.createFakeAsync(RootCmp);
|
const fixture = tcb.createFakeAsync(RootCmp);
|
||||||
router.navigateByUrl('/user/init');
|
router.navigateByUrl('/user/init');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
@ -244,6 +247,17 @@ describe("Integration", () => {
|
|||||||
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('user fedor');
|
expect(fixture.debugElement.nativeElement).toHaveText('user fedor');
|
||||||
expect(user.recordedParams).toEqual([{name: 'init'}, {name: '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",
|
it("should handle failed navigations gracefully",
|
||||||
@ -252,6 +266,9 @@ describe("Integration", () => {
|
|||||||
{ path: '/user/:name', component: UserCmp }
|
{ path: '/user/:name', component: UserCmp }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const recordedEvents = [];
|
||||||
|
router.events.forEach(e => recordedEvents.push(e));
|
||||||
|
|
||||||
const fixture = tcb.createFakeAsync(RootCmp);
|
const fixture = tcb.createFakeAsync(RootCmp);
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
@ -264,6 +281,13 @@ describe("Integration", () => {
|
|||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('user fedor');
|
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", () => {
|
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({
|
@Component({
|
||||||
selector: 'link-cmp',
|
selector: 'link-cmp',
|
||||||
template: `<a routerLink="/team/33/simple">link</a>`,
|
template: `<a routerLink="/team/33/simple">link</a>`,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user