Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular guards, unclear statement in the docs

Tags:

I'm trying to understand angular deeply, so i read the docs and it was very helpful.
now i'm studying the guards. and i read this statement in 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.

now i'm confused, why does angular perform it in this way?
is there any benefits of doing the checking from the deepest child to the top for CanDeactivate & CanActivateChild. and from top to the deepest child route for CanActivate?

like image 212
Raed Khalaf Avatar asked Jul 10 '17 07:07

Raed Khalaf


People also ask

What is canActivate and CanActivateChild?

canActivate is checked. You navigate between the children of /admin route, but canActivate isn't called because it protects /admin. canActivateChild is called whenever changing between children of the route its defined on.

What are different types of guards in Angular?

There are 5 types of guards in Angular namely CanActivate, CanActivateChild, CanDeactivate, Resolve and CanLoad.

What is the difference between the canActivate and the CanLoad route guards?

Well, there is a difference, the canActivate exists to prevent unauthorized users from accessing a route, while canLoad is used to prevent the application from loading an entire module or component in a lazy way (lazy loading) if the user is not authorized.

What is CanActivateChild in Angular?

CanActivateChildlinkInterface that a class can implement to be a guard deciding if a child route can be activated. If all guards return true , navigation continues. If any guard returns false , navigation is cancelled.


1 Answers

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.

like image 54
e-cloud Avatar answered Oct 07 '22 05:10

e-cloud