feat(router): implement canLoad
This commit is contained in:
parent
fc83bbbe98
commit
62e7c0f464
@ -12,7 +12,7 @@ export {Data, ResolveData, Route, RouterConfig, Routes} from './src/config';
|
|||||||
export {RouterLink, RouterLinkWithHref} from './src/directives/router_link';
|
export {RouterLink, RouterLinkWithHref} from './src/directives/router_link';
|
||||||
export {RouterLinkActive} from './src/directives/router_link_active';
|
export {RouterLinkActive} from './src/directives/router_link_active';
|
||||||
export {RouterOutlet} from './src/directives/router_outlet';
|
export {RouterOutlet} from './src/directives/router_outlet';
|
||||||
export {CanActivate, CanActivateChild, CanDeactivate, Resolve} from './src/interfaces';
|
export {CanActivate, CanActivateChild, CanDeactivate, CanLoad, Resolve} from './src/interfaces';
|
||||||
export {Event, NavigationCancel, NavigationEnd, NavigationError, NavigationExtras, NavigationStart, Router, RoutesRecognized} from './src/router';
|
export {Event, NavigationCancel, NavigationEnd, NavigationError, NavigationExtras, NavigationStart, Router, RoutesRecognized} from './src/router';
|
||||||
export {ROUTER_DIRECTIVES, RouterModule, RouterModuleWithoutProviders} from './src/router_module';
|
export {ROUTER_DIRECTIVES, RouterModule, RouterModuleWithoutProviders} from './src/router_module';
|
||||||
export {RouterOutletMap} from './src/router_outlet_map';
|
export {RouterOutletMap} from './src/router_outlet_map';
|
||||||
|
@ -13,6 +13,7 @@ import 'rxjs/add/operator/concatAll';
|
|||||||
import {Injector} from '@angular/core';
|
import {Injector} from '@angular/core';
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
import {Observer} from 'rxjs/Observer';
|
import {Observer} from 'rxjs/Observer';
|
||||||
|
import {from} from 'rxjs/observable/from';
|
||||||
import {of } from 'rxjs/observable/of';
|
import {of } from 'rxjs/observable/of';
|
||||||
import {EmptyError} from 'rxjs/util/EmptyError';
|
import {EmptyError} from 'rxjs/util/EmptyError';
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ import {Route, Routes} from './config';
|
|||||||
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
|
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
|
||||||
import {PRIMARY_OUTLET} from './shared';
|
import {PRIMARY_OUTLET} from './shared';
|
||||||
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
|
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
|
||||||
import {merge, waitForMap} from './utils/collection';
|
import {andObservables, merge, waitForMap, wrapIntoObservable} from './utils/collection';
|
||||||
|
|
||||||
class NoMatch {
|
class NoMatch {
|
||||||
constructor(public segmentGroup: UrlSegmentGroup = null) {}
|
constructor(public segmentGroup: UrlSegmentGroup = null) {}
|
||||||
@ -40,6 +41,12 @@ function absoluteRedirect(segments: UrlSegment[]): Observable<UrlSegmentGroup> {
|
|||||||
(obs: Observer<UrlSegmentGroup>) => obs.error(new AbsoluteRedirect(segments)));
|
(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(
|
export function applyRedirects(
|
||||||
injector: Injector, configLoader: RouterConfigLoader, urlTree: UrlTree,
|
injector: Injector, configLoader: RouterConfigLoader, urlTree: UrlTree,
|
||||||
@ -209,15 +216,35 @@ function getChildConfig(injector: Injector, configLoader: RouterConfigLoader, ro
|
|||||||
if (route.children) {
|
if (route.children) {
|
||||||
return of (new LoadedRouterConfig(route.children, injector, null));
|
return of (new LoadedRouterConfig(route.children, injector, null));
|
||||||
} else if (route.loadChildren) {
|
} else if (route.loadChildren) {
|
||||||
return configLoader.load(injector, route.loadChildren).map(r => {
|
return runGuards(injector, route).mergeMap(shouldLoad => {
|
||||||
(<any>route)._loadedConfig = r;
|
if (shouldLoad) {
|
||||||
return r;
|
return configLoader.load(injector, route.loadChildren).map(r => {
|
||||||
|
(<any>route)._loadedConfig = r;
|
||||||
|
return r;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return canLoadFails(route);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return of (new LoadedRouterConfig([], injector, null));
|
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[]): {
|
function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment[]): {
|
||||||
matched: boolean,
|
matched: boolean,
|
||||||
consumedSegments: UrlSegment[],
|
consumedSegments: UrlSegment[],
|
||||||
|
@ -492,6 +492,7 @@ export interface Route {
|
|||||||
canActivate?: any[];
|
canActivate?: any[];
|
||||||
canActivateChild?: any[];
|
canActivateChild?: any[];
|
||||||
canDeactivate?: any[];
|
canDeactivate?: any[];
|
||||||
|
canLoad?: any[];
|
||||||
data?: Data;
|
data?: Data;
|
||||||
resolve?: ResolveData;
|
resolve?: ResolveData;
|
||||||
children?: Route[];
|
children?: Route[];
|
||||||
|
@ -7,8 +7,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
|
|
||||||
|
import {Route} from './config';
|
||||||
import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
|
import {ActivatedRouteSnapshot, RouterStateSnapshot} from './router_state';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface a class can implement to be a guard deciding if a route can be activated.
|
* 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>
|
* 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) {}
|
* constructor(private permissions: Permissions, private currentUser: UserToken) {}
|
||||||
*
|
*
|
||||||
* canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):Observable<boolean> {
|
* 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):
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
|
||||||
Observable<any>|Promise<any>|any;
|
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 {Subject} from 'rxjs/Subject';
|
||||||
import {Subscription} from 'rxjs/Subscription';
|
import {Subscription} from 'rxjs/Subscription';
|
||||||
import {from} from 'rxjs/observable/from';
|
import {from} from 'rxjs/observable/from';
|
||||||
import {fromPromise} from 'rxjs/observable/fromPromise';
|
|
||||||
import {of } from 'rxjs/observable/of';
|
import {of } from 'rxjs/observable/of';
|
||||||
|
|
||||||
import {applyRedirects} from './apply_redirects';
|
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 {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state';
|
||||||
import {PRIMARY_OUTLET, Params} from './shared';
|
import {PRIMARY_OUTLET, Params} from './shared';
|
||||||
import {UrlSerializer, UrlTree, createEmptyUrlTree} from './url_tree';
|
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';
|
import {TreeNode} from './utils/tree';
|
||||||
|
|
||||||
declare var Zone: any;
|
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 {
|
class ActivateRoutes {
|
||||||
constructor(private futureState: RouterState, private currState: RouterState) {}
|
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;
|
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 {
|
function pushQueryParamsAndFragment(state: RouterState): void {
|
||||||
if (!shallowEqual(state.snapshot.queryParams, (<any>state.queryParams).value)) {
|
if (!shallowEqual(state.snapshot.queryParams, (<any>state.queryParams).value)) {
|
||||||
(<any>state.queryParams).next(state.snapshot.queryParams);
|
(<any>state.queryParams).next(state.snapshot.queryParams);
|
||||||
|
@ -10,6 +10,7 @@ import 'rxjs/add/operator/concatAll';
|
|||||||
import 'rxjs/add/operator/last';
|
import 'rxjs/add/operator/last';
|
||||||
|
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
|
import {fromPromise} from 'rxjs/observable/fromPromise';
|
||||||
import {of } from 'rxjs/observable/of';
|
import {of } from 'rxjs/observable/of';
|
||||||
|
|
||||||
import {PRIMARY_OUTLET} from '../shared';
|
import {PRIMARY_OUTLET} from '../shared';
|
||||||
@ -115,4 +116,18 @@ export function waitForMap<A, B>(
|
|||||||
} else {
|
} else {
|
||||||
return of (res);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
@ -168,6 +168,89 @@ describe('applyRedirects', () => {
|
|||||||
expect(e.message).toEqual('Loading Error');
|
expect(e.message).toEqual('Loading Error');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should load when all canLoad guards return true', () => {
|
||||||
|
const loadedConfig = new LoadedRouterConfig(
|
||||||
|
[{path: 'b', component: ComponentB}], <any>'stubInjector', <any>'stubFactoryResolver');
|
||||||
|
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||||
|
|
||||||
|
const guard = () => true;
|
||||||
|
const injector = {get: () => guard};
|
||||||
|
|
||||||
|
const config = [{
|
||||||
|
path: 'a',
|
||||||
|
component: ComponentA,
|
||||||
|
canLoad: ['guard1', 'guard2'],
|
||||||
|
loadChildren: 'children'
|
||||||
|
}];
|
||||||
|
|
||||||
|
applyRedirects(<any>injector, <any>loader, tree('a/b'), config).forEach(r => {
|
||||||
|
compareTrees(r, tree('/a/b'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not load when any canLoad guards return false', () => {
|
||||||
|
const loadedConfig = new LoadedRouterConfig(
|
||||||
|
[{path: 'b', component: ComponentB}], <any>'stubInjector', <any>'stubFactoryResolver');
|
||||||
|
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||||
|
|
||||||
|
const trueGuard = () => true;
|
||||||
|
const falseGuard = () => false;
|
||||||
|
const injector = {get: (guardName: any) => guardName === 'guard1' ? trueGuard : falseGuard};
|
||||||
|
|
||||||
|
const config = [{
|
||||||
|
path: 'a',
|
||||||
|
component: ComponentA,
|
||||||
|
canLoad: ['guard1', 'guard2'],
|
||||||
|
loadChildren: 'children'
|
||||||
|
}];
|
||||||
|
|
||||||
|
applyRedirects(<any>injector, <any>loader, tree('a/b'), config)
|
||||||
|
.subscribe(
|
||||||
|
() => { throw 'Should not reach'; },
|
||||||
|
(e) => {
|
||||||
|
expect(e.message).toEqual(
|
||||||
|
`Cannot load children because the guard of the route "path: 'a'" returned false`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not load when any canLoad guards is rejected (promises)', () => {
|
||||||
|
const loadedConfig = new LoadedRouterConfig(
|
||||||
|
[{path: 'b', component: ComponentB}], <any>'stubInjector', <any>'stubFactoryResolver');
|
||||||
|
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||||
|
|
||||||
|
const trueGuard = () => Promise.resolve(true);
|
||||||
|
const falseGuard = () => Promise.reject('someError');
|
||||||
|
const injector = {get: (guardName: any) => guardName === 'guard1' ? trueGuard : falseGuard};
|
||||||
|
|
||||||
|
const config = [{
|
||||||
|
path: 'a',
|
||||||
|
component: ComponentA,
|
||||||
|
canLoad: ['guard1', 'guard2'],
|
||||||
|
loadChildren: 'children'
|
||||||
|
}];
|
||||||
|
|
||||||
|
applyRedirects(<any>injector, <any>loader, tree('a/b'), config)
|
||||||
|
.subscribe(
|
||||||
|
() => { throw 'Should not reach'; }, (e) => { expect(e).toEqual('someError'); });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with objects implementing the CanLoad interface', () => {
|
||||||
|
const loadedConfig = new LoadedRouterConfig(
|
||||||
|
[{path: 'b', component: ComponentB}], <any>'stubInjector', <any>'stubFactoryResolver');
|
||||||
|
const loader = {load: (injector: any, p: any) => of (loadedConfig)};
|
||||||
|
|
||||||
|
const guard = {canLoad: () => Promise.resolve(true)};
|
||||||
|
const injector = {get: () => guard};
|
||||||
|
|
||||||
|
const config =
|
||||||
|
[{path: 'a', component: ComponentA, canLoad: ['guard'], loadChildren: 'children'}];
|
||||||
|
|
||||||
|
applyRedirects(<any>injector, <any>loader, tree('a/b'), config)
|
||||||
|
.subscribe(
|
||||||
|
(r) => { compareTrees(r, tree('/a/b')); }, (e) => { throw 'Should not reach'; });
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('empty paths', () => {
|
describe('empty paths', () => {
|
||||||
|
@ -1116,6 +1116,36 @@ describe('Integration', () => {
|
|||||||
expect(location.path()).toEqual('/team/33');
|
expect(location.path()).toEqual('/team/33');
|
||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('should work when returns an observable', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
addProviders([{
|
||||||
|
provide: 'CanDeactivate',
|
||||||
|
useValue: (c: TeamCmp, a: ActivatedRouteSnapshot, b: RouterStateSnapshot) => {
|
||||||
|
return of (false);
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works',
|
||||||
|
fakeAsync(inject(
|
||||||
|
[Router, TestComponentBuilder, Location],
|
||||||
|
(router: Router, tcb: TestComponentBuilder, location: Location) => {
|
||||||
|
const fixture = createRoot(tcb, router, RootCmp);
|
||||||
|
|
||||||
|
router.resetConfig(
|
||||||
|
[{path: 'team/:id', component: TeamCmp, canDeactivate: ['CanDeactivate']}]);
|
||||||
|
|
||||||
|
router.navigateByUrl('/team/22');
|
||||||
|
advance(fixture);
|
||||||
|
expect(location.path()).toEqual('/team/22');
|
||||||
|
|
||||||
|
router.navigateByUrl('/team/33');
|
||||||
|
advance(fixture);
|
||||||
|
expect(location.path()).toEqual('/team/22');
|
||||||
|
})));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('CanActivateChild', () => {
|
describe('CanActivateChild', () => {
|
||||||
@ -1151,33 +1181,71 @@ describe('Integration', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('should work when returns an observable', () => {
|
describe('CanLoad', () => {
|
||||||
beforeEach(() => {
|
describe('should not load children when CanLoad returns false', () => {
|
||||||
addProviders([{
|
beforeEach(() => {
|
||||||
provide: 'CanDeactivate',
|
addProviders([
|
||||||
useValue: (c: TeamCmp, a: ActivatedRouteSnapshot, b: RouterStateSnapshot) => {
|
{provide: 'alwaysFalse', useValue: (a: any) => false},
|
||||||
return of (false);
|
{provide: 'alwaysTrue', useValue: (a: any) => true}
|
||||||
}
|
]);
|
||||||
}]);
|
});
|
||||||
|
|
||||||
|
it('works',
|
||||||
|
fakeAsync(inject(
|
||||||
|
[Router, TestComponentBuilder, Location, NgModuleFactoryLoader],
|
||||||
|
(router: Router, tcb: TestComponentBuilder, location: Location,
|
||||||
|
loader: SpyNgModuleFactoryLoader) => {
|
||||||
|
|
||||||
|
@Component({selector: 'lazy', template: 'lazy-loaded'})
|
||||||
|
class LazyLoadedComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [LazyLoadedComponent],
|
||||||
|
providers: [provideRoutes([{path: 'loaded', component: LazyLoadedComponent}])],
|
||||||
|
imports: [RouterModuleWithoutProviders],
|
||||||
|
entryComponents: [LazyLoadedComponent]
|
||||||
|
})
|
||||||
|
class LoadedModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.stubbedModules = {lazyFalse: LoadedModule, lazyTrue: LoadedModule};
|
||||||
|
const fixture = createRoot(tcb, router, RootCmp);
|
||||||
|
|
||||||
|
router.resetConfig([
|
||||||
|
{path: 'lazyFalse', canLoad: ['alwaysFalse'], loadChildren: 'lazyFalse'},
|
||||||
|
{path: 'lazyTrue', canLoad: ['alwaysTrue'], loadChildren: 'lazyTrue'}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const recordedEvents: any[] = [];
|
||||||
|
router.events.forEach(e => recordedEvents.push(e));
|
||||||
|
|
||||||
|
|
||||||
|
// failed navigation
|
||||||
|
router.navigateByUrl('/lazyFalse/loaded').catch(s => {});
|
||||||
|
advance(fixture);
|
||||||
|
|
||||||
|
expect(location.path()).toEqual('/');
|
||||||
|
|
||||||
|
expectEvents(recordedEvents, [
|
||||||
|
[NavigationStart, '/lazyFalse/loaded'], [NavigationError, '/lazyFalse/loaded']
|
||||||
|
]);
|
||||||
|
|
||||||
|
recordedEvents.splice(0);
|
||||||
|
|
||||||
|
|
||||||
|
// successful navigation
|
||||||
|
router.navigateByUrl('/lazyTrue/loaded');
|
||||||
|
advance(fixture);
|
||||||
|
|
||||||
|
expect(location.path()).toEqual('/lazyTrue/loaded');
|
||||||
|
|
||||||
|
expectEvents(recordedEvents, [
|
||||||
|
[NavigationStart, '/lazyTrue/loaded'], [RoutesRecognized, '/lazyTrue/loaded'],
|
||||||
|
[NavigationEnd, '/lazyTrue/loaded']
|
||||||
|
]);
|
||||||
|
})));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works',
|
|
||||||
fakeAsync(inject(
|
|
||||||
[Router, TestComponentBuilder, Location],
|
|
||||||
(router: Router, tcb: TestComponentBuilder, location: Location) => {
|
|
||||||
const fixture = createRoot(tcb, router, RootCmp);
|
|
||||||
|
|
||||||
router.resetConfig(
|
|
||||||
[{path: 'team/:id', component: TeamCmp, canDeactivate: ['CanDeactivate']}]);
|
|
||||||
|
|
||||||
router.navigateByUrl('/team/22');
|
|
||||||
advance(fixture);
|
|
||||||
expect(location.path()).toEqual('/team/22');
|
|
||||||
|
|
||||||
router.navigateByUrl('/team/33');
|
|
||||||
advance(fixture);
|
|
||||||
expect(location.path()).toEqual('/team/22');
|
|
||||||
})));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
5
tools/public_api_guard/router/index.d.ts
vendored
5
tools/public_api_guard/router/index.d.ts
vendored
@ -34,6 +34,11 @@ export interface CanDeactivate<T> {
|
|||||||
canDeactivate(component: T, route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean;
|
canDeactivate(component: T, route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @stable */
|
||||||
|
export interface CanLoad {
|
||||||
|
canLoad(route: Route): Observable<boolean> | Promise<boolean> | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare type Data = {
|
export declare type Data = {
|
||||||
[name: string]: any;
|
[name: string]: any;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user