fix(router): Only retrieve stored route when reuse strategy indicates it should reattach (#30263)
When creating the router state, the `RouteReuseStrategy#retrieve` should
only be called when `RouteReuseStrategy#shouldAttach` returns `true`.
That is, we should only retrieve a stored route when the reuse strategy
indicates that there is one stored and that it should be reattached.
This now matches the behavior in the route activation:
1d12c50f63/packages/router/src/operators/activate_routes.ts (L170-L172)
Fixes #23162
PR Close #30263
This commit is contained in:
parent
9ffd9f6f37
commit
6bceb709df
|
@ -28,22 +28,21 @@ function createNode(
|
||||||
value._futureSnapshot = curr.value;
|
value._futureSnapshot = curr.value;
|
||||||
const children = createOrReuseChildren(routeReuseStrategy, curr, prevState);
|
const children = createOrReuseChildren(routeReuseStrategy, curr, prevState);
|
||||||
return new TreeNode<ActivatedRoute>(value, children);
|
return new TreeNode<ActivatedRoute>(value, children);
|
||||||
|
|
||||||
// retrieve an activated route that is used to be displayed, but is not currently displayed
|
|
||||||
} else {
|
} else {
|
||||||
const detachedRouteHandle =
|
if (routeReuseStrategy.shouldAttach(curr.value)) {
|
||||||
<DetachedRouteHandleInternal>routeReuseStrategy.retrieve(curr.value);
|
// retrieve an activated route that is used to be displayed, but is not currently displayed
|
||||||
if (detachedRouteHandle) {
|
const detachedRouteHandle = routeReuseStrategy.retrieve(curr.value);
|
||||||
const tree: TreeNode<ActivatedRoute> = detachedRouteHandle.route;
|
if (detachedRouteHandle !== null) {
|
||||||
|
const tree = (detachedRouteHandle as DetachedRouteHandleInternal).route;
|
||||||
setFutureSnapshotsOfActivatedRoutes(curr, tree);
|
setFutureSnapshotsOfActivatedRoutes(curr, tree);
|
||||||
return tree;
|
return tree;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
|
||||||
const value = createActivatedRoute(curr.value);
|
const value = createActivatedRoute(curr.value);
|
||||||
const children = curr.children.map(c => createNode(routeReuseStrategy, c));
|
const children = curr.children.map(c => createNode(routeReuseStrategy, c));
|
||||||
return new TreeNode<ActivatedRoute>(value, children);
|
return new TreeNode<ActivatedRoute>(value, children);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setFutureSnapshotsOfActivatedRoutes(
|
function setFutureSnapshotsOfActivatedRoutes(
|
||||||
|
|
|
@ -92,25 +92,18 @@ describe('create router state', () => {
|
||||||
checkActivatedRoute(currC[1], ComponentB, 'right');
|
checkActivatedRoute(currC[1], ComponentB, 'right');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should cache the retrieved routeReuseStrategy', () => {
|
it('should not retrieve routes when `shouldAttach` is always false', () => {
|
||||||
const config = [
|
const config = [
|
||||||
{path: 'a', component: ComponentA}, {path: 'b', component: ComponentB, outlet: 'left'},
|
{path: 'a', component: ComponentA}, {path: 'b', component: ComponentB, outlet: 'left'},
|
||||||
{path: 'c', component: ComponentC, outlet: 'left'}
|
{path: 'c', component: ComponentC, outlet: 'left'}
|
||||||
];
|
];
|
||||||
spyOn(reuseStrategy, 'retrieve').and.callThrough();
|
spyOn(reuseStrategy, 'retrieve');
|
||||||
|
|
||||||
const prevState =
|
const prevState =
|
||||||
createRouterState(reuseStrategy, createState(config, 'a(left:b)'), emptyState());
|
createRouterState(reuseStrategy, createState(config, 'a(left:b)'), emptyState());
|
||||||
advanceState(prevState);
|
advanceState(prevState);
|
||||||
|
createRouterState(reuseStrategy, createState(config, 'a(left:c)'), prevState);
|
||||||
// Expect 2 calls as the baseline setup
|
expect(reuseStrategy.retrieve).not.toHaveBeenCalled();
|
||||||
expect(reuseStrategy.retrieve).toHaveBeenCalledTimes(2);
|
|
||||||
|
|
||||||
// This call should produce a reused activated route
|
|
||||||
const state = createRouterState(reuseStrategy, createState(config, 'a(left:c)'), prevState);
|
|
||||||
|
|
||||||
// Verify the retrieve method has been called one more time
|
|
||||||
expect(reuseStrategy.retrieve).toHaveBeenCalledTimes(3);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should consistently represent future and current state', () => {
|
it('should consistently represent future and current state', () => {
|
||||||
|
|
|
@ -5807,6 +5807,7 @@ describe('Integration', () => {
|
||||||
const fixture = createRoot(router, RootCmp);
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
router.routeReuseStrategy = new AttachDetachReuseStrategy();
|
router.routeReuseStrategy = new AttachDetachReuseStrategy();
|
||||||
|
spyOn(router.routeReuseStrategy, 'retrieve').and.callThrough();
|
||||||
|
|
||||||
router.resetConfig([
|
router.resetConfig([
|
||||||
{
|
{
|
||||||
|
@ -5824,14 +5825,19 @@ describe('Integration', () => {
|
||||||
expect(location.path()).toEqual('/a/b');
|
expect(location.path()).toEqual('/a/b');
|
||||||
expect(teamCmp).toBeDefined();
|
expect(teamCmp).toBeDefined();
|
||||||
expect(simpleCmp).toBeDefined();
|
expect(simpleCmp).toBeDefined();
|
||||||
|
expect(router.routeReuseStrategy.retrieve).not.toHaveBeenCalled();
|
||||||
|
|
||||||
router.navigateByUrl('/c');
|
router.navigateByUrl('/c');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/c');
|
expect(location.path()).toEqual('/c');
|
||||||
expect(fixture.debugElement.children[1].componentInstance).toBeAnInstanceOf(UserCmp);
|
expect(fixture.debugElement.children[1].componentInstance).toBeAnInstanceOf(UserCmp);
|
||||||
|
// We have still not encountered a route that should be reattached
|
||||||
|
expect(router.routeReuseStrategy.retrieve).not.toHaveBeenCalled();
|
||||||
|
|
||||||
router.navigateByUrl('/a;p=1/b;p=2');
|
router.navigateByUrl('/a;p=1/b;p=2');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
// We retrieve both the stored route snapshots
|
||||||
|
expect(router.routeReuseStrategy.retrieve).toHaveBeenCalledTimes(2);
|
||||||
const teamCmp2 = fixture.debugElement.children[1].componentInstance;
|
const teamCmp2 = fixture.debugElement.children[1].componentInstance;
|
||||||
const simpleCmp2 = fixture.debugElement.children[1].children[1].componentInstance;
|
const simpleCmp2 = fixture.debugElement.children[1].children[1].componentInstance;
|
||||||
expect(location.path()).toEqual('/a;p=1/b;p=2');
|
expect(location.path()).toEqual('/a;p=1/b;p=2');
|
||||||
|
|
Loading…
Reference in New Issue