From 152ca66ebaa61737b4ef0321570ad6b75235980d Mon Sep 17 00:00:00 2001 From: Jason Aden Date: Fri, 5 Oct 2018 11:45:06 -0700 Subject: [PATCH] feat(router): allow redirect from guards by returning UrlTree (#26521) * Improve type checking within the CheckGuards function * Change public API to allow returning of UrlTree instead of boolean PR Close #26521 --- packages/router/src/apply_redirects.ts | 13 +++++- packages/router/src/interfaces.ts | 5 +- packages/router/src/operators/check_guards.ts | 46 +++++++++++-------- packages/router/src/utils/type_guards.ts | 4 +- 4 files changed, 44 insertions(+), 24 deletions(-) diff --git a/packages/router/src/apply_redirects.ts b/packages/router/src/apply_redirects.ts index c29a128cad..905a846c7d 100644 --- a/packages/router/src/apply_redirects.ts +++ b/packages/router/src/apply_redirects.ts @@ -11,10 +11,12 @@ import {EmptyError, Observable, Observer, from, of } from 'rxjs'; import {catchError, concatAll, first, map, mergeMap} from 'rxjs/operators'; import {LoadedRouterConfig, Route, Routes} from './config'; +import {CanLoadFn} from './interfaces'; import {RouterConfigLoader} from './router_config_loader'; import {PRIMARY_OUTLET, Params, defaultUrlMatcher, navigationCancelingError} from './shared'; import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree'; import {andObservables, forEach, waitForMap, wrapIntoObservable} from './utils/collection'; +import {isCanLoad, isFunction} from './utils/type_guards'; class NoMatch { public segmentGroup: UrlSegmentGroup|null; @@ -408,8 +410,15 @@ function runCanLoadGuard( const obs = from(canLoad).pipe(map((injectionToken: any) => { const guard = moduleInjector.get(injectionToken); - return wrapIntoObservable( - guard.canLoad ? guard.canLoad(route, segments) : guard(route, segments)); + let guardVal; + if (isCanLoad(guard)) { + guardVal = guard.canLoad(route, segments); + } else if (isFunction(guard)) { + guardVal = guard(route, segments); + } else { + throw new Error('Invalid CanLoad guard'); + } + return wrapIntoObservable(guardVal); })); return andObservables(obs); diff --git a/packages/router/src/interfaces.ts b/packages/router/src/interfaces.ts index 91c5222635..eb4d66428c 100644 --- a/packages/router/src/interfaces.ts +++ b/packages/router/src/interfaces.ts @@ -84,7 +84,7 @@ export interface CanActivate { } export type CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => - Observable| Promise| boolean | UrlTree; + Observable| Promise| boolean | UrlTree; /** * @description @@ -246,7 +246,8 @@ export interface CanDeactivate { export type CanDeactivateFn = (component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, - nextState?: RouterStateSnapshot) => Observable| Promise| boolean | UrlTree; + nextState?: RouterStateSnapshot) => + Observable| Promise| boolean | UrlTree; /** * @description diff --git a/packages/router/src/operators/check_guards.ts b/packages/router/src/operators/check_guards.ts index bf2d82f1b7..937cccf5f7 100644 --- a/packages/router/src/operators/check_guards.ts +++ b/packages/router/src/operators/check_guards.ts @@ -11,10 +11,13 @@ import {MonoTypeOperatorFunction, Observable, from, of } from 'rxjs'; import {concatMap, every, first, map, mergeMap} from 'rxjs/operators'; import {ActivationStart, ChildActivationStart, Event} from '../events'; +import {CanActivateChildFn, CanActivateFn, CanDeactivateFn} from '../interfaces'; import {NavigationTransition} from '../router'; import {ActivatedRouteSnapshot, RouterStateSnapshot} from '../router_state'; +import {UrlTree} from '../url_tree'; import {andObservables, wrapIntoObservable} from '../utils/collection'; import {CanActivate, CanDeactivate, getCanActivateChild, getToken} from '../utils/preactivation'; +import {isCanActivate, isCanActivateChild, isCanDeactivate, isFunction} from '../utils/type_guards'; export function checkGuards(moduleInjector: Injector, forwardEvent?: (evt: Event) => void): MonoTypeOperatorFunction { @@ -29,7 +32,7 @@ export function checkGuards(moduleInjector: Injector, forwardEvent?: (evt: Event return runCanDeactivateChecks( canDeactivateChecks, targetSnapshot !, currentSnapshot, moduleInjector) .pipe( - mergeMap((canDeactivate: boolean) => { + mergeMap(canDeactivate => { return canDeactivate ? runCanActivateChecks( targetSnapshot !, canActivateChecks, moduleInjector, forwardEvent) : @@ -42,12 +45,12 @@ export function checkGuards(moduleInjector: Injector, forwardEvent?: (evt: Event function runCanDeactivateChecks( checks: CanDeactivate[], futureRSS: RouterStateSnapshot, currRSS: RouterStateSnapshot, - moduleInjector: Injector): Observable { + moduleInjector: Injector): Observable { return from(checks).pipe( - mergeMap( - (check: CanDeactivate) => - runCanDeactivate(check.component, check.route, currRSS, futureRSS, moduleInjector)), - every((result: boolean) => result === true)); + mergeMap(check => { + return runCanDeactivate(check.component, check.route, currRSS, futureRSS, moduleInjector); + }), + every(result => result === true)); } function runCanActivateChecks( @@ -104,11 +107,13 @@ function runCanActivate( if (!canActivate || canActivate.length === 0) return of (true); const obs = from(canActivate).pipe(map((c: any) => { const guard = getToken(c, futureARS, moduleInjector); - let observable: Observable; - if (guard.canActivate) { + let observable; + if (isCanActivate(guard)) { observable = wrapIntoObservable(guard.canActivate(futureARS, futureRSS)); - } else { + } else if (isFunction(guard)) { observable = wrapIntoObservable(guard(futureARS, futureRSS)); + } else { + throw new Error('Invalid canActivate guard'); } return observable.pipe(first()); })); @@ -128,11 +133,13 @@ function runCanActivateChild( return andObservables(from(canActivateChildGuards).pipe(map((d: any) => { const obs = from(d.guards).pipe(map((c: any) => { const guard = getToken(c, d.node, moduleInjector); - let observable: Observable; - if (guard.canActivateChild) { + let observable; + if (isCanActivateChild(guard)) { observable = wrapIntoObservable(guard.canActivateChild(futureARS, futureRSS)); - } else { + } else if (isFunction(guard)) { observable = wrapIntoObservable(guard(futureARS, futureRSS)); + } else { + throw new Error('Invalid CanActivateChild guard'); } return observable.pipe(first()); })); @@ -141,17 +148,20 @@ function runCanActivateChild( } function runCanDeactivate( - component: Object | null, currARS: ActivatedRouteSnapshot, currRSS: RouterStateSnapshot, - futureRSS: RouterStateSnapshot, moduleInjector: Injector): Observable { + component: Object| null, currARS: ActivatedRouteSnapshot, currRSS: RouterStateSnapshot, + futureRSS: RouterStateSnapshot, moduleInjector: Injector): Observable { const canDeactivate = currARS && currARS.routeConfig ? currARS.routeConfig.canDeactivate : null; if (!canDeactivate || canDeactivate.length === 0) return of (true); const canDeactivate$ = from(canDeactivate).pipe(mergeMap((c: any) => { const guard = getToken(c, currARS, moduleInjector); - let observable: Observable; - if (guard.canDeactivate) { - observable = wrapIntoObservable(guard.canDeactivate(component, currARS, currRSS, futureRSS)); - } else { + let observable; + if (isCanDeactivate(guard)) { + observable = + wrapIntoObservable(guard.canDeactivate(component !, currARS, currRSS, futureRSS)); + } else if (isFunction>(guard)) { observable = wrapIntoObservable(guard(component, currARS, currRSS, futureRSS)); + } else { + throw new Error('Invalid CanDeactivate guard'); } return observable.pipe(first()); })); diff --git a/packages/router/src/utils/type_guards.ts b/packages/router/src/utils/type_guards.ts index 008394d81c..befda967be 100644 --- a/packages/router/src/utils/type_guards.ts +++ b/packages/router/src/utils/type_guards.ts @@ -38,6 +38,6 @@ export function isCanActivateChild(guard: any): guard is CanActivateChild { return guard && isFunction(guard.canActivate); } -export function isCanDeactivate(guard: any): guard is CanDeactivate> { - return guard && isFunction>>(guard.canDeactivate); +export function isCanDeactivate(guard: any): guard is CanDeactivate { + return guard && isFunction>(guard.canDeactivate); }