I had tried to believe what have written in the docs site. However, it appears it's not totally right, or the implementation has been updated but docs doesn't update.
To be Brief:
First, CanDeactivate
guards are checked from deepest to top and CanActivate
guards are checked from top to deepest(it will quit with falsy check in the traversal).
Second, CanActivateChild
guards are not checked from deepest to top.
TL;DR
Detail Explanation
we should check the source to see how it work.
Note: the commit checked is: https://github.com/angular/angular/tree/edb8375a5ff15d77709ccf1759efb14091fa86a4
step 1 - see when CanActivateChild
got called
source here L929.
This is only place its superior caller runCanActivateChild
got called.
At that line, we can get some hint that it does the same trick as CanActivate
, because CanActivate
's superior caller runCanActivate
is called after.
step 2 - see how does runCanActivateChild
work
L926 and L950.
runCanActivateChild
got called within the iteration of canActivateChecks
, same as how runCanActivate
got called. Here we know CanActivate
(i mean the feature) and CanActivateChild
share the same data source -- canActivateChecks
.
step 3 - what is canActivateChecks
and how does it get processed
So, what is canActivateChecks
? Obviously, We can find out it's an array of CanActivate
class instances. But how is canActivateChecks
got assigned? Go to here L865. This is the important part, so i am going to paste them here.
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);
}
}
}
It's a little long. But If you go through it, you would figure out it plays a depth-first-traversal. Let's ignore the same route switching. Find ##### comment by e-cloud #####
and see the main procedure. It shows that it updates the canActivateChecks
first then performs next level travesal(Pre-order traversal at whole).
You must know the router treats all the routes of the app as a url tree. Each PreActivation
split its future
(as a tree path) into path segments by the traversal.
Take a simplified example:
we have the future route as /a/b/c
.
Then we will get [ '/a', '/a/b', '/a/b/c' ] as canActivateChecks
Apparently, canActivateChecks
represents the routes from top to deepest of the future
The source shows canActivateChecks
is iterated from left to right.
step 4 - conclusion
we can conclude that CanActivateChild
is run from top to deepest child.
Hope i explain it clearly.