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<boolean>|Promise<boolean>|boolean;

AFTER:

    canDeactivate(component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): Observable<boolean>|Promise<boolean>|boolean;

Closes #9853
This commit is contained in:
Dzmitry Shylovich 2016-12-28 01:08:06 +03:00 committed by Hans
parent 445ed43b9a
commit 69fa3bbc03
4 changed files with 75 additions and 8 deletions

View File

@ -185,8 +185,9 @@ export interface CanActivateChild {
* *
* canDeactivate( * canDeactivate(
* component: TeamComponent, * component: TeamComponent,
* route: ActivatedRouteSnapshot, * currentRoute: ActivatedRouteSnapshot,
* state: RouterStateSnapshot * currentState: RouterStateSnapshot,
* nextState: RouterStateSnapshot
* ): Observable<boolean>|Promise<boolean>|boolean { * ): Observable<boolean>|Promise<boolean>|boolean {
* return this.permissions.canDeactivate(this.currentUser, route.params.id); * return this.permissions.canDeactivate(this.currentUser, route.params.id);
* } * }
@ -223,7 +224,8 @@ export interface CanActivateChild {
* providers: [ * providers: [
* { * {
* provide: 'canDeactivateTeam', * 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 * @stable
*/ */
export interface CanDeactivate<T> { export interface CanDeactivate<T> {
canDeactivate(component: T, route: ActivatedRouteSnapshot, state: RouterStateSnapshot): canDeactivate(
Observable<boolean>|Promise<boolean>|boolean; component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot,
nextState?: RouterStateSnapshot): Observable<boolean>|Promise<boolean>|boolean;
} }
/** /**

View File

@ -985,9 +985,10 @@ export class PreActivation {
const guard = this.getToken(c, curr); const guard = this.getToken(c, curr);
let observable: Observable<boolean>; let observable: Observable<boolean>;
if (guard.canDeactivate) { if (guard.canDeactivate) {
observable = wrapIntoObservable(guard.canDeactivate(component, curr, this.curr)); observable =
wrapIntoObservable(guard.canDeactivate(component, curr, this.curr, this.future));
} else { } else {
observable = wrapIntoObservable(guard(component, curr, this.curr)); observable = wrapIntoObservable(guard(component, curr, this.curr, this.future));
} }
return first.call(observable); return first.call(observable);
}); });

View File

@ -1647,6 +1647,69 @@ describe('Integration', () => {
expect(location.path()).toEqual('/simple'); expect(location.path()).toEqual('/simple');
}))); })));
describe('next state', () => {
let log: string[];
class ClassWithNextState implements CanDeactivate<TeamCmp> {
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', () => { describe('should work when given a class', () => {
class AlwaysTrue implements CanDeactivate<TeamCmp> { class AlwaysTrue implements CanDeactivate<TeamCmp> {

View File

@ -47,7 +47,7 @@ export interface CanActivateChild {
/** @stable */ /** @stable */
export interface CanDeactivate<T> { export interface CanDeactivate<T> {
canDeactivate(component: T, route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean; canDeactivate(component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean;
} }
/** @stable */ /** @stable */