feat(router): implement canLoad
This commit is contained in:
@ -13,6 +13,7 @@ import 'rxjs/add/operator/concatAll';
|
||||
import {Injector} from '@angular/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Observer} from 'rxjs/Observer';
|
||||
import {from} from 'rxjs/observable/from';
|
||||
import {of } from 'rxjs/observable/of';
|
||||
import {EmptyError} from 'rxjs/util/EmptyError';
|
||||
|
||||
@ -20,7 +21,7 @@ import {Route, Routes} from './config';
|
||||
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
|
||||
import {PRIMARY_OUTLET} from './shared';
|
||||
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
|
||||
import {merge, waitForMap} from './utils/collection';
|
||||
import {andObservables, merge, waitForMap, wrapIntoObservable} from './utils/collection';
|
||||
|
||||
class NoMatch {
|
||||
constructor(public segmentGroup: UrlSegmentGroup = null) {}
|
||||
@ -40,6 +41,12 @@ function absoluteRedirect(segments: UrlSegment[]): Observable<UrlSegmentGroup> {
|
||||
(obs: Observer<UrlSegmentGroup>) => obs.error(new AbsoluteRedirect(segments)));
|
||||
}
|
||||
|
||||
function canLoadFails(route: Route): Observable<LoadedRouterConfig> {
|
||||
return new Observable<LoadedRouterConfig>(
|
||||
(obs: Observer<LoadedRouterConfig>) => obs.error(new Error(
|
||||
`Cannot load children because the guard of the route "path: '${route.path}'" returned false`)));
|
||||
}
|
||||
|
||||
|
||||
export function applyRedirects(
|
||||
injector: Injector, configLoader: RouterConfigLoader, urlTree: UrlTree,
|
||||
@ -209,15 +216,35 @@ function getChildConfig(injector: Injector, configLoader: RouterConfigLoader, ro
|
||||
if (route.children) {
|
||||
return of (new LoadedRouterConfig(route.children, injector, null));
|
||||
} else if (route.loadChildren) {
|
||||
return configLoader.load(injector, route.loadChildren).map(r => {
|
||||
(<any>route)._loadedConfig = r;
|
||||
return r;
|
||||
return runGuards(injector, route).mergeMap(shouldLoad => {
|
||||
if (shouldLoad) {
|
||||
return configLoader.load(injector, route.loadChildren).map(r => {
|
||||
(<any>route)._loadedConfig = r;
|
||||
return r;
|
||||
});
|
||||
} else {
|
||||
return canLoadFails(route);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return of (new LoadedRouterConfig([], injector, null));
|
||||
}
|
||||
}
|
||||
|
||||
function runGuards(injector: Injector, route: Route): Observable<boolean> {
|
||||
const canLoad = route.canLoad;
|
||||
if (!canLoad || canLoad.length === 0) return of (true);
|
||||
const obs = from(canLoad).map(c => {
|
||||
const guard = injector.get(c);
|
||||
if (guard.canLoad) {
|
||||
return wrapIntoObservable(guard.canLoad(route));
|
||||
} else {
|
||||
return wrapIntoObservable(guard(route));
|
||||
}
|
||||
});
|
||||
return andObservables(obs);
|
||||
}
|
||||
|
||||
function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment[]): {
|
||||
matched: boolean,
|
||||
consumedSegments: UrlSegment[],
|
||||
|
@ -492,6 +492,7 @@ export interface Route {
|
||||
canActivate?: any[];
|
||||
canActivateChild?: any[];
|
||||
canDeactivate?: any[];
|
||||
canLoad?: any[];
|
||||
data?: Data;
|
||||
resolve?: ResolveData;
|
||||
children?: Route[];
|
||||
|
@ -7,8 +7,11 @@
|
||||
*/
|
||||
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
|
||||
import {Route} from './config';
|
||||
import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
|
||||
|
||||
|
||||
/**
|
||||
* An interface a class can implement to be a guard deciding if a route can be activated.
|
||||
*
|
||||
@ -68,7 +71,7 @@ export interface CanActivate {
|
||||
*
|
||||
* canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):Observable<boolean>
|
||||
* {
|
||||
* return this.permissions.canActivate(this.currentUser, this.route.params.id);
|
||||
* return this.permissions.canActivate(this.currentUser, route.params.id);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
@ -128,7 +131,7 @@ export interface CanActivateChild {
|
||||
* constructor(private permissions: Permissions, private currentUser: UserToken) {}
|
||||
*
|
||||
* canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):Observable<boolean> {
|
||||
* return this.permissions.canDeactivate(this.currentUser, this.route.params.id);
|
||||
* return this.permissions.canDeactivate(this.currentUser, route.params.id);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
@ -200,3 +203,49 @@ export interface Resolve<T> {
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
|
||||
Observable<any>|Promise<any>|any;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An interface a class can implement to be a guard deciding if a children can be loaded.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* @Injectable()
|
||||
* class CanLoadTeamSection implements CanActivate {
|
||||
* constructor(private permissions: Permissions, private currentUser: UserToken) {}
|
||||
*
|
||||
* canLoad(route: Route):Observable<boolean> {
|
||||
* return this.permissions.canLoadChildren(this.currentUser, route);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* bootstrap(AppComponent, [
|
||||
* CanLoadTeamSection,
|
||||
*
|
||||
* provideRouter([{
|
||||
* path: 'team/:id',
|
||||
* component: Team,
|
||||
* loadChildren: 'team.js',
|
||||
* canLoad: [CanLoadTeamSection]
|
||||
* }])
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* You can also provide a function with the same signature instead of the class:
|
||||
*
|
||||
* ```
|
||||
* bootstrap(AppComponent, [
|
||||
* {provide: 'canLoadTeamSection', useValue: (route: Route) => true},
|
||||
* provideRouter([{
|
||||
* path: 'team/:id',
|
||||
* component: Team,
|
||||
* loadChildren: 'team.js',
|
||||
* canLoad: ['canLoadTeamSection']
|
||||
* }])
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface CanLoad { canLoad(route: Route): Observable<boolean>|Promise<boolean>|boolean; }
|
@ -18,7 +18,6 @@ import {Observable} from 'rxjs/Observable';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
import {from} from 'rxjs/observable/from';
|
||||
import {fromPromise} from 'rxjs/observable/fromPromise';
|
||||
import {of } from 'rxjs/observable/of';
|
||||
|
||||
import {applyRedirects} from './apply_redirects';
|
||||
@ -33,7 +32,7 @@ import {RouterOutletMap} from './router_outlet_map';
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state';
|
||||
import {PRIMARY_OUTLET, Params} from './shared';
|
||||
import {UrlSerializer, UrlTree, createEmptyUrlTree} from './url_tree';
|
||||
import {forEach, merge, shallowEqual, waitForMap} from './utils/collection';
|
||||
import {andObservables, forEach, merge, shallowEqual, waitForMap, wrapIntoObservable} from './utils/collection';
|
||||
import {TreeNode} from './utils/tree';
|
||||
|
||||
declare var Zone: any;
|
||||
@ -628,16 +627,6 @@ class PreActivation {
|
||||
}
|
||||
}
|
||||
|
||||
function wrapIntoObservable<T>(value: T | Observable<T>): Observable<T> {
|
||||
if (value instanceof Observable) {
|
||||
return value;
|
||||
} else if (value instanceof Promise) {
|
||||
return fromPromise(value);
|
||||
} else {
|
||||
return of (value);
|
||||
}
|
||||
}
|
||||
|
||||
class ActivateRoutes {
|
||||
constructor(private futureState: RouterState, private currState: RouterState) {}
|
||||
|
||||
@ -755,10 +744,6 @@ function closestLoadedConfig(
|
||||
return b.length > 0 ? (<any>b[b.length - 1])._routeConfig._loadedConfig : null;
|
||||
}
|
||||
|
||||
function andObservables(observables: Observable<Observable<any>>): Observable<boolean> {
|
||||
return observables.mergeAll().every(result => result === true);
|
||||
}
|
||||
|
||||
function pushQueryParamsAndFragment(state: RouterState): void {
|
||||
if (!shallowEqual(state.snapshot.queryParams, (<any>state.queryParams).value)) {
|
||||
(<any>state.queryParams).next(state.snapshot.queryParams);
|
||||
|
@ -10,6 +10,7 @@ import 'rxjs/add/operator/concatAll';
|
||||
import 'rxjs/add/operator/last';
|
||||
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {fromPromise} from 'rxjs/observable/fromPromise';
|
||||
import {of } from 'rxjs/observable/of';
|
||||
|
||||
import {PRIMARY_OUTLET} from '../shared';
|
||||
@ -115,4 +116,18 @@ export function waitForMap<A, B>(
|
||||
} else {
|
||||
return of (res);
|
||||
}
|
||||
}
|
||||
|
||||
export function andObservables(observables: Observable<Observable<any>>): Observable<boolean> {
|
||||
return observables.mergeAll().every(result => result === true);
|
||||
}
|
||||
|
||||
export function wrapIntoObservable<T>(value: T | Observable<T>): Observable<T> {
|
||||
if (value instanceof Observable) {
|
||||
return value;
|
||||
} else if (value instanceof Promise) {
|
||||
return fromPromise(value);
|
||||
} else {
|
||||
return of (value);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user