Skip to content

Commit

Permalink
feat(router): add UrlSegment[] to CanLoad interface (#13127)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
marcuskrahl authored and jasonaden committed Aug 16, 2018
1 parent 116946f commit 07d8d39
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 22 deletions.
32 changes: 18 additions & 14 deletions packages/router/src/apply_redirects.ts
Expand Up @@ -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;
Expand All @@ -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));
Expand All @@ -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));
Expand Down Expand Up @@ -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);
Expand Down
13 changes: 8 additions & 5 deletions packages/router/src/interfaces.ts
Expand Up @@ -10,6 +10,7 @@ import {Observable} from 'rxjs';

import {Route} from './config';
import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
import {UrlSegment} from './url_tree';


/**
Expand Down Expand Up @@ -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;
* }
* }
Expand All @@ -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);
* }
* }
*
Expand Down Expand Up @@ -363,7 +364,7 @@ export interface Resolve<T> {
* providers: [
* {
* provide: 'canLoadTeamSection',
* useValue: (route: Route) => true
* useValue: (route: Route, segments: UrlSegment[]) => true
* }
* ]
* })
Expand All @@ -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;
}
60 changes: 58 additions & 2 deletions packages/router/test/apply_redirects.spec.ts
Expand Up @@ -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();
Expand Down Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion tools/public_api_guard/router/router.d.ts
Expand Up @@ -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 */
Expand Down

0 comments on commit 07d8d39

Please sign in to comment.