Angular 守卫,文档中的表述不清楚
Angular guards, unclear statement in the docs
我想深入了解 angular,所以我阅读了 the docs,这对我很有帮助。
现在我正在研究守卫。我在文档中阅读了此声明。
The router checks the CanDeactivate and CanActivateChild guards first, from the deepest child route to the top. Then it checks the CanActivate guards from the top down to the deepest child route.
现在我很困惑,为什么angular会这样执行?
对于 CanDeactivate 和 CanActivateChild,从最深的 child 到顶部进行检查有什么好处吗? CanActivate?
从顶部到最深的 child 路线
当您考虑路由时,您进入树越深,您得到的就越具体。
例如:
/food-types/sweets/pies/blueberry
因此,当您告诉 Angular 您想要离开 blueberry
饼图时,它会首先检查 CanDeactivate
蓝莓,因为您正沿着导航树往回走,以一个不同的位置。 CanActivateChild
也会沿着树往上走,到子路径,据我理解,原因是一样的:先检查最深的层次,验证他们的子路径是否可以被激活。
CanActivate
则相反。当你告诉 Angular 你想看 blueberry
馅饼时,你正在沿着树走,因此,它会在沿着树走的时候按顺序检查守卫。
我曾试图相信文档站点上写的内容。但是,它似乎并不完全正确,或者实现已更新但文档没有更新。
简而言之:
首先,CanDeactivate
守卫从最深到顶部 检查,CanActivate
守卫从顶部到最深[=102] 检查=](它将退出并在遍历中进行虚假检查)。
其次,CanActivateChild
守卫没有从最深处到顶部进行检查。
TL;DR
详细说明
我们应该检查源代码以了解它是如何工作的。
Note: the commit checked is: https://github.com/angular/angular/tree/edb8375a5ff15d77709ccf1759efb14091fa86a4
第 1 步 - 查看 CanActivateChild
何时被呼叫
这只是其上级呼叫者 runCanActivateChild
被呼叫的地方。
在那一行,我们可以得到一些提示,它与 CanActivate
有相同的技巧,因为 CanActivate
的上级调用者 runCanActivate
在之后被调用。
第 2 步 - 查看 runCanActivateChild
是如何工作的
runCanActivateChild
在 canActivateChecks
的迭代中被调用,与 runCanActivate
的调用方式相同。这里我们知道 CanActivate
(我指的是特征)和 CanActivateChild
共享相同的数据源 -- canActivateChecks
.
第 3 步 - 什么是 canActivateChecks
及其处理方式
那么,什么是canActivateChecks
?显然,我们可以发现它是一个 CanActivate
class 个实例的数组。但是 canActivateChecks
是如何分配的呢? Go to here L865。这是重要的部分,所以我将它们粘贴在这里。
private traverseChildRoutes(
futureNode: TreeNode<ActivatedRouteSnapshot>, currNode: TreeNode<ActivatedRouteSnapshot>|null,
contexts: ChildrenOutletContexts|null, futurePath: ActivatedRouteSnapshot[]): void {
const prevChildren = nodeChildrenAsMap(currNode);
// Process the children of the future route
futureNode.children.forEach(c => {
this.traverseRoutes(c, prevChildren[c.value.outlet], contexts, futurePath.concat([c.value]));
delete prevChildren[c.value.outlet];
});
// Process any children left from the current route (not active for the future route)
forEach(
prevChildren, (v: TreeNode<ActivatedRouteSnapshot>, k: string) =>
this.deactivateRouteAndItsChildren(v, contexts !.getContext(k)));
}
private traverseRoutes(
futureNode: TreeNode<ActivatedRouteSnapshot>, currNode: TreeNode<ActivatedRouteSnapshot>,
parentContexts: ChildrenOutletContexts|null, futurePath: ActivatedRouteSnapshot[]): void {
const future = futureNode.value;
const curr = currNode ? currNode.value : null;
const context = parentContexts ? parentContexts.getContext(futureNode.value.outlet) : null;
// reusing the node
if (curr && future._routeConfig === curr._routeConfig) {
if (this.shouldRunGuardsAndResolvers(
curr, future, future._routeConfig !.runGuardsAndResolvers)) {
this.canActivateChecks.push(new CanActivate(futurePath));
const outlet = context !.outlet !;
this.canDeactivateChecks.push(new CanDeactivate(outlet.component, curr));
} else {
// we need to set the data
future.data = curr.data;
future._resolvedData = curr._resolvedData;
}
// If we have a component, we need to go through an outlet.
if (future.component) {
this.traverseChildRoutes(
futureNode, currNode, context ? context.children : null, futurePath);
// if we have a componentless route, we recurse but keep the same outlet map.
} else {
this.traverseChildRoutes(futureNode, currNode, parentContexts, futurePath);
}
} else {
// ##### comment by e-cloud #####
if (curr) {
this.deactivateRouteAndItsChildren(currNode, context);
}
this.canActivateChecks.push(new CanActivate(futurePath));
// If we have a component, we need to go through an outlet.
if (future.component) {
this.traverseChildRoutes(futureNode, null, context ? context.children : null, futurePath);
// if we have a componentless route, we recurse but keep the same outlet map.
} else {
this.traverseChildRoutes(futureNode, null, parentContexts, futurePath);
}
}
}
有点长。但是如果你仔细研究它,你会发现它播放 depth-first-traversal。让我们忽略相同的路由切换。找到 ##### comment by e-cloud #####
并查看主要过程。它表明它先更新canActivateChecks
然后执行下一级遍历(整个Pre-order遍历)。
你一定知道路由器把app的所有路由都当做一棵url树。每个PreActivation
通过遍历将其future
(作为树路径)分成路径段。
举一个简化的例子:
we have the future route as /a/b/c
.
Then we will get [ '/a', '/a/b', '/a/b/c' ] as canActivateChecks
显然,canActivateChecks
代表了future
中从顶部到最深处的路线
来源显示 canActivateChecks
从左到右迭代。
第 4 步 - 结论
我们可以得出结论,CanActivateChild
从上到下是运行child。
希望我解释清楚。
我想深入了解 angular,所以我阅读了 the docs,这对我很有帮助。
现在我正在研究守卫。我在文档中阅读了此声明。
The router checks the CanDeactivate and CanActivateChild guards first, from the deepest child route to the top. Then it checks the CanActivate guards from the top down to the deepest child route.
现在我很困惑,为什么angular会这样执行?
对于 CanDeactivate 和 CanActivateChild,从最深的 child 到顶部进行检查有什么好处吗? CanActivate?
当您考虑路由时,您进入树越深,您得到的就越具体。
例如:
/food-types/sweets/pies/blueberry
因此,当您告诉 Angular 您想要离开 blueberry
饼图时,它会首先检查 CanDeactivate
蓝莓,因为您正沿着导航树往回走,以一个不同的位置。 CanActivateChild
也会沿着树往上走,到子路径,据我理解,原因是一样的:先检查最深的层次,验证他们的子路径是否可以被激活。
CanActivate
则相反。当你告诉 Angular 你想看 blueberry
馅饼时,你正在沿着树走,因此,它会在沿着树走的时候按顺序检查守卫。
我曾试图相信文档站点上写的内容。但是,它似乎并不完全正确,或者实现已更新但文档没有更新。
简而言之:
首先,CanDeactivate
守卫从最深到顶部 检查,CanActivate
守卫从顶部到最深[=102] 检查=](它将退出并在遍历中进行虚假检查)。
其次,CanActivateChild
守卫没有从最深处到顶部进行检查。
TL;DR
详细说明
我们应该检查源代码以了解它是如何工作的。
Note: the commit checked is: https://github.com/angular/angular/tree/edb8375a5ff15d77709ccf1759efb14091fa86a4
第 1 步 - 查看 CanActivateChild
何时被呼叫
这只是其上级呼叫者 runCanActivateChild
被呼叫的地方。
在那一行,我们可以得到一些提示,它与 CanActivate
有相同的技巧,因为 CanActivate
的上级调用者 runCanActivate
在之后被调用。
第 2 步 - 查看 runCanActivateChild
是如何工作的
runCanActivateChild
在 canActivateChecks
的迭代中被调用,与 runCanActivate
的调用方式相同。这里我们知道 CanActivate
(我指的是特征)和 CanActivateChild
共享相同的数据源 -- canActivateChecks
.
第 3 步 - 什么是 canActivateChecks
及其处理方式
那么,什么是canActivateChecks
?显然,我们可以发现它是一个 CanActivate
class 个实例的数组。但是 canActivateChecks
是如何分配的呢? Go to here L865。这是重要的部分,所以我将它们粘贴在这里。
private traverseChildRoutes(
futureNode: TreeNode<ActivatedRouteSnapshot>, currNode: TreeNode<ActivatedRouteSnapshot>|null,
contexts: ChildrenOutletContexts|null, futurePath: ActivatedRouteSnapshot[]): void {
const prevChildren = nodeChildrenAsMap(currNode);
// Process the children of the future route
futureNode.children.forEach(c => {
this.traverseRoutes(c, prevChildren[c.value.outlet], contexts, futurePath.concat([c.value]));
delete prevChildren[c.value.outlet];
});
// Process any children left from the current route (not active for the future route)
forEach(
prevChildren, (v: TreeNode<ActivatedRouteSnapshot>, k: string) =>
this.deactivateRouteAndItsChildren(v, contexts !.getContext(k)));
}
private traverseRoutes(
futureNode: TreeNode<ActivatedRouteSnapshot>, currNode: TreeNode<ActivatedRouteSnapshot>,
parentContexts: ChildrenOutletContexts|null, futurePath: ActivatedRouteSnapshot[]): void {
const future = futureNode.value;
const curr = currNode ? currNode.value : null;
const context = parentContexts ? parentContexts.getContext(futureNode.value.outlet) : null;
// reusing the node
if (curr && future._routeConfig === curr._routeConfig) {
if (this.shouldRunGuardsAndResolvers(
curr, future, future._routeConfig !.runGuardsAndResolvers)) {
this.canActivateChecks.push(new CanActivate(futurePath));
const outlet = context !.outlet !;
this.canDeactivateChecks.push(new CanDeactivate(outlet.component, curr));
} else {
// we need to set the data
future.data = curr.data;
future._resolvedData = curr._resolvedData;
}
// If we have a component, we need to go through an outlet.
if (future.component) {
this.traverseChildRoutes(
futureNode, currNode, context ? context.children : null, futurePath);
// if we have a componentless route, we recurse but keep the same outlet map.
} else {
this.traverseChildRoutes(futureNode, currNode, parentContexts, futurePath);
}
} else {
// ##### comment by e-cloud #####
if (curr) {
this.deactivateRouteAndItsChildren(currNode, context);
}
this.canActivateChecks.push(new CanActivate(futurePath));
// If we have a component, we need to go through an outlet.
if (future.component) {
this.traverseChildRoutes(futureNode, null, context ? context.children : null, futurePath);
// if we have a componentless route, we recurse but keep the same outlet map.
} else {
this.traverseChildRoutes(futureNode, null, parentContexts, futurePath);
}
}
}
有点长。但是如果你仔细研究它,你会发现它播放 depth-first-traversal。让我们忽略相同的路由切换。找到 ##### comment by e-cloud #####
并查看主要过程。它表明它先更新canActivateChecks
然后执行下一级遍历(整个Pre-order遍历)。
你一定知道路由器把app的所有路由都当做一棵url树。每个PreActivation
通过遍历将其future
(作为树路径)分成路径段。
举一个简化的例子:
we have the future route as
/a/b/c
.
Then we will get [ '/a', '/a/b', '/a/b/c' ] ascanActivateChecks
显然,canActivateChecks
代表了future
中从顶部到最深处的路线
来源显示 canActivateChecks
从左到右迭代。
第 4 步 - 结论
我们可以得出结论,CanActivateChild
从上到下是运行child。
希望我解释清楚。