From 07d8d3994cb1f20fcaf0a521485f75739e474acc Mon Sep 17 00:00:00 2001 From: Marcus Krahl Date: Wed, 19 Jul 2017 20:48:39 +0200 Subject: [PATCH] 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 --- packages/router/src/apply_redirects.ts | 32 ++++++----- packages/router/src/interfaces.ts | 13 +++-- packages/router/test/apply_redirects.spec.ts | 60 +++++++++++++++++++- tools/public_api_guard/router/router.d.ts | 2 +- 4 files changed, 85 insertions(+), 22 deletions(-) diff --git a/packages/router/src/apply_redirects.ts b/packages/router/src/apply_redirects.ts index 81e4ba36eac17..c29a128cadc56 100644 --- a/packages/router/src/apply_redirects.ts +++ b/packages/router/src/apply_redirects.ts @@ -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, route: Route): Observable { + private getChildConfig(ngModule: NgModuleRef, route: Route, segments: UrlSegment[]): + Observable { 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 { +function runCanLoadGuard( + moduleInjector: Injector, route: Route, segments: UrlSegment[]): Observable { 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); diff --git a/packages/router/src/interfaces.ts b/packages/router/src/interfaces.ts index 1d10033c04397..6de696dca4907 100644 --- a/packages/router/src/interfaces.ts +++ b/packages/router/src/interfaces.ts @@ -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 { * ``` * 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 { * class CanLoadTeamSection implements CanLoad { * constructor(private permissions: Permissions, private currentUser: UserToken) {} * - * canLoad(route: Route): Observable|Promise|boolean { - * return this.permissions.canLoadChildren(this.currentUser, route); + * canLoad(route: Route, segments: UrlSegment[]): Observable|Promise|boolean { + * return this.permissions.canLoadChildren(this.currentUser, route, segments); * } * } * @@ -363,7 +364,7 @@ export interface Resolve { * providers: [ * { * provide: 'canLoadTeamSection', - * useValue: (route: Route) => true + * useValue: (route: Route, segments: UrlSegment[]) => true * } * ] * }) @@ -372,4 +373,6 @@ export interface Resolve { * * */ -export interface CanLoad { canLoad(route: Route): Observable|Promise|boolean; } +export interface CanLoad { + canLoad(route: Route, segments: UrlSegment[]): Observable|Promise|boolean; +} diff --git a/packages/router/test/apply_redirects.spec.ts b/packages/router/test/apply_redirects.spec.ts index 70f9abc7f6ac9..c22166c653efb 100644 --- a/packages/router/test/apply_redirects.spec.ts +++ b/packages/router/test/apply_redirects.spec.ts @@ -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(injector, 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(injector, 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); diff --git a/tools/public_api_guard/router/router.d.ts b/tools/public_api_guard/router/router.d.ts index c97c290390c62..96ed908f896aa 100644 --- a/tools/public_api_guard/router/router.d.ts +++ b/tools/public_api_guard/router/router.d.ts @@ -66,7 +66,7 @@ export interface CanDeactivate { } export interface CanLoad { - canLoad(route: Route): Observable | Promise | boolean; + canLoad(route: Route, segments: UrlSegment[]): Observable | Promise | boolean; } /** @experimental */