feat(router): add UrlSegment[] to CanLoad interface (#13127)

CanLoad now defines UrlSegment[] as a second parameter of the function.
Users can store the initial url segments and refer to them later, e.g. to go
back to the original url after authentication via router.navigate(urlSegments).
Existing code still works as before because the second function parameter
does not have to be defined.

Closes #12411

PR Close #13127
This commit is contained in:
Marcus Krahl 2017-07-19 20:48:39 +02:00 committed by Jason Aden
parent 116946fb11
commit 07d8d3994c
4 changed files with 85 additions and 22 deletions

View File

@ -255,7 +255,7 @@ class ApplyRedirects {
if (!matched) return noMatch(rawSegmentGroup);
const rawSlicedSegments = segments.slice(lastChild);
const childConfig$ = this.getChildConfig(ngModule, route);
const childConfig$ = this.getChildConfig(ngModule, route, segments);
return childConfig$.pipe(mergeMap((routerConfig: LoadedRouterConfig) => {
const childModule = routerConfig.module;
@ -282,7 +282,8 @@ class ApplyRedirects {
}));
}
private getChildConfig(ngModule: NgModuleRef<any>, route: Route): Observable<LoadedRouterConfig> {
private getChildConfig(ngModule: NgModuleRef<any>, route: Route, segments: UrlSegment[]):
Observable<LoadedRouterConfig> {
if (route.children) {
// The children belong to the same module
return of (new LoadedRouterConfig(route.children, ngModule));
@ -294,16 +295,17 @@ class ApplyRedirects {
return of (route._loadedConfig);
}
return runCanLoadGuard(ngModule.injector, route).pipe(mergeMap((shouldLoad: boolean) => {
if (shouldLoad) {
return this.configLoader.load(ngModule.injector, route)
.pipe(map((cfg: LoadedRouterConfig) => {
route._loadedConfig = cfg;
return cfg;
}));
}
return canLoadFails(route);
}));
return runCanLoadGuard(ngModule.injector, route, segments)
.pipe(mergeMap((shouldLoad: boolean) => {
if (shouldLoad) {
return this.configLoader.load(ngModule.injector, route)
.pipe(map((cfg: LoadedRouterConfig) => {
route._loadedConfig = cfg;
return cfg;
}));
}
return canLoadFails(route);
}));
}
return of (new LoadedRouterConfig([], ngModule));
@ -399,13 +401,15 @@ class ApplyRedirects {
}
}
function runCanLoadGuard(moduleInjector: Injector, route: Route): Observable<boolean> {
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);
return wrapIntoObservable(guard.canLoad ? guard.canLoad(route) : guard(route));
return wrapIntoObservable(
guard.canLoad ? guard.canLoad(route, segments) : guard(route, segments));
}));
return andObservables(obs);

View File

@ -10,6 +10,7 @@ import {Observable} from 'rxjs';
import {Route} from './config';
import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
import {UrlSegment} from './url_tree';
/**
@ -316,7 +317,7 @@ export interface Resolve<T> {
* ```
* class UserToken {}
* class Permissions {
* canLoadChildren(user: UserToken, id: string): boolean {
* canLoadChildren(user: UserToken, id: string, segments: UrlSegment[]): boolean {
* return true;
* }
* }
@ -325,8 +326,8 @@ export interface Resolve<T> {
* class CanLoadTeamSection implements CanLoad {
* constructor(private permissions: Permissions, private currentUser: UserToken) {}
*
* canLoad(route: Route): Observable<boolean>|Promise<boolean>|boolean {
* return this.permissions.canLoadChildren(this.currentUser, route);
* canLoad(route: Route, segments: UrlSegment[]): Observable<boolean>|Promise<boolean>|boolean {
* return this.permissions.canLoadChildren(this.currentUser, route, segments);
* }
* }
*
@ -363,7 +364,7 @@ export interface Resolve<T> {
* providers: [
* {
* provide: 'canLoadTeamSection',
* useValue: (route: Route) => true
* useValue: (route: Route, segments: UrlSegment[]) => true
* }
* ]
* })
@ -372,4 +373,6 @@ export interface Resolve<T> {
*
*
*/
export interface CanLoad { canLoad(route: Route): Observable<boolean>|Promise<boolean>|boolean; }
export interface CanLoad {
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean>|Promise<boolean>|boolean;
}

View File

@ -11,8 +11,8 @@ import {TestBed} from '@angular/core/testing';
import {Observable, of } from 'rxjs';
import {applyRedirects} from '../src/apply_redirects';
import {LoadedRouterConfig, Routes} from '../src/config';
import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree, equalSegments} from '../src/url_tree';
import {LoadedRouterConfig, Route, Routes} from '../src/config';
import {DefaultUrlSerializer, UrlSegment, UrlSegmentGroup, UrlTree, equalSegments} from '../src/url_tree';
describe('applyRedirects', () => {
const serializer = new DefaultUrlSerializer();
@ -293,6 +293,62 @@ describe('applyRedirects', () => {
});
it('should pass UrlSegments to functions implementing the canLoad guard interface', () => {
const loadedConfig = new LoadedRouterConfig([{path: 'b', component: ComponentB}], testModule);
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
let passedUrlSegments: UrlSegment[];
const guard = (route: Route, urlSegments: UrlSegment[]) => {
passedUrlSegments = urlSegments;
return true;
};
const injector = {get: (token: any) => token === 'guard' ? guard : {injector}};
const config =
[{path: 'a', component: ComponentA, canLoad: ['guard'], loadChildren: 'children'}];
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config)
.subscribe(
(r) => {
expectTreeToBe(r, '/a/b');
expect(passedUrlSegments.length).toBe(2);
expect(passedUrlSegments[0].path).toBe('a');
expect(passedUrlSegments[1].path).toBe('b');
},
(e) => { throw 'Should not reach'; });
});
it('should pass UrlSegments to objects implementing the canLoad guard interface', () => {
const loadedConfig = new LoadedRouterConfig([{path: 'b', component: ComponentB}], testModule);
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
let passedUrlSegments: UrlSegment[];
const guard = {
canLoad: (route: Route, urlSegments: UrlSegment[]) => {
passedUrlSegments = urlSegments;
return true;
}
};
const injector = {get: (token: any) => token === 'guard' ? guard : {injector}};
const config =
[{path: 'a', component: ComponentA, canLoad: ['guard'], loadChildren: 'children'}];
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config)
.subscribe(
(r) => {
expectTreeToBe(r, '/a/b');
expect(passedUrlSegments.length).toBe(2);
expect(passedUrlSegments[0].path).toBe('a');
expect(passedUrlSegments[1].path).toBe('b');
},
(e) => { throw 'Should not reach'; });
});
it('should work with absolute redirects', () => {
const loadedConfig = new LoadedRouterConfig([{path: '', component: ComponentB}], testModule);

View File

@ -66,7 +66,7 @@ export interface CanDeactivate<T> {
}
export interface CanLoad {
canLoad(route: Route): Observable<boolean> | Promise<boolean> | boolean;
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean> | Promise<boolean> | boolean;
}
/** @experimental */