From 69fa3bbc032f745252fe2f4c9e8b4e4cabf53c7d Mon Sep 17 00:00:00 2001 From: Dzmitry Shylovich Date: Wed, 28 Dec 2016 01:08:06 +0300 Subject: [PATCH] feat(router): add an extra argument to CanDeactivate interface (#13560) Adds a `nextState` argument to access the future url from `CanDeactivate`. BEFORE: canDeactivate(component: T, route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable|Promise|boolean; AFTER: canDeactivate(component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): Observable|Promise|boolean; Closes #9853 --- modules/@angular/router/src/interfaces.ts | 13 ++-- modules/@angular/router/src/router.ts | 5 +- .../@angular/router/test/integration.spec.ts | 63 +++++++++++++++++++ tools/public_api_guard/router/index.d.ts | 2 +- 4 files changed, 75 insertions(+), 8 deletions(-) diff --git a/modules/@angular/router/src/interfaces.ts b/modules/@angular/router/src/interfaces.ts index da35d2d90a016..39b83ce4356fd 100644 --- a/modules/@angular/router/src/interfaces.ts +++ b/modules/@angular/router/src/interfaces.ts @@ -185,8 +185,9 @@ export interface CanActivateChild { * * canDeactivate( * component: TeamComponent, - * route: ActivatedRouteSnapshot, - * state: RouterStateSnapshot + * currentRoute: ActivatedRouteSnapshot, + * currentState: RouterStateSnapshot, + * nextState: RouterStateSnapshot * ): Observable|Promise|boolean { * return this.permissions.canDeactivate(this.currentUser, route.params.id); * } @@ -223,7 +224,8 @@ export interface CanActivateChild { * providers: [ * { * provide: 'canDeactivateTeam', - * useValue: (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => true + * useValue: (component: TeamComponent, currentRoute: ActivatedRouteSnapshot, currentState: + * RouterStateSnapshot, nextState: RouterStateSnapshot) => true * } * ] * }) @@ -233,8 +235,9 @@ export interface CanActivateChild { * @stable */ export interface CanDeactivate { - canDeactivate(component: T, route: ActivatedRouteSnapshot, state: RouterStateSnapshot): - Observable|Promise|boolean; + canDeactivate( + component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, + nextState?: RouterStateSnapshot): Observable|Promise|boolean; } /** diff --git a/modules/@angular/router/src/router.ts b/modules/@angular/router/src/router.ts index 9750bff5b290f..139b72ea2d12e 100644 --- a/modules/@angular/router/src/router.ts +++ b/modules/@angular/router/src/router.ts @@ -985,9 +985,10 @@ export class PreActivation { const guard = this.getToken(c, curr); let observable: Observable; if (guard.canDeactivate) { - observable = wrapIntoObservable(guard.canDeactivate(component, curr, this.curr)); + observable = + wrapIntoObservable(guard.canDeactivate(component, curr, this.curr, this.future)); } else { - observable = wrapIntoObservable(guard(component, curr, this.curr)); + observable = wrapIntoObservable(guard(component, curr, this.curr, this.future)); } return first.call(observable); }); diff --git a/modules/@angular/router/test/integration.spec.ts b/modules/@angular/router/test/integration.spec.ts index 7f3d86b0af4ea..b09d342a70a1a 100644 --- a/modules/@angular/router/test/integration.spec.ts +++ b/modules/@angular/router/test/integration.spec.ts @@ -1647,6 +1647,69 @@ describe('Integration', () => { expect(location.path()).toEqual('/simple'); }))); + describe('next state', () => { + let log: string[]; + + class ClassWithNextState implements CanDeactivate { + canDeactivate( + component: TeamCmp, currentRoute: ActivatedRouteSnapshot, + currentState: RouterStateSnapshot, nextState: RouterStateSnapshot): boolean { + log.push(currentState.url, nextState.url); + return true; + } + } + + beforeEach(() => { + log = []; + TestBed.configureTestingModule({ + providers: [ + ClassWithNextState, { + provide: 'FunctionWithNextState', + useValue: (cmp: any, currentRoute: ActivatedRouteSnapshot, + currentState: RouterStateSnapshot, nextState: RouterStateSnapshot) => { + log.push(currentState.url, nextState.url); + return true; + } + } + ] + }); + }); + + it('should pass next state as the 4 argument when guard is a class', + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); + + router.resetConfig( + [{path: 'team/:id', component: TeamCmp, canDeactivate: [ClassWithNextState]}]); + + router.navigateByUrl('/team/22'); + advance(fixture); + expect(location.path()).toEqual('/team/22'); + + router.navigateByUrl('/team/33'); + advance(fixture); + expect(location.path()).toEqual('/team/33'); + expect(log).toEqual(['/team/22', '/team/33']); + }))); + + it('should pass next state as the 4 argument when guard is a function', + fakeAsync(inject([Router, Location], (router: Router, location: Location) => { + const fixture = createRoot(router, RootCmp); + + router.resetConfig([ + {path: 'team/:id', component: TeamCmp, canDeactivate: ['FunctionWithNextState']} + ]); + + router.navigateByUrl('/team/22'); + advance(fixture); + expect(location.path()).toEqual('/team/22'); + + router.navigateByUrl('/team/33'); + advance(fixture); + expect(location.path()).toEqual('/team/33'); + expect(log).toEqual(['/team/22', '/team/33']); + }))); + }); describe('should work when given a class', () => { class AlwaysTrue implements CanDeactivate { diff --git a/tools/public_api_guard/router/index.d.ts b/tools/public_api_guard/router/index.d.ts index 01c4d65b5ef99..075743c3ecc1f 100644 --- a/tools/public_api_guard/router/index.d.ts +++ b/tools/public_api_guard/router/index.d.ts @@ -47,7 +47,7 @@ export interface CanActivateChild { /** @stable */ export interface CanDeactivate { - canDeactivate(component: T, route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean; + canDeactivate(component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): Observable | Promise | boolean; } /** @stable */