What is the most effective approach to managing permissions in Angular 4?

For my Angular 4 project, I am looking to implement a permission system that will retrieve permissions from an API in the form of id arrays. Certain entities such as users or blog posts will have properties specifying allowed actions like editing or deleting, also stored as arrays of ids.

What is the most effective approach for managing and checking permissions in Angular 4 projects? Does Angular offer native solutions for handling permissions, or are there alternative methods for implementing permission management if no out-of-the-box solution exists?

Answer №1

As mentioned by Rahul in the comment, using Guard is an out-of-the-box solution that may meet your requirements.

It's important to note that guards are specifically for routing purposes. They are used to determine whether a user can access a particular route or not. If you need to display or hide single elements within a component based on roles, I would recommend using *ngIf to render UI elements based on conditions.

If you want to create a Guard based on roles (not just checking if a user is authenticated), you can implement something like this:

import { Injectable } from "@angular/core";
import { AuthService, CurrentUserService } from "app/shared/services";
import { Router, RouterStateSnapshot, ActivatedRouteSnapshot, CanActivate } from "@angular/router";
import { AspNetUsersDTO } from "app/shared/models";
import { Observable } from "rxjs/Rx";

@Injectable()
export class RoleGuard implements CanActivate {

    constructor(private authService: AuthService,
        private _currentUser: CurrentUserService,
        private router: Router) {
    }

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
        return new Promise<boolean>((resolve, reject) => {

            if (!this.authService.isLoggedIn()) {
                resolve(false);
                return;
            }


            var currentUser: AspNetUsersDTO = new AspNetUsersDTO();

            this._currentUser.GetCurrentUser().then((resp) => {
                currentUser = resp;
                let userRole = currentUser.roles && currentUser.roles.length > 0 ? currentUser.roles[0].toUpperCase() : '';
                let roles = route && route.data["roles"] && route.data["roles"].length > 0 ? route.data["roles"].map(xx => xx.toUpperCase()) : null;

                if (roles == null || roles.indexOf(userRole) != -1) resolve(true);
                else {
                    resolve(false);
                    this.router.navigate(['login']);
                }

            }).catch((err) => {
                reject(err);
                this.router.navigate(['login']);
            });
        });

    }

    canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {

        return new Promise<boolean>((resolve, reject) => {

            if (!this.authService.isLoggedIn()) {
                resolve(false);
                return;
            }


            var currentUser: AspNetUsersDTO = new AspNetUsersDTO();

            this._currentUser.GetCurrentUser().then((resp) => {
                currentUser = resp;
                let userRole = currentUser.roles && currentUser.roles.length > 0 ? currentUser.roles[0].toUpperCase() : '';
                let roles = route && route.data["roles"] && route.data["roles"].length > 0 ? route.data["roles"].map(xx => xx.toUpperCase()) : null;

                if (roles == null || roles.indexOf(userRole) != -1) resolve(true);
                else {
                    resolve(false);
                    this.router.navigate(['login']);
                }

            }).catch((err) => {
                reject(err);
                this.router.navigate(['login']);
            });
        });

    }
}

You can then use this Guard in your routing configuration like this:

{
        path: 'awards-team',
        component: AwardsTeamComponent,
        canActivateChild: [RoleGuard],
        children: [
          {
            path: 'admin',

            component: TeamComponentsAdminComponent,
            data: { roles: ['super-admin', 'admin', 'user'] }
          },
          {
            path: 'user',

            component: TeamComponentsUserComponent,
            data: { roles: ['user'] }
          }
        ]
      }

Answer №2

If you're looking to manage permissions in your Angular application, consider using the ngx-permissions library. This library offers the advantage of removing elements from the DOM based on set permissions. An example of how to load permissions:

import { Component, OnInit } from '@angular/core';
import { NgxPermissionsService } from 'ngx-permissions';
import { HttpClient } from '@angular/common/http';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  title = 'app';

   constructor(private permissionsService: NgxPermissionsService,
               private http: HttpClient) {}

  ngOnInit(): void {
    const perm = ["ADMIN", "EDITOR"];

    this.permissionsService.loadPermissions(perm);

     this.http.get('url').subscribe((permissions) => {
       //const perm = ["ADMIN", "EDITOR"]; example of permissions
       this.permissionsService.loadPermissions(permissions);
    })
  }
}

Implementation in templates

<ng-template [ngxPermissionsOnly]="['ADMIN']" (permissionsAuthorized)="yourCustomAuthorizedFunction()" (permissionsUnauthorized)="yourCustomAuthorizedFunction()">
    <div>Congratulations! You can see this text.</div>
 </ng-template>
<div *ngxPermissionsOnly="['ADMIN', 'GUEST']">
    <div>Congratulations! You can see this text.</div>
</div>

 <div *ngxPermissionsExcept="['ADMIN', 'JOHNY']">
   <div>Everyone will see it except for Admin and Johny.</div>
 </div>

Answer №3

From my perspective, obtaining API permissions is the most effective way to manage user rights. Along with utilizing the canActivate attribute in Router for permission checks, I prefer incorporating permission validation in a navigation interceptor. This method ensures that the URL remains intact even when incorrect permissions are detected, instead of redirecting to localhost:42000/#/.../permission-denied.

Below is an example of how I implement this:

In the template:

<ng-template [ngIf]="!loading" [ngIfElse]="loadingView">
  <router-outlet *ngIf="canView"></router-outlet>
  <app-permission-denied *ngIf="!canView"></app-permission-denied>
</ng-template>
<app-loading #loadingView></app-loading>

In the component:

import { Component, OnInit} from '@angular/core';
import {
  Router,
  Event as RouterEvent,
  NavigationStart,
  NavigationEnd,
  NavigationCancel,
  NavigationError
} from '@angular/router'
import { Title } from '@angular/platform-browser';
import { DataService } from '../core/service/data.service';
import { NotificationService } from '../core/service/notification.service';

@Component({
  selector: 'app-main',
  templateUrl: './main.component.html',
  styleUrls: ['./main.component.css']
})
export class MainComponent implements OnInit{

  loading: boolean = true;
  canView: boolean = false;

  constructor(private router: Router,
              private _dataService: DataService,
              private titleService: Title,
              private _notificationService: NotificationService) {
    this.router.events.subscribe((event: RouterEvent) => {
      this.navigationInterceptor(event);
    });
  }

  ngOnInit() { }

  navigationInterceptor(event: RouterEvent): void {
    if (event instanceof NavigationStart) {
      this.loading = true;
      this.canView = false;
    }
    if (event instanceof NavigationEnd) {
      
      // Permission check based on function ID...
      let functionId = this.getDataRouter(this.router, "function");

      if (functionId != null) {
        this._dataService.get('your_api_to_check_permission_with_functionId' + functionId).subscribe(
          (data) => {
            if (data && data.CanRead) {
              this.canView = true;
              let title = this.getDataRouter(this.router, "title");
              this.titleService.setTitle(title);
            } else {
              this.canView = false;
              this.titleService.setTitle('Permission denied');
            }
            this.loading = false;
          }, (err) => {
            this._dataService.handleError(err);
          });
      } else {
        this.loading = false;
        this.canView = true;
        this._notificationService.printErrorMessage('Dev: please provide function Id to check permission');
      }
    }

    if (event instanceof NavigationCancel || event instanceof NavigationError) {
      this.loading = false;
    }
  }

  getDataRouter(router, name) {
    var root = router.routerState.snapshot.root;
    while (root) {
      if (root.children && root.children.length) {
        root = root.children[0];
      } else if (root.data && root.data[name]) {
        return root.data[name];
      } else {
        break;
      }
    }
    return null;
  }
}

In the router module:

const mainRoutes: Routes = [
      { path: 'user', loadChildren: './system/user/user.module#UserModule', data: { function: "USER" } },

      { path: 'function', loadChildren: './system/function/function.module#FunctionModule', data: { function: "FUNCTION" } }
    ]

Answer №4

If you're looking for the best method to achieve this, I recommend utilizing Angular's Router Guard.

To kickstart your journey, take a look at the following resources:

router guard alligator

Router Guard Medium

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Any URLs that are requested through HTTP on the server must be for absolute server rendering

I've encountered an error while working on Angular 4 CLI + Universal, and I need help resolving it. I recall that it should work with relative paths, but I'm unsure how to achieve this. ERROR Error: URLs requested via Http on the server must b ...

Angular2 can enhance your webpage with a <div> element that covers the entire screen

While working on my Angular2 application, I encountered a challenging issue that I can't seem to resolve or find a solution for. Take a look at this image of my page: https://i.stack.imgur.com/b6KlV.png Code: content.component.html <app-header& ...

Guide to swapping out embedded objects within a TypeScript data structure

I am in need of modifying a TypeScript object by conducting a key search. It is important to note that the key may be repeated within the object, so I must ensure it belongs to the correct branch before making modifications to the corresponding object. To ...

Transferring a zipped JSZip file to a Java server

I am striving to achieve the task of uploading a zip file to my Angular 2 front-end, transferring it to my Java (Spring Boot) back-end, and extracting the xml files enclosed within the zip. Currently, I am utilizing JSZip for handling the zip file uploads ...

Is the parameter rejecting the use of orderBy for 'startTime'?

Hey there! I'm currently working on an AngularJS project in TypeScript that involves integrating Google API to fetch Google Calendar events. The syntax for making a call is specified in the documentation available at: https://developers.google.com/goo ...

How to compare and filter items from two arrays using TypeScript

I am looking to filter out certain elements from an array in TypeScript Original Array: [ {"Id":"3","DisplayName":"Fax"}, {"Id":"1","DisplayName":"Home"}, {"Id":&quo ...

Ways to apply CSS class styles when a button is clicked in Angular

How can I create a button that toggles between light and dark mode when clicked, changing the background color and font color accordingly? I need to add CSS classes .bgdark and .textlight to the 'mainbody' for dark mode. HTML <div cla ...

Angular 6 Bootstrap Fixed Positioning

Looking to implement affix or scrollspy functionality from Bootstrap 4 into Angular 6, I've been searching for existing libraries. Came across JonnyBGod's scrollspy, but it appears to be designed for Angular 5 and uses rxjs 5. Does anyone know of ...

Angular 5: There was an issue with the property not defined for lowercase conversion

I keep encountering this error whenever I attempt to filter a column of a table. The data is retrieved from a FireStore cloud collection and the 'auteur' field is defined in each document. Here is how my component looks: import { Component, OnI ...

"Encountered an undefined ViewChild error when using Material Angular MatSidenav

Struggling with Material and angular here! Trying to toggle the Sidenav from a different component using viewchild but running into issues with undefined elements. Here's what I have so far: sidenav.component.html <mat-sidenav-container class="ex ...

What could be the reason for the authentication issues in ionic/angular?

An authentication application has been created to receive user information and tokens (jwt) from the server. The data is stored locally and used for further computations within the app. For route guarding, if the token is available, it should proceed to th ...

How can I retrieve all values from an input number field that is created using *ngFor in Angular?

In my table, I have a list of cart products displayed with a quantity field. Users can increase or decrease the quantity using options provided. Currently, if I place an update button inside a loop, it creates separate buttons for each product. However, I ...

Incorporate chat functionality into Angular using an embedded script

Recently, I encountered an issue with inserting an online chat using a Javascript script into my website. My initial plan was to add it directly to my app.component.html, but unfortunately, it didn't display as expected. I'm now exploring option ...

Update current properties of objects

I'm feeling like I'm going crazy and could really use some assistance. My predicament involves a function that looks like this: private generateTimeObject(firstObject: someInterface, secondObject?: someInterface) { let firstTime; let secondTi ...

Looking for a way to convert 24-hour format to minutes in Angular? I'm in need of some TypeScript code to

I am looking for Typescript code that can convert 24-hour time format into minutes. For example, when converting 1.00 it should output as 60 minutes. When converting 1.30 it should equal to 90 minutes. If anyone has the code for this conversion, p ...

What imports are needed for utilizing Rx.Observable in Angular 6?

My goal is to incorporate the following code snippet: var map = new google.maps.Map(document.getElementById('map'), { zoom: 4, center: { lat: -25.363, lng: 131.044 } }); var source = Rx.Observable.fromEventPattern( function (han ...

Both undefined and null are sometimes allowed as values in conditional types, even when they should not be

Do you think this code should trigger a compiler error? type Test<T extends number | string> = { v: T extends number ? true : false } const test: Test<1> = { v: undefined } Is there something I am overlooking? Appreciate your help! ...

What is the best way to delay an observable from triggering the next event?

In my Angular project, I am implementing RxJs with two subjects. s1.next() s1.subscribe(() => { // perform some operation and then trigger the event for s2 s2.next() }); s2.subscribe(() => { // perform some operat ...

Using TypeScript and Angular to retrieve a service instance within a static function

I have a situation where I am trying to access an instance property within a static method in my utility class. Here is an example code snippet: export class DataUtil { constructor(public core:CoreStructureService){ } static fetchContact(id:s ...

Performing unit tests in Angular 2 with karma

Trying to grasp the concept of unit testing in Angular has been quite a challenge for me, especially since I am still navigating through Angular 2 and its syntax. Understanding testing becomes even more intricate. I attempt to follow the examples provided ...