本文除了介绍路由复用策略外,还实现了父子路由如何复用的问题,项目见末尾;
1、父路由和子路由页面的tab不应该同时存在,否则,切换时容易报以下错误:
Cannot reattach ActivatedRouteSnapshot with a different number of children
2、子路由实例化时可以根据路由缓存同时恢复其对应的父路由;具体详见项目中的路由复用策略;
路由导航过程:创建新的路由节点;激活路由节点(停用旧路由、激活新路由);
其中,可以看到一个路由复用过程中,可能会出现俩次retrieve操作。这是因为第一次调用是为了获取缓存路由对应的快照对象TreeNode,第二次调用是为了获取缓存对应的组件实例化对象ComponentRef;
创建新的路由节点:
初始化节点时,通过shouldReuseRoute判断未来路由是否与当前路由相同,若相同,则赋当前路由页面快照到新创建节点的初始值;否则,判断是否存在已有缓存,若有,则通过retrieve同样赋快照初始值,否则,则默认初始化节点;
function createNode(
routeReuseStrategy: RouteReuseStrategy, curr: TreeNode,
prevState?: TreeNode): TreeNode {
// reuse an activated route that is currently displayed on the screen
if (prevState && routeReuseStrategy.shouldReuseRoute(curr.value, prevState.value.snapshot)) {
const value = prevState.value;
value._futureSnapshot = curr.value;
const children = createOrReuseChildren(routeReuseStrategy, curr, prevState);
return new TreeNode(value, children);
// retrieve an activated route that is used to be displayed, but is not currently displayed
} else {
const detachedRouteHandle =
routeReuseStrategy.retrieve(curr.value);
if (detachedRouteHandle) {
const tree: TreeNode = detachedRouteHandle.route;
setFutureSnapshotsOfActivatedRoutes(curr, tree);
return tree;
} else {
const value = createActivatedRoute(curr.value);
const children = curr.children.map(c => createNode(routeReuseStrategy, c));
return new TreeNode(value, children);
}
}
}
停用旧路由:
停用路由及其子路由:判断该路由是否要保存shouldDetach,若要,则保存store该路由;
private deactivateRouteAndItsChildren(
route: TreeNode, parentContexts: ChildrenOutletContexts): void {
if (this.routeReuseStrategy.shouldDetach(route.value.snapshot)) {
this.detachAndStoreRouteSubtree(route, parentContexts);
} else {
this.deactivateRouteAndOutlet(route, parentContexts);
}
}
private detachAndStoreRouteSubtree(
route: TreeNode, parentContexts: ChildrenOutletContexts): void {
const context = parentContexts.getContext(route.value.outlet);
if (context && context.outlet) {
const componentRef = context.outlet.detach();
const contexts = context.children.onOutletDeactivated();
this.routeReuseStrategy.store(route.value.snapshot, {componentRef, route, contexts});
}
}
激活新路由:
激活未来路由时,判断是否可以取缓存路由(shouldAttach),若可以,则retrieve获取缓存路由,且通过store置空缓存对象;
if (this.routeReuseStrategy.shouldAttach(future.snapshot)) {
const stored =
(this.routeReuseStrategy.retrieve(future.snapshot));
this.routeReuseStrategy.store(future.snapshot, null);
context.children.onOutletReAttached(stored.contexts);
context.attachRef = stored.componentRef;
context.route = stored.route.value;
if (context.outlet) {
// Attach right away when the outlet has already been instantiated
// Otherwise attach from `RouterOutlet.ngOnInit` when it is instantiated
context.outlet.attach(stored.componentRef, stored.route.value);
}
advanceActivatedRouteNodeAndItsChildren(stored.route);
}
具体路由复用策略的实现,可参考本人git上发布的实例项目:
https://github.com/007katoo/Ng6-Manage
参考文章:
1、兜兜转转的小菊——angular 路由复用策略实现懒路由下多tab页切换;
2、NG-ZORRO
3、及文章1里的所有引用文章;