fix(router): advance a route only after its children have been deactivated (#12676)

Closes #11715
This commit is contained in:
Victor Savkin 2016-11-03 16:26:10 -07:00 committed by vikerman
parent 69f006cd89
commit 9ddf9b3d3d
2 changed files with 95 additions and 9 deletions

View File

@ -984,19 +984,53 @@ class ActivateRoutes {
activate(parentOutletMap: RouterOutletMap): void { activate(parentOutletMap: RouterOutletMap): void {
const futureRoot = this.futureState._root; const futureRoot = this.futureState._root;
const currRoot = this.currState ? this.currState._root : null; const currRoot = this.currState ? this.currState._root : null;
this.deactivateChildRoutes(futureRoot, currRoot, parentOutletMap);
advanceActivatedRoute(this.futureState.root); advanceActivatedRoute(this.futureState.root);
this.activateChildRoutes(futureRoot, currRoot, parentOutletMap); this.activateChildRoutes(futureRoot, currRoot, parentOutletMap);
} }
private deactivateChildRoutes(
futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>,
outletMap: RouterOutletMap): void {
const prevChildren: {[key: string]: any} = nodeChildrenAsMap(currNode);
futureNode.children.forEach(c => {
this.deactivateRoutes(c, prevChildren[c.value.outlet], outletMap);
delete prevChildren[c.value.outlet];
});
forEach(prevChildren, (v: any, k: string) => this.deactiveRouteAndItsChildren(v, outletMap));
}
private activateChildRoutes( private activateChildRoutes(
futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>, futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>,
outletMap: RouterOutletMap): void { outletMap: RouterOutletMap): void {
const prevChildren: {[key: string]: any} = nodeChildrenAsMap(currNode); const prevChildren: {[key: string]: any} = nodeChildrenAsMap(currNode);
futureNode.children.forEach(c => { futureNode.children.forEach(
this.activateRoutes(c, prevChildren[c.value.outlet], outletMap); c => { this.activateRoutes(c, prevChildren[c.value.outlet], outletMap); });
delete prevChildren[c.value.outlet]; }
});
forEach(prevChildren, (v: any, k: string) => this.deactiveRouteAndItsChildren(v, outletMap)); deactivateRoutes(
futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>,
parentOutletMap: RouterOutletMap): void {
const future = futureNode.value;
const curr = currNode ? currNode.value : null;
// reusing the node
if (future === curr) {
// If we have a normal route, we need to go through an outlet.
if (future.component) {
const outlet = getOutlet(parentOutletMap, future);
this.deactivateChildRoutes(futureNode, currNode, outlet.outletMap);
// if we have a componentless route, we recurse but keep the same outlet map.
} else {
this.deactivateChildRoutes(futureNode, currNode, parentOutletMap);
}
} else {
if (curr) {
this.deactiveRouteAndItsChildren(currNode, parentOutletMap);
}
}
} }
activateRoutes( activateRoutes(
@ -1020,10 +1054,6 @@ class ActivateRoutes {
this.activateChildRoutes(futureNode, currNode, parentOutletMap); this.activateChildRoutes(futureNode, currNode, parentOutletMap);
} }
} else { } else {
if (curr) {
this.deactiveRouteAndItsChildren(currNode, parentOutletMap);
}
// if we have a normal route, we need to advance the route // if we have a normal route, we need to advance the route
// and place the component into the outlet. After that recurse. // and place the component into the outlet. After that recurse.
if (future.component) { if (future.component) {

View File

@ -72,6 +72,62 @@ describe('Integration', () => {
}); });
}); });
describe('should advance the parent route after deactivating its children', () => {
let log: string[] = [];
@Component({template: '<router-outlet></router-outlet>'})
class Parent {
constructor(route: ActivatedRoute) {
route.params.subscribe((s: any) => { log.push(s); });
}
}
@Component({template: 'child1'})
class Child1 {
ngOnDestroy() { log.push('child1 destroy'); }
}
@Component({template: 'child2'})
class Child2 {
constructor() { log.push('child2 constructor'); }
}
@NgModule({
declarations: [Parent, Child1, Child2],
entryComponents: [Parent, Child1, Child2],
imports: [RouterModule]
})
class TestModule {
}
beforeEach(() => {
log = [];
TestBed.configureTestingModule({imports: [TestModule]});
});
it('should work',
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
const fixture = createRoot(router, RootCmp);
router.resetConfig([{
path: 'parent/:id',
component: Parent,
children:
[{path: 'child1', component: Child1}, {path: 'child2', component: Child2}]
}]);
router.navigateByUrl('/parent/1/child1');
advance(fixture);
router.navigateByUrl('/parent/2/child2');
advance(fixture);
expect(location.path()).toEqual('/parent/2/child2');
expect(log).toEqual([{id: '1'}, 'child1 destroy', {id: '2'}, 'child2 constructor']);
})));
});
it('should execute navigations serialy', it('should execute navigations serialy',
fakeAsync(inject([Router, Location], (router: Router, location: Location) => { fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
const fixture = createRoot(router, RootCmp); const fixture = createRoot(router, RootCmp);