Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hide a routerLink if its associated route cannot be activated?

Tags:

angular

I have:

  • Links with routerLinks
  • That point to Routes with canActivate logic
  • That canActivateLogic authorises or not the road based on the targetted component and user right

I would like to hide links with routerLinks that can not be activated.

Is there some nice way to to hide routerLink that cannot be activated ?

Now you would say do something like 'share the canActivate logic' like:

<a [hidden]="callThatCanActivateLogic()" [routerLink]="['/brand']"

The thing is that this canActivateLogic depends on components and rights, that would be wrongly designed to the following as component resolution is supposed to be delegated to routes, not embed into links:

<a [hidden]="callThatCanActivateLogic(TheComponentAtTheEndTargettedByTheRoute, IAmInThatContext)" [routerLink]="['/brand']"

That would be fortunate to have a solution that provides a link between a routerLink and it's route canActivate :)

like image 638
ATX Avatar asked Aug 16 '16 13:08

ATX


People also ask

How do I disable my routerLink?

When linkEnabled returns false , null will make routerLink to link to the current/active route. If routerLink links to the active route, the class which is specified in routerLinkActive will be applied. That will be is_disabled in this case. There we can specify, how the disabled routerLink should appear.

Can we use href instead of routerLink?

Always avoid href when using Angular or any other SPA. routerLink loads resources internal to Angular, and the page that loaded in the browser is never completely reloaded (and so app state is maintained.)

How do I know if my routerLink is active?

routerLinkActive is simply an indicator of whether this link represents the active route. @jessepinho - Tried it - [routerLinkActive]="'active'" will only work if you also have [routerLink] in the a-tag. If you had (click)="navitageTo('/route')" instead of [routerLink], and in that function, you called this.

What is difference between routerLink and routerLink?

What is the difference between [routerLink] and routerLink ? How should you use each one? They're the same directive. You use the first one to pass a dynamic value, and the second one to pass a static path as a string.


1 Answers

Today I faced with the same problem. Unfortunately, I hadn't found any nice solution, so I had to create own one.

The idea is to create a directive, which takes a router path as a parameter and hides link if transition is not allowed. For example:

<li [allowTransition]="'list'">
      <a routerLink="list">List</a>
</li>

The directive's logic is following:

1)Import all router's config and find the configuration with input path from attribute. My configuration is below:

  { path: 'login', component: LoginComponent },
  { path: 'list', component: ListComponent, canActivate: [UserGuardService]},

2)Then the directive reads canActivate property of configuration and gets UserGuardService by injector. In this way the directive takes an instance of UserGuardService. UserGuardService is class which implements CanActivate interface.

3)UserGuardService knows is the transition is allowed or not. So we should just ask it about and handle answer by changing css display property.

But there are two features which have a big influence to the implementation:

A) CanActivate interface is not comfortable because to call it we have to create ActivatedRouteSnapshot and RouterStateSnapshot objects. It can be necessary for real transition but in out case we want just know if transition is possible or not.

B) We should provide links visibility refreshing after some events (login event in my case)

To workaround problem A I've created the interface Guard.

export interface Guard {
    allowTransition():boolean;
}

So, I expect all my CanActivate implementations also implement Guard interface. Now my typical CanActivate implementation looks like this:

canActivate(route:ActivatedRouteSnapshot,
          state:RouterStateSnapshot):Observable<boolean>|boolean {
  //this method performs only "naigation" logic. It calls another method for business logic.
  var allow = this.allowTransition();
  if (!allow) {
      this.router.navigate(['login']);
  }
  return allow;
}

allowTransition():boolean {
  // this method contains business logic of transition possibility 
  return this.userService.hasCurrentUserRole("USER");
}

After such "interface dividing" I'm able to call simple method allowTransition() from my directive. The code is below.

import {routes} from "../../../app.routes";
... other imports
@Directive({selector: '[allowTransition]'})
  export class AllowTransition implements OnDestroy, LoginListener, OnInit {

  private el: HTMLElement;

  private visibleDisplay:string;

  @Input('allowTransition') destUrl: string;

  constructor(el: ElementRef, private injector: Injector,
          @Inject('appUserService') private userService : UserService ) {
    this.el = el.nativeElement;
    this.visibleDisplay = this.el.style.display;
  }

  ngOnDestroy() {
    this.userService.removeLoginListener(this)
  }


  ngOnInit() {
    this.userService.addLoginListener(this);
    this.onLogin();
  }

  onLogin() {
    let allow = true;

    for (let i = 0; i < routes.length; i++) {
      let path = routes[i].path;
      if (path == this.destUrl) {
        let canActivate = routes[i].canActivate;
        if (canActivate != null) {
          //todo do the same for canActivate[i]
          try {
            let canActivateInstance = this.injector.get(canActivate[0]) as Guard;
            allow = canActivateInstance.allowTransition();
          } catch (error) {
            //did you forget to implement Guard?
            console.error('Error');
          }
        }
      }
    }
    this.el.style.display = allow ? this.visibleDisplay : 'none';
  }
}

I've mentioned about problem B above, that's why my directive implements LoginListener interface with one method onLogin(). While the directive exists, it should be registered at event producer and handle if something changes. In my case after login or logout UserService calls method onLogin() of all directives and each link refreshes.

Also initial display property can have several values, that's why the directive have to keep it as 'visibleDisplay' property.

As a conclusion, now it is possible to use this directive without any information about components corresponding with the route. The main disadvantage of this method is that it doesn't work with dynamic routes like 'view/:id'. Also it is not allowed to use Observale instead of boolean in CanActivate method. Of course, it is possible to fix it, but in my point of view the way I do it is already too complicated for such simple thing. I just have done all I need and waiting for angular2 updates with similar feature.

More info: https://kosbr.github.io/2016/10/18/angular2-guard-router.html

like image 170
kosbr Avatar answered Oct 02 '22 14:10

kosbr