diff --git a/modules/@angular/router/src/index.ts b/modules/@angular/router/src/index.ts index 3321a15c7c..85000e9477 100644 --- a/modules/@angular/router/src/index.ts +++ b/modules/@angular/router/src/index.ts @@ -6,6 +6,7 @@ export { RouterOutletMap } from './router_outlet_map'; export { RouterConfig, Route } from './config'; export { Params, PRIMARY_OUTLET } from './shared'; export { provideRouter } from './router_providers'; +export { CanActivate, CanDeactivate } from './interfaces'; import { RouterOutlet } from './directives/router_outlet'; import { RouterLink } from './directives/router_link'; diff --git a/modules/@angular/router/src/interfaces.ts b/modules/@angular/router/src/interfaces.ts new file mode 100644 index 0000000000..e271070155 --- /dev/null +++ b/modules/@angular/router/src/interfaces.ts @@ -0,0 +1,16 @@ +import {Observable} from 'rxjs/Observable'; +import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state'; + +/** + * An interface a class can implement to be a guard deciding if a route can be activated. + */ +export interface CanActivate { + canActivate(route:ActivatedRouteSnapshot, state:RouterStateSnapshot):Observable | boolean; +} + +/** + * An interface a class can implement to be a guard deciding if a route can be deactivated. + */ +export interface CanDeactivate { + canDeactivate(component:T, route:ActivatedRouteSnapshot, state:RouterStateSnapshot):Observable | boolean; +} \ No newline at end of file diff --git a/modules/@angular/router/src/router.ts b/modules/@angular/router/src/router.ts index 9cf4ee21c2..8ce4848234 100644 --- a/modules/@angular/router/src/router.ts +++ b/modules/@angular/router/src/router.ts @@ -254,7 +254,11 @@ class GuardChecks { if (!canActivate || canActivate.length === 0) return of(true); return forkJoin(canActivate.map(c => { const guard = this.injector.get(c); - return of(guard(future, this.future)); + if (guard.canActivate) { + return of(guard.canActivate(future, this.future)); + } else { + return of(guard(future, this.future)); + } })).map(and); } @@ -263,7 +267,11 @@ class GuardChecks { if (!canDeactivate || canDeactivate.length === 0) return of(true); return forkJoin(canDeactivate.map(c => { const guard = this.injector.get(c); - return of(guard(component, curr, this.curr)); + if (guard.canDeactivate) { + return of(guard.canDeactivate(component, curr, this.curr)); + } else { + return of(guard(component, curr, this.curr)); + } })).map(and); } } diff --git a/modules/@angular/router/test/router.spec.ts b/modules/@angular/router/test/router.spec.ts index 4ae8b78ece..3420d35962 100644 --- a/modules/@angular/router/test/router.spec.ts +++ b/modules/@angular/router/test/router.spec.ts @@ -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 } from '../src/index'; + RouterStateSnapshot, ActivatedRouteSnapshot, CanActivate, CanDeactivate } from '../src/index'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; @@ -330,13 +330,38 @@ describe("Integration", () => { describe("should activate a route when CanActivate returns true", () => { beforeEachProviders(() => [ - {provide: 'alwaysFalse', useValue: (a:ActivatedRouteSnapshot, s:RouterStateSnapshot) => true} + {provide: 'alwaysTrue', useValue: (a:ActivatedRouteSnapshot, s:RouterStateSnapshot) => true} ]); it('works', fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => { router.resetConfig([ - { path: 'team/:id', component: TeamCmp, canActivate: ["alwaysFalse"] } + { path: 'team/:id', component: TeamCmp, canActivate: ["alwaysTrue"] } + ]); + + const fixture = tcb.createFakeAsync(RootCmp); + advance(fixture); + + router.navigateByUrl('/team/22'); + advance(fixture); + + expect(location.path()).toEqual('/team/22'); + }))); + }); + + describe("should work when given a class", () => { + class AlwaysTrue implements CanActivate { + canActivate(route:ActivatedRouteSnapshot, state:RouterStateSnapshot):boolean { + return true; + } + } + + beforeEachProviders(() => [AlwaysTrue]); + + it('works', + fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => { + router.resetConfig([ + { path: 'team/:id', component: TeamCmp, canActivate: [AlwaysTrue] } ]); const fixture = tcb.createFakeAsync(RootCmp); @@ -384,6 +409,31 @@ describe("Integration", () => { expect(location.path()).toEqual('/team/33'); }))); }); + + describe("should work when given a class", () => { + class AlwaysTrue implements CanDeactivate { + canDeactivate(component: TeamCmp, route:ActivatedRouteSnapshot, state:RouterStateSnapshot):boolean { + return true; + } + } + + beforeEachProviders(() => [AlwaysTrue]); + + it('works', + fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => { + router.resetConfig([ + { path: 'team/:id', component: TeamCmp, canDeactivate: [AlwaysTrue] } + ]); + + const fixture = tcb.createFakeAsync(RootCmp); + advance(fixture); + + router.navigateByUrl('/team/22'); + advance(fixture); + + expect(location.path()).toEqual('/team/22'); + }))); + }); }); }); }); diff --git a/modules/@angular/router/tsconfig.json b/modules/@angular/router/tsconfig.json index 7ac428001d..95fe5988b1 100644 --- a/modules/@angular/router/tsconfig.json +++ b/modules/@angular/router/tsconfig.json @@ -30,6 +30,7 @@ "src/create_url_tree.ts", "src/directives/router_outlet.ts", "src/directives/router_link.ts", + "src/interfaces.ts", "src/utils/tree.ts", "src/utils/collection.ts", "test/utils/tree.spec.ts",