0
votes

I am using a structural directive in angular 7 to 7 to hide parts of the application based on the user role. the user role is decoded from the jwt token. however i am running into issues. I have use the same implementation with Angular 6 and had no problems, however nothing i try seems to work. if i login as an admin i need to refresh the browser for the admin link to show or be hidden if i login a a regular user. the error message has been posted below and i have added the hasRole.directive and the html link

NavComponent.html:2 ERROR TypeError: Cannot read property 'role' of undefined at HasRoleDirective.push../src/app/_directives/hasRole.directive.ts.HasRoleDirective.ngOnInit (hasRole.directive.ts:17) at checkAndUpdateDirectiveInline (core.js:20665) at checkAndUpdateNodeInline (core.js:21929) at checkAndUpdateNode (core.js:21891) at debugCheckAndUpdateNode (core.js:22525) at debugCheckDirectivesFn (core.js:22485) at Object.eval [as updateDirectives] (NavComponent.html:5) at Object.debugUpdateDirectives [as updateDirectives] (core.js:22477) at checkAndUpdateView (core.js:21873) at callViewAction (core.js:22114)

import { Directive, Input, ViewContainerRef, TemplateRef, OnInit } from '@angular/core';
import { AuthService } from '../_services/auth.service';

@Directive({
  selector: '[appHasRole]'
})
export class HasRoleDirective implements OnInit {
  @Input() appHasRole: string[];
  isVisible = false;

  constructor(
    private viewContainerRef: ViewContainerRef,
    private templateRef: TemplateRef<any>,
    private authService: AuthService) { }

  ngOnInit() {
    const userRoles = this.authService.decodedToken.role as Array<string>;
    // if no roles clear the view container ref
    if (!userRoles) {
      this.viewContainerRef.clear();
    }

    // if user has role needed then render the element
    if (this.authService.roleMatch(this.appHasRole)) {
      if (!this.isVisible) {
        this.isVisible = true;
        this.viewContainerRef.createEmbeddedView(this.templateRef);
      } else {
        this.isVisible = false;
        this.viewContainerRef.clear();
      }
    }
  }

}
<ul class="navbar-nav">
        <li *appHasRole="['Admin', 'Moderator']"  class="nav-item" routerLinkActive="active" >
            <a class="nav-link" [routerLink]="['/admin']"  id="side-menu">Admin</a>
          </li>
3

3 Answers

0
votes

I have used a similar concept in one of my projects, in order to show or hide DOM elements based on the current user's permissions (=~role).

Suggestion: It seems your authService.decodedToken property is undefined at some point - you could use an RxJS subject / observable combo in your authService to propagate current users's roles to subscribers. Then subscribe to that property in your directive, to show or hide the element. My implementation looks somewhat like this (the actual show/hide might be different, but you should still get my point):

  ngOnInit() {
    this.shouldDisplay().subscribe((shouldDisplay) => {
      if (shouldDisplay && !this.hasView) {
        this.show();
      } else if (!shouldDisplay && this.hasView) {
        this.hide();
      }
    });
  }

  [...]

  shouldDisplay(): Observable<boolean> {
    return this.permissionService.isPermitted(this.permission);
  }

The isPermitted method returns an observable of the result, as the permissions might still have to be fetched in my case when the directive requests the result (that would be your auth token in that case, that might not be available at SPA birth).

0
votes

This directive is fine. I have used the same directive in Angular 6, 7, & 8 with no problems.

  1. make sure your regular user role is being passed into the directive
  2. Have you tried moving the directive to the anchor tag within the viewContainerRef?
    <a *appHasRole="['Admin', 'Moderator']" class="nav-link" [routerLink]="['/admin']"  id="side-menu">Admin</a>
0
votes

I don't know if you ever solved this, but I think you're trying to show the admin link before being logged in. If you're not logged in, you won't have a token to decode and you'll get the undefined error.

Try putting a *ngIf=(loggedIn) in your <ul> tag.