feat(router): implement cancelation
This commit is contained in:
parent
5d386dc426
commit
2717bcc3af
|
@ -15,9 +15,12 @@ import { createUrlTree } from './create_url_tree';
|
|||
import { forEach, and, shallowEqual } from './utils/collection';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/scan';
|
||||
import 'rxjs/add/operator/mergeMap';
|
||||
import 'rxjs/add/operator/concat';
|
||||
import 'rxjs/add/operator/concatMap';
|
||||
import {of} from 'rxjs/observable/of';
|
||||
import {forkJoin} from 'rxjs/observable/forkJoin';
|
||||
|
||||
|
@ -31,6 +34,7 @@ export class Router {
|
|||
private currentRouterState: RouterState;
|
||||
private config: RouterConfig;
|
||||
private locationSubscription: Subscription;
|
||||
private navigationId: number = 0;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
@ -65,9 +69,9 @@ export class Router {
|
|||
* router.navigateByUrl("/team/33/user/11");
|
||||
* ```
|
||||
*/
|
||||
navigateByUrl(url: string): Observable<void> {
|
||||
navigateByUrl(url: string): Promise<boolean> {
|
||||
const urlTree = this.urlSerializer.parse(url);
|
||||
return this.runNavigate(urlTree, false);
|
||||
return this.scheduleNavigation(urlTree, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -140,8 +144,8 @@ export class Router {
|
|||
* router.navigate(['team', 33, 'team', '11], {relativeTo: route});
|
||||
* ```
|
||||
*/
|
||||
navigate(commands: any[], extras: NavigationExtras = {}): Observable<void> {
|
||||
return this.runNavigate(this.createUrlTree(commands, extras));
|
||||
navigate(commands: any[], extras: NavigationExtras = {}): Promise<boolean> {
|
||||
return this.scheduleNavigation(this.createUrlTree(commands, extras), false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -154,39 +158,50 @@ export class Router {
|
|||
*/
|
||||
parseUrl(url: string): UrlTree { return this.urlSerializer.parse(url); }
|
||||
|
||||
private scheduleNavigation(url: UrlTree, pop: boolean):Promise<boolean> {
|
||||
const id = ++ this.navigationId;
|
||||
return Promise.resolve().then((_) => this.runNavigate(url, false, id));
|
||||
}
|
||||
|
||||
private setUpLocationChangeListener(): void {
|
||||
this.locationSubscription = <any>this.location.subscribe((change) => {
|
||||
this.runNavigate(this.urlSerializer.parse(change['url']), change['pop'])
|
||||
return this.scheduleNavigation(this.urlSerializer.parse(change['url']), change['pop']);
|
||||
});
|
||||
}
|
||||
|
||||
private runNavigate(url:UrlTree, pop?:boolean):Observable<any> {
|
||||
let state;
|
||||
const r = recognize(this.rootComponentType, this.config, url).mergeMap((newRouterStateSnapshot) => {
|
||||
return resolve(this.resolver, newRouterStateSnapshot);
|
||||
private runNavigate(url: UrlTree, pop: boolean, id: number):Promise<boolean> {
|
||||
if (id !== this.navigationId) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
}).map((routerStateSnapshot) => {
|
||||
return createRouterState(routerStateSnapshot, this.currentRouterState);
|
||||
return new Promise((resolvePromise, rejectPromise) => {
|
||||
let state;
|
||||
recognize(this.rootComponentType, this.config, url).mergeMap((newRouterStateSnapshot) => {
|
||||
return resolve(this.resolver, newRouterStateSnapshot);
|
||||
|
||||
}).map((newState:RouterState) => {
|
||||
state = newState;
|
||||
}).map((routerStateSnapshot) => {
|
||||
return createRouterState(routerStateSnapshot, this.currentRouterState);
|
||||
|
||||
}).mergeMap(_ => {
|
||||
return new GuardChecks(state.snapshot, this.currentRouterState.snapshot, this.injector).check(this.outletMap);
|
||||
}).map((newState:RouterState) => {
|
||||
state = newState;
|
||||
|
||||
}).mergeMap(_ => {
|
||||
return new GuardChecks(state.snapshot, this.currentRouterState.snapshot, this.injector).check(this.outletMap);
|
||||
|
||||
}).forEach((shouldActivate) => {
|
||||
if (!shouldActivate || id !== this.navigationId) {
|
||||
return;
|
||||
}
|
||||
|
||||
new ActivateRoutes(state, this.currentRouterState).activate(this.outletMap);
|
||||
|
||||
this.currentUrlTree = url;
|
||||
this.currentRouterState = state;
|
||||
if (!pop) {
|
||||
this.location.go(this.urlSerializer.serialize(url));
|
||||
}
|
||||
}).then(() => resolvePromise(true), e => rejectPromise(e));
|
||||
});
|
||||
|
||||
r.subscribe((shouldActivate) => {
|
||||
if (!shouldActivate) return;
|
||||
new ActivateRoutes(state, this.currentRouterState).activate(this.outletMap);
|
||||
|
||||
this.currentUrlTree = url;
|
||||
this.currentRouterState = state;
|
||||
|
||||
if (!pop) {
|
||||
this.location.go(this.urlSerializer.serialize(url));
|
||||
}
|
||||
});
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -222,6 +222,50 @@ describe("Integration", () => {
|
|||
expect(fixture.debugElement.nativeElement).toHaveText('simple');
|
||||
})));
|
||||
|
||||
it("should cancel in-flight navigations",
|
||||
fakeAsync(inject([Router, TestComponentBuilder], (router, tcb:TestComponentBuilder) => {
|
||||
router.resetConfig([
|
||||
{ path: '/user/:name', component: UserCmp }
|
||||
]);
|
||||
|
||||
const fixture = tcb.createFakeAsync(RootCmp);
|
||||
router.navigateByUrl('/user/init');
|
||||
advance(fixture);
|
||||
|
||||
const user = fixture.debugElement.children[1].componentInstance;
|
||||
|
||||
let r1, r2;
|
||||
router.navigateByUrl('/user/victor').then(_ => r1 = _);
|
||||
router.navigateByUrl('/user/fedor').then(_ => r2 = _);
|
||||
advance(fixture);
|
||||
|
||||
expect(r1).toEqual(false); // returns false because it was canceled
|
||||
expect(r2).toEqual(true); // returns true because it was successful
|
||||
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('user fedor');
|
||||
expect(user.recordedParams).toEqual([{name: 'init'}, {name: 'fedor'}]);
|
||||
})));
|
||||
|
||||
it("should handle failed navigations gracefully",
|
||||
fakeAsync(inject([Router, TestComponentBuilder], (router, tcb:TestComponentBuilder) => {
|
||||
router.resetConfig([
|
||||
{ path: '/user/:name', component: UserCmp }
|
||||
]);
|
||||
|
||||
const fixture = tcb.createFakeAsync(RootCmp);
|
||||
advance(fixture);
|
||||
|
||||
let e;
|
||||
router.navigateByUrl('/invalid').catch(_ => e = _);
|
||||
advance(fixture);
|
||||
expect(e.message).toContain("Cannot match any routes");
|
||||
|
||||
router.navigateByUrl('/user/fedor');
|
||||
advance(fixture);
|
||||
|
||||
expect(fixture.debugElement.nativeElement).toHaveText('user fedor');
|
||||
})));
|
||||
|
||||
describe("router links", () => {
|
||||
it("should support string router links",
|
||||
fakeAsync(inject([Router, TestComponentBuilder], (router, tcb) => {
|
||||
|
|
Loading…
Reference in New Issue