feat(router): add support for custom route reuse strategies

This commit is contained in:
vsavkin
2016-11-29 23:21:41 -08:00
parent c4bbafc291
commit 42cf06fa12
9 changed files with 319 additions and 35 deletions

View File

@ -9,24 +9,27 @@
import {Routes} from '../src/config';
import {createRouterState} from '../src/create_router_state';
import {recognize} from '../src/recognize';
import {DefaultRouteReuseStrategy} from '../src/router';
import {ActivatedRoute, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from '../src/router_state';
import {PRIMARY_OUTLET} from '../src/shared';
import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree} from '../src/url_tree';
import {TreeNode} from '../src/utils/tree';
describe('create router state', () => {
const reuseStrategy = new DefaultRouteReuseStrategy();
const emptyState = () =>
createEmptyState(new UrlTree(new UrlSegmentGroup([], {}), {}, null), RootComponent);
it('should work create new state', () => {
const state = createRouterState(
createState(
[
{path: 'a', component: ComponentA},
{path: 'b', component: ComponentB, outlet: 'left'},
{path: 'c', component: ComponentC, outlet: 'right'}
],
'a(left:b//right:c)'),
reuseStrategy, createState(
[
{path: 'a', component: ComponentA},
{path: 'b', component: ComponentB, outlet: 'left'},
{path: 'c', component: ComponentC, outlet: 'right'}
],
'a(left:b//right:c)'),
emptyState());
checkActivatedRoute(state.root, RootComponent);
@ -43,9 +46,10 @@ describe('create router state', () => {
{path: 'c', component: ComponentC, outlet: 'left'}
];
const prevState = createRouterState(createState(config, 'a(left:b)'), emptyState());
const prevState =
createRouterState(reuseStrategy, createState(config, 'a(left:b)'), emptyState());
advanceState(prevState);
const state = createRouterState(createState(config, 'a(left:c)'), prevState);
const state = createRouterState(reuseStrategy, createState(config, 'a(left:c)'), prevState);
expect(prevState.root).toBe(state.root);
const prevC = prevState.children(prevState.root);
@ -65,9 +69,11 @@ describe('create router state', () => {
}];
const prevState = createRouterState(createState(config, 'a/1;p=11/(b//right:c)'), emptyState());
const prevState = createRouterState(
reuseStrategy, createState(config, 'a/1;p=11/(b//right:c)'), emptyState());
advanceState(prevState);
const state = createRouterState(createState(config, 'a/2;p=22/(b//right:c)'), prevState);
const state =
createRouterState(reuseStrategy, createState(config, 'a/2;p=22/(b//right:c)'), prevState);
expect(prevState.root).toBe(state.root);
const prevP = prevState.firstChild(prevState.root);

View File

@ -13,7 +13,7 @@ import {expect} from '@angular/platform-browser/testing/matchers';
import {Observable} from 'rxjs/Observable';
import {map} from 'rxjs/operator/map';
import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, CanDeactivate, Event, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, PRIMARY_OUTLET, Params, PreloadAllModules, PreloadingStrategy, Resolve, Router, RouterModule, RouterStateSnapshot, RoutesRecognized, UrlHandlingStrategy, UrlSegmentGroup, UrlTree} from '../index';
import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, CanDeactivate, DetachedRouteHandle, Event, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, PRIMARY_OUTLET, Params, PreloadAllModules, PreloadingStrategy, Resolve, RouteReuseStrategy, Router, RouterModule, RouterStateSnapshot, RoutesRecognized, UrlHandlingStrategy, UrlSegmentGroup, UrlTree} from '../index';
import {RouterPreloader} from '../src/router_preloader';
import {forEach} from '../src/utils/collection';
import {RouterTestingModule, SpyNgModuleFactoryLoader} from '../testing';
@ -2392,6 +2392,105 @@ describe('Integration', () => {
})));
});
});
describe('Custom Route Reuse Strategy', () => {
class AttachDetachReuseStrategy implements RouteReuseStrategy {
stored: {[k: string]: DetachedRouteHandle} = {};
shouldDetach(route: ActivatedRouteSnapshot): boolean {
return route.routeConfig.path === 'a';
}
store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void {
this.stored[route.routeConfig.path] = detachedTree;
}
shouldAttach(route: ActivatedRouteSnapshot): boolean {
return !!this.stored[route.routeConfig.path];
}
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
return this.stored[route.routeConfig.path];
}
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
return future.routeConfig === curr.routeConfig;
}
}
class ShortLifecycle implements RouteReuseStrategy {
shouldDetach(route: ActivatedRouteSnapshot): boolean { return false; }
store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void {}
shouldAttach(route: ActivatedRouteSnapshot): boolean { return false; }
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle { return null; }
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
if (future.routeConfig !== curr.routeConfig) {
return false;
} else if (Object.keys(future.params).length !== Object.keys(curr.params).length) {
return false;
} else {
return Object.keys(future.params).every(k => future.params[k] === curr.params[k]);
}
}
}
it('should support attaching & detaching fragments',
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
const fixture = createRoot(router, RootCmp);
router.routeReuseStrategy = new AttachDetachReuseStrategy();
router.resetConfig([
{path: 'a', component: TeamCmp, children: [{path: 'b', component: SimpleCmp}]},
{path: 'c', component: UserCmp}
]);
router.navigateByUrl('/a/b');
advance(fixture);
const teamCmp = fixture.debugElement.children[1].componentInstance;
const simpleCmp = fixture.debugElement.children[1].children[1].componentInstance;
expect(location.path()).toEqual('/a/b');
expect(teamCmp).toBeDefined();
expect(simpleCmp).toBeDefined();
router.navigateByUrl('/c');
advance(fixture);
expect(location.path()).toEqual('/c');
expect(fixture.debugElement.children[1].componentInstance).toBeAnInstanceOf(UserCmp);
router.navigateByUrl('/a;p=1/b;p=2');
advance(fixture);
const teamCmp2 = fixture.debugElement.children[1].componentInstance;
const simpleCmp2 = fixture.debugElement.children[1].children[1].componentInstance;
expect(location.path()).toEqual('/a;p=1/b;p=2');
expect(teamCmp2).toBe(teamCmp);
expect(simpleCmp2).toBe(simpleCmp);
expect(teamCmp.route).toBe(router.routerState.root.firstChild);
expect(teamCmp.route.snapshot).toBe(router.routerState.snapshot.root.firstChild);
expect(teamCmp.route.snapshot.params).toEqual({p: '1'});
expect(teamCmp.route.firstChild.snapshot.params).toEqual({p: '2'});
})));
it('should support shorter lifecycles',
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
const fixture = createRoot(router, RootCmp);
router.routeReuseStrategy = new ShortLifecycle();
router.resetConfig([{path: 'a', component: SimpleCmp}]);
router.navigateByUrl('/a');
advance(fixture);
const simpleCmp1 = fixture.debugElement.children[1].componentInstance;
expect(location.path()).toEqual('/a');
router.navigateByUrl('/a;p=1');
advance(fixture);
expect(location.path()).toEqual('/a;p=1');
const simpleCmp2 = fixture.debugElement.children[1].componentInstance;
expect(simpleCmp1).not.toBe(simpleCmp2);
})));
});
});
function expectEvents(events: Event[], pairs: any[]) {