Role-based Dynamic Menus in Angular (Enterprise)

Role-based Dynamic Menus in Angular (Enterprise)

This Angular setup includes:

  • ✅ Roles + Permissions
  • ✅ Show/Hide menu items dynamically
  • ✅ Works with Angular Material Sidenav
  • ✅ Supports nested menus
  • ✅ Supports multiple roles
  • ✅ Enterprise clean structure

Goal

If user has:

  • Admin → show everything
  • Manager → show dashboard + reports
  • User → show only basic pages

1️⃣ Permission Model (Enterprise)


// permission.model.ts
export type Role = 'Admin' | 'Manager' | 'User';

export type Permission =
  | 'DASHBOARD_VIEW'
  | 'USERS_VIEW'
  | 'USERS_CREATE'
  | 'REPORTS_VIEW'
  | 'SETTINGS_VIEW';

2️⃣ Menu Model


// menu-item.model.ts
import { Permission } from './permission.model';

export interface MenuItem {
  label: string;
  icon?: string;
  route?: string;
  permissions?: Permission[]; // permissions required
  children?: MenuItem[]; // nested menus
}

3️⃣ Define Enterprise Menu Config


// menu.config.ts
import { MenuItem } from './menu-item.model';

export const MENU_ITEMS: MenuItem[] = [
  {
    label: 'Dashboard',
    icon: 'dashboard',
    route: '/dashboard',
    permissions: ['DASHBOARD_VIEW']
  },
  {
    label: 'Users',
    icon: 'people',
    permissions: ['USERS_VIEW'],
    children: [
      { label: 'User List', route: '/users', permissions: ['USERS_VIEW'] },
      { label: 'Create User', route: '/users/create', permissions: ['USERS_CREATE'] }
    ]
  },
  {
    label: 'Reports',
    icon: 'bar_chart',
    route: '/reports',
    permissions: ['REPORTS_VIEW']
  },
  {
    label: 'Settings',
    icon: 'settings',
    route: '/settings',
    permissions: ['SETTINGS_VIEW']
  }
];

4️⃣ Auth / Permission Service


// permission.service.ts
import { Injectable } from '@angular/core';
import { Permission, Role } from './permission.model';

@Injectable({ providedIn: 'root' })
export class PermissionService {
  private userRoles: Role[] = [];
  private userPermissions: Permission[] = [];

  setUserAccess(roles: Role[], permissions: Permission[]) {
    this.userRoles = roles;
    this.userPermissions = permissions;
  }

  hasPermission(permission: Permission): boolean {
    return this.userPermissions.includes(permission);
  }

  hasAnyPermission(permissions: Permission[]): boolean {
    return permissions.some(p => this.hasPermission(p));
  }

  isInRole(role: Role): boolean {
    return this.userRoles.includes(role);
  }
}

5️⃣ Create a Custom Directive (Best Enterprise Way)

This avoids long *ngIf checks in templates.


// has-permission.directive.ts
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
import { Permission } from './permission.model';
import { PermissionService } from './permission.service';

@Directive({ selector: '[hasPermission]' })
export class HasPermissionDirective {
  @Input('hasPermission') requiredPermissions: Permission[] = [];

  constructor(
    private templateRef: TemplateRef,
    private viewContainer: ViewContainerRef,
    private permissionService: PermissionService
  ) {}

  ngOnInit() {
    const canShow = this.permissionService.hasAnyPermission(this.requiredPermissions);
    this.viewContainer.clear();
    if (canShow) this.viewContainer.createEmbeddedView(this.templateRef);
  }
}

6️⃣ Use in Angular Material Menu / Sidenav


// app-sidenav.component.html

  
    
    
      {{ item.icon }}
      {{ item.label }}
    

    
    
{{ child.label }}

7️⃣ Load Menu in Component


// app-sidenav.component.ts
import { Component } from '@angular/core';
import { MENU_ITEMS } from './menu.config';

@Component({
  selector: 'app-sidenav',
  templateUrl: './app-sidenav.component.html'
})
export class AppSidenavComponent {
  menu = MENU_ITEMS;
}

8️⃣ Enterprise: Filter Menu Items Automatically

Instead of directive in template, filter menu in service before display.


// menu.service.ts
import { Injectable } from '@angular/core';
import { MenuItem } from './menu-item.model';
import { MENU_ITEMS } from './menu.config';
import { PermissionService } from './permission.service';

@Injectable({ providedIn: 'root' })
export class MenuService {
  constructor(private permissionService: PermissionService) {}

  getMenu(): MenuItem[] {
    return this.filterMenu(MENU_ITEMS);
  }

  private filterMenu(items: MenuItem[]): MenuItem[] {
    return items
      .filter(item => !item.permissions || this.permissionService.hasAnyPermission(item.permissions))
      .map(item => ({
        ...item,
        children: item.children ? this.filterMenu(item.children) : undefined
      }))
      .filter(item => !item.children || item.children.length > 0);
  }
}
// Usage in component
menu = this.menuService.getMenu();

Interview Answer (Perfect)

Q) How do you implement role-based menus in Angular?

  • ✅ Store menu items in a config file with required permissions.
  • ✅ Backend returns roles/permissions after login.
  • ✅ Angular filters menu items based on permissions.
  • ✅ UI displays only allowed items using a directive or menu filtering service.

Extra Enterprise Final Touch

Backend should return permissions like:


{
  "roles": ["Admin"],
  "permissions": ["DASHBOARD_VIEW", "USERS_VIEW", "USERS_CREATE"]
}

Comments

Popular posts from this blog

Debouncing & Throttling in RxJS: Optimizing API Calls and User Interactions

Promises in Angular

Comprehensive Guide to C# and .NET Core OOP Concepts and Language Features