A few weeks ago while I was developing my Angular 2 application I faced with problem: How to hide routerLinks if the
transition is not allowed? I couldn’t find any simple solution so I had to create my own one. Also I’ve found the similar
question in stackOverFlow and shared my idea. This post describes this problem and
the solution in details.
Problem description
We configure routes in Angular2 by creating array of Route objects. The example is below.
CanActivate property contains an array of objects, which implement CanActivate interface. They are responsible
for availability of the state. Data property contains an additional information about the route. It can be extracted
in CanActivate method. I use it for keeping the list of allowed roles.
After the configuration is done it is possible to use such routerLinks to change state:
There are two links. When a state is active, corresponding link is highlighted. The problem is
how to hide it (li tag) if a target state is forbidden? Unfortunately, I don’t know simple way to do it now. Of course,
I’m not going to check manually access for every link.
How it should be done?
I’d like to have a directive, that hides whole element if the transition is not allowed. So it could be like this:
If I wanted to hide only link without container, I would may not use doubled parameter with destUrl. However, I’d like to have a
possibility to hide blocks even without links. That’s why I have two parameters with the same value.
Implementation
Creating additional interface
Interface CanActivate contains only one method. It is called before transition and if it returns false, the transition will
not happen. So the goal is call this method from the directive and hide element if it returns false. However, it is not easy because
to call it the directive must have ActivatedRouteSnapshot and RouterStateSnapshot objects, but it doesn’t have it.
A possible solution is create additional interface:
It must have one method which accepts an object from data property of Route in routers configuration. In my case it is the list of roles which
are allowed to visit the target state. The implementation of this interface must know about roles that current user has and should return a
boolean result.
Then lets create a contract inside the application: all CanActivate implementations must implement Guard interface. So, my implementation will be
like this:
By this step I’ve done nothing except extracting some logic of standard canActivate method to another method.
Next task is creating a directive. The directive will allow us to call allowTransition directly, not throw
canActivate method.
Creating a directive
The idea of directive is following:
import all necessary routes configuration
Parse all Routes from configuration and find appropriate one (which path matches with parameter)
Get the instances of all necessary CanActivates(which are Guards) and call allowTransition method.
Handle returned value.
The first step is very easy. You can use simple import for example.
The most difficult part is step 2 and 3. This is the piece of code which processes it:
I hope my comments will make the code clear for you. The internal angular2 injector is used for getting necessary instances.
I also used pathMatch method for check path matching. This methods contains a couple
of lines due to the Route Matcher library.
The whole code of the directive is published below:
If the allowTransition method returns false, it hides the element. I use css display property for it. Obviously, I need
to remember the initial property value for getting back.
Of course, I had to create some mechanism for updating. If user logs in, this event possible will change current roles and the allowTrainsition method must be called one more time.
That’s why I’ve created an interface LoginListener with one method onLogin. After every login event the onLogin method
is called and my element visibility is always actual.
Disadvantages
This solution has two points to improve:
CanActivate method can return a promise of boolean. However in the previous description I considered it returns only
boolean. It’s a good idea to allow allowTransition method return promise too.
A lazy loaded module has own independent routes. It means all paths are written regarding module’s root, not
application’s root like we do with usual module. This fact breaks down logic with iterating over routes. Something more
complex should be used in such case.