DEV Community

Raúl Julián López Caña
Raúl Julián López Caña

Posted on • Originally published at Medium on

Angular permissions based on roles | Part 1. Roles, permissions and Permission Manager

One of the biggest challenges we face when building Angular applications is the control and management of user permissions.

Many times, we must show components or allow actions depending on the type of user.

In this article, a proposed solution to this problem will be explained:

Glossary:

  1. Define roles and permissions
  2. Build classes for the management of permissions of each user
  3. Service for the management of permissions
  4. Directive for the easy use of permissions management
  5. Stackblitz demo implementation_

1. Define roles and permissions

The first thing we have to define are the types of roles that our application will handle:

export enum Role {
  SUPERUSER = 'su',
  ADMIN = 'admin',
  USER = 'user',
  UNKNOWN = 'unknown'
}
Enter fullscreen mode Exit fullscreen mode

The next step is to define the different permissions that we want to grant to users:

export enum PermissionType {
  CREATE = 'CREATE',
  READ = 'READ',
  UPDATE = 'UPDATE',
  DELETE = 'DELETE',
  OTHER = 'OTHER'
}
Enter fullscreen mode Exit fullscreen mode

2. Build classes for the management of permissions of each user

Once the structure of our roles and permissions has been defined, we will implement the base class for the permissions of each type of user.

import { PermissionType } from '../permission-type';

export abstract class PermissionBase {
  public permissions: PermissionType[];
  constructor() {}
}
Enter fullscreen mode Exit fullscreen mode

The next step will be to extend the base class for each type of user.

For example, for the superuser role who is granted for CREAD, READ, UPDATE, DELETE and OTHER actions:

import { PermissionType } from '../permission-type';
import { PermissionBase } from './base.permissions';

export class SuperuserPermission extends PermissionBase {

  constructor() {
    super();
    this.permissions = [  
      PermissionType.CREATE, PermissionType.READ,   
      PermissionType.UPDATE, PermissionType.DELETE,  
      PermissionType.OTHER  
    ];  
  }
}
Enter fullscreen mode Exit fullscreen mode

After defining the permissions for each type of user, we will build a factory to build the instance of the class of permissions.By using the singleton pattern, we will limit to a single instance to prevent multiple instances of the permission handler.

import { PermissionBase } from './base.permissions';
import { Role } from '../role';
import { SuperuserPermission } from './superuser.permissions';
import { AdminPermission } from './admin.permissions';
import { UserPermission } from './user.permissions';
import { UnknownPermission } from './unknown.permissions';

export class PermissionsFactory {

  public static instance: PermissionBase;
  private constructor() {}

  public static getInstance() {
    if (this.instance) {
      return this.instance;
    } else {
      const role = localStorage.getItem('role');
      switch(role) {
        case Role.SUPERUSER:
          this.instance = new SuperuserPermission();
          break;
        case Role.UNKNOWN:
          this.instance = new UnknownPermission();
          break;
        . . .
        default:  
          this.instance = new UnknownPermission();
          break;
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Service for the management of permissions

For now, we have built our permissions for each user and a permission factory to obtain the instance. However, we need an accessible way to determine whether or not a user can perform a specific action.

To solve this problem, we will implement a global service that makes use of our permission instance. In this way, it will be accessible from any component or directive of our application.

import { Injectable } from '@angular/core';
import { PermissionType } from './permission-type';
import { Role } from './role';
import { EnumValues } from 'enum-values';
import { PermissionBase } from './permissions/base.permissions';
import { PermissionsFactory } from './permissions/factory.permissions';

@Injectable(  
 provideIn: 'root'
)  
export class PermissionManagerService {

  private permissions: PermissionBase;
  constructor() { }

  isGranted (permission: PermissionType) {
    const permissions = PermissionsFactory.getInstance().permissions;
    for (let perm of permissions) {  
      if (perm === permission){  
        return true;  
      }  
    }  
    return false;
  }

  authAs (role: Role) {
    localStorage.setItem('role',
      (role === null)
        ? Role.UNKNOWN
        : role
    );
    this.permissions = PermissionsFactory.getInstance();
  }
}
Enter fullscreen mode Exit fullscreen mode
  • isGranted method receives a permission type as parameter, iterates the user’s permissions and returns true if found.
  • authAs method demand a permission manager instance.

4. Directive for the easy use of permissions management

Now, PermissionManagerService can be consumed easily. However, we can make it even easier to use the service in the templates by creating a directive that consumes it:

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
import { PermissionType } from './permission-type';
import { PermissionManagerService } from './permission-manager.service';

@Directive({  
 selector: '[appIsGranted]'  
})  
export class IsGrantedDirective {
  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef,
    private permissionManagerS: PermissionManagerService
  ) { }

  @Input() set appIsGranted(permission: PermissionType) {
    this.isGranted(permission);
  }

  private isGranted(permission: PermissionType) {
    if (this.permissionManagerS.isGranted(permission)) {  
      this.viewContainer.createEmbeddedView(this.templateRef);  
    } else {  
      this.viewContainer.clear();  
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Note that appIsGranted Directive is it is an overwrite of the ngIf directive implementation.

Directive usage:

<div appIsGranted="'CREATE'">
  // This block will only be shown to granted users
</div>

<my-component appIsGranted="'DELETE'">
  // This component will only be shown to granted users
</my-component>
Enter fullscreen mode Exit fullscreen mode

5. Stackblitz demo implementation

rjlopez-angular-permissions-part1 - StackBlitz

Note that it is not an implementation for a real environment and contains mocked code and data.

See Angular permissions based on roles | Part 2. Permissions for multiple resources

Top comments (0)