feat(router): allow CanLoad guard to return UrlTree (#36610)
A CanLoad guard returning UrlTree cancels current navigation and redirects. This matches the behavior available to `CanActivate` guards added in #26521. Note that this does not affect preloading. A `CanLoad` guard blocks any preloading. That is, any route with a `CanLoad` guard is not preloaded and the guards are not executed as part of preloading. fixes #28306 PR Close #36610
This commit is contained in:

committed by
Andrew Kushnir

parent
cae2a893f2
commit
00e6cb1d62
@ -8,7 +8,7 @@
|
||||
|
||||
import {Injector, NgModuleRef} from '@angular/core';
|
||||
import {EmptyError, from, Observable, Observer, of} from 'rxjs';
|
||||
import {catchError, concatAll, every, first, map, mergeMap} from 'rxjs/operators';
|
||||
import {catchError, concatAll, every, first, map, mergeMap, tap} from 'rxjs/operators';
|
||||
|
||||
import {LoadedRouterConfig, Route, Routes} from './config';
|
||||
import {CanLoadFn} from './interfaces';
|
||||
@ -16,7 +16,7 @@ import {RouterConfigLoader} from './router_config_loader';
|
||||
import {defaultUrlMatcher, navigationCancelingError, Params, PRIMARY_OUTLET} from './shared';
|
||||
import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
|
||||
import {forEach, waitForMap, wrapIntoObservable} from './utils/collection';
|
||||
import {isCanLoad, isFunction} from './utils/type_guards';
|
||||
import {isCanLoad, isFunction, isUrlTree} from './utils/type_guards';
|
||||
|
||||
class NoMatch {
|
||||
public segmentGroup: UrlSegmentGroup|null;
|
||||
@ -300,9 +300,9 @@ class ApplyRedirects {
|
||||
return of(route._loadedConfig);
|
||||
}
|
||||
|
||||
return runCanLoadGuard(ngModule.injector, route, segments)
|
||||
.pipe(mergeMap((shouldLoad: boolean) => {
|
||||
if (shouldLoad) {
|
||||
return this.runCanLoadGuards(ngModule.injector, route, segments)
|
||||
.pipe(mergeMap((shouldLoadResult: boolean) => {
|
||||
if (shouldLoadResult) {
|
||||
return this.configLoader.load(ngModule.injector, route)
|
||||
.pipe(map((cfg: LoadedRouterConfig) => {
|
||||
route._loadedConfig = cfg;
|
||||
@ -316,6 +316,38 @@ class ApplyRedirects {
|
||||
return of(new LoadedRouterConfig([], ngModule));
|
||||
}
|
||||
|
||||
private runCanLoadGuards(moduleInjector: Injector, route: Route, segments: UrlSegment[]):
|
||||
Observable<boolean> {
|
||||
const canLoad = route.canLoad;
|
||||
if (!canLoad || canLoad.length === 0) return of(true);
|
||||
|
||||
const obs = from(canLoad).pipe(map((injectionToken: any) => {
|
||||
const guard = moduleInjector.get(injectionToken);
|
||||
let guardVal;
|
||||
if (isCanLoad(guard)) {
|
||||
guardVal = guard.canLoad(route, segments);
|
||||
} else if (isFunction<CanLoadFn>(guard)) {
|
||||
guardVal = guard(route, segments);
|
||||
} else {
|
||||
throw new Error('Invalid CanLoad guard');
|
||||
}
|
||||
return wrapIntoObservable(guardVal);
|
||||
}));
|
||||
|
||||
return obs.pipe(
|
||||
concatAll(),
|
||||
tap((result: UrlTree|boolean) => {
|
||||
if (!isUrlTree(result)) return;
|
||||
|
||||
const error: Error&{url?: UrlTree} =
|
||||
navigationCancelingError(`Redirecting to "${this.urlSerializer.serialize(result)}"`);
|
||||
error.url = result;
|
||||
throw error;
|
||||
}),
|
||||
every(result => result === true),
|
||||
);
|
||||
}
|
||||
|
||||
private lineralizeSegments(route: Route, urlTree: UrlTree): Observable<UrlSegment[]> {
|
||||
let res: UrlSegment[] = [];
|
||||
let c = urlTree.root;
|
||||
@ -406,27 +438,6 @@ class ApplyRedirects {
|
||||
}
|
||||
}
|
||||
|
||||
function runCanLoadGuard(
|
||||
moduleInjector: Injector, route: Route, segments: UrlSegment[]): Observable<boolean> {
|
||||
const canLoad = route.canLoad;
|
||||
if (!canLoad || canLoad.length === 0) return of(true);
|
||||
|
||||
const obs = from(canLoad).pipe(map((injectionToken: any) => {
|
||||
const guard = moduleInjector.get(injectionToken);
|
||||
let guardVal;
|
||||
if (isCanLoad(guard)) {
|
||||
guardVal = guard.canLoad(route, segments);
|
||||
} else if (isFunction<CanLoadFn>(guard)) {
|
||||
guardVal = guard(route, segments);
|
||||
} else {
|
||||
throw new Error('Invalid CanLoad guard');
|
||||
}
|
||||
return wrapIntoObservable(guardVal);
|
||||
}));
|
||||
|
||||
return obs.pipe(concatAll(), every(result => result === true));
|
||||
}
|
||||
|
||||
function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment[]): {
|
||||
matched: boolean,
|
||||
consumedSegments: UrlSegment[],
|
||||
|
@ -339,6 +339,10 @@ export interface Resolve<T> {
|
||||
* @description
|
||||
*
|
||||
* Interface that a class can implement to be a guard deciding if children can be loaded.
|
||||
* If all guards return `true`, navigation will continue. If any guard returns `false`,
|
||||
* navigation will be cancelled. If any guard returns a `UrlTree`, current navigation will
|
||||
* be cancelled and a new navigation will be kicked off to the `UrlTree` returned from the
|
||||
* guard.
|
||||
*
|
||||
* ```
|
||||
* class UserToken {}
|
||||
@ -400,8 +404,9 @@ export interface Resolve<T> {
|
||||
* @publicApi
|
||||
*/
|
||||
export interface CanLoad {
|
||||
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean>|Promise<boolean>|boolean;
|
||||
canLoad(route: Route, segments: UrlSegment[]):
|
||||
Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean|UrlTree;
|
||||
}
|
||||
|
||||
export type CanLoadFn = (route: Route, segments: UrlSegment[]) =>
|
||||
Observable<boolean>|Promise<boolean>|boolean;
|
||||
Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean|UrlTree;
|
||||
|
Reference in New Issue
Block a user