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:
parent
116946fb11
commit
07d8d3994c
|
@ -255,7 +255,7 @@ class ApplyRedirects {
|
||||||
if (!matched) return noMatch(rawSegmentGroup);
|
if (!matched) return noMatch(rawSegmentGroup);
|
||||||
|
|
||||||
const rawSlicedSegments = segments.slice(lastChild);
|
const rawSlicedSegments = segments.slice(lastChild);
|
||||||
const childConfig$ = this.getChildConfig(ngModule, route);
|
const childConfig$ = this.getChildConfig(ngModule, route, segments);
|
||||||
|
|
||||||
return childConfig$.pipe(mergeMap((routerConfig: LoadedRouterConfig) => {
|
return childConfig$.pipe(mergeMap((routerConfig: LoadedRouterConfig) => {
|
||||||
const childModule = routerConfig.module;
|
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) {
|
if (route.children) {
|
||||||
// The children belong to the same module
|
// The children belong to the same module
|
||||||
return of (new LoadedRouterConfig(route.children, ngModule));
|
return of (new LoadedRouterConfig(route.children, ngModule));
|
||||||
|
@ -294,16 +295,17 @@ class ApplyRedirects {
|
||||||
return of (route._loadedConfig);
|
return of (route._loadedConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
return runCanLoadGuard(ngModule.injector, route).pipe(mergeMap((shouldLoad: boolean) => {
|
return runCanLoadGuard(ngModule.injector, route, segments)
|
||||||
if (shouldLoad) {
|
.pipe(mergeMap((shouldLoad: boolean) => {
|
||||||
return this.configLoader.load(ngModule.injector, route)
|
if (shouldLoad) {
|
||||||
.pipe(map((cfg: LoadedRouterConfig) => {
|
return this.configLoader.load(ngModule.injector, route)
|
||||||
route._loadedConfig = cfg;
|
.pipe(map((cfg: LoadedRouterConfig) => {
|
||||||
return cfg;
|
route._loadedConfig = cfg;
|
||||||
}));
|
return cfg;
|
||||||
}
|
}));
|
||||||
return canLoadFails(route);
|
}
|
||||||
}));
|
return canLoadFails(route);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return of (new LoadedRouterConfig([], ngModule));
|
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;
|
const canLoad = route.canLoad;
|
||||||
if (!canLoad || canLoad.length === 0) return of (true);
|
if (!canLoad || canLoad.length === 0) return of (true);
|
||||||
|
|
||||||
const obs = from(canLoad).pipe(map((injectionToken: any) => {
|
const obs = from(canLoad).pipe(map((injectionToken: any) => {
|
||||||
const guard = moduleInjector.get(injectionToken);
|
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);
|
return andObservables(obs);
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {Observable} from 'rxjs';
|
||||||
|
|
||||||
import {Route} from './config';
|
import {Route} from './config';
|
||||||
import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
|
import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
|
||||||
|
import {UrlSegment} from './url_tree';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -316,7 +317,7 @@ export interface Resolve<T> {
|
||||||
* ```
|
* ```
|
||||||
* class UserToken {}
|
* class UserToken {}
|
||||||
* class Permissions {
|
* class Permissions {
|
||||||
* canLoadChildren(user: UserToken, id: string): boolean {
|
* canLoadChildren(user: UserToken, id: string, segments: UrlSegment[]): boolean {
|
||||||
* return true;
|
* return true;
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
|
@ -325,8 +326,8 @@ export interface Resolve<T> {
|
||||||
* class CanLoadTeamSection implements CanLoad {
|
* class CanLoadTeamSection implements CanLoad {
|
||||||
* constructor(private permissions: Permissions, private currentUser: UserToken) {}
|
* constructor(private permissions: Permissions, private currentUser: UserToken) {}
|
||||||
*
|
*
|
||||||
* canLoad(route: Route): Observable<boolean>|Promise<boolean>|boolean {
|
* canLoad(route: Route, segments: UrlSegment[]): Observable<boolean>|Promise<boolean>|boolean {
|
||||||
* return this.permissions.canLoadChildren(this.currentUser, route);
|
* return this.permissions.canLoadChildren(this.currentUser, route, segments);
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
|
@ -363,7 +364,7 @@ export interface Resolve<T> {
|
||||||
* providers: [
|
* providers: [
|
||||||
* {
|
* {
|
||||||
* provide: 'canLoadTeamSection',
|
* 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;
|
||||||
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ import {TestBed} from '@angular/core/testing';
|
||||||
import {Observable, of } from 'rxjs';
|
import {Observable, of } from 'rxjs';
|
||||||
|
|
||||||
import {applyRedirects} from '../src/apply_redirects';
|
import {applyRedirects} from '../src/apply_redirects';
|
||||||
import {LoadedRouterConfig, Routes} from '../src/config';
|
import {LoadedRouterConfig, Route, Routes} from '../src/config';
|
||||||
import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree, equalSegments} from '../src/url_tree';
|
import {DefaultUrlSerializer, UrlSegment, UrlSegmentGroup, UrlTree, equalSegments} from '../src/url_tree';
|
||||||
|
|
||||||
describe('applyRedirects', () => {
|
describe('applyRedirects', () => {
|
||||||
const serializer = new DefaultUrlSerializer();
|
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', () => {
|
it('should work with absolute redirects', () => {
|
||||||
const loadedConfig = new LoadedRouterConfig([{path: '', component: ComponentB}], testModule);
|
const loadedConfig = new LoadedRouterConfig([{path: '', component: ComponentB}], testModule);
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ export interface CanDeactivate<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CanLoad {
|
export interface CanLoad {
|
||||||
canLoad(route: Route): Observable<boolean> | Promise<boolean> | boolean;
|
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean> | Promise<boolean> | boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
|
|
Loading…
Reference in New Issue