Search code examples
angulartypescriptngforangular10

Display multiple same values only once while others are looping using ngFor in Angular


I have the following sample data set which comes from server.

[
    {
        subMenuId: 1,
        submenu: 'Users',
        menu: 'Administration',
        url: '/pms/admin/usrs-dsh',
        icon: 'fas fa-cogs',
    },
    {
        subMenuId: 2,
        submenu: 'Roles',
        menu: 'Administration',
        url: '/pms/admin/roles-dsh',
        icon: 'fas fa-cogs',
    },
    {
        subMenuId: 3,
        submenu: 'Menu Items',
        menu: 'Administration',
        url: '/pms/admin/menus-dsh',
        icon: 'fas fa-cogs',
    },
    {
        subMenuId: 4,
        submenu: 'Banks',
        menu: 'Administration',
        url: '/pms/admin/banks-dsh',
        icon: 'fas fa-cogs',
    },
    {
        subMenuId: 5,
        submenu: 'Branches',
        menu: 'Administration',
        url: '/pms/admin/branches-dsh',
        icon: 'fas fa-cogs',
    },
    {
        subMenuId: 6,
        submenu: 'Dashboard',
        menu: 'PD3',
        url: '/pms/pd3/dsh',
        icon: 'fas fa-cogs',
    },

];

And I want to display this data like this. But currently when displaying data main level values repeat with each sub level value. Expected Output: Expected Output

My Output: my output

Html file:

 <tr *ngFor="let m of userPermissions;">
                        <td>
                            <label class="ml-3 form-check-label">{{m.menu}}</label>
                        </td>
                        <td>
                            <div>
                                <input (change)="changeSubMenus($event, m.menu,m.subMenuId)" type="checkbox" class="ml-0 form-check-input">
                                <label class="ml-3 form-check-label">{{m.submenu}}</label><br>
                            </div>
                        </td>
                    </tr>

TS file:

getMenus(){
  this.userPermissions = this.adminService.allMenus;
  
  this.userPermissions.forEach((val) =>{
     // console.log(val)
     // TODO: remove multiple main level values and display sub level values with one main level value
  })

}

Is there any way to achieve this. Thanks


Solution

  • You need to modify the objects in the array to achieve the requirement you want. You can use reduce for that like below.

    You need to have an object structure like the below, to make things simpler

    modifiedMenu = [{
      menu: < Main Menu 1 > ,
      subMenu: [ < submenu item > , < submenu item > , ...]
    }, {
      menu: < Main Menu 2 > ,
      subMenu: [ < submenu item > , < submenu item > , ...]
    }, ...]
    

    So, the original one is

    userPermissions = this.menus.reduce((acc, ele) => {
      if (acc.length === 0) {
        acc.push(this.getModifiedMainMenu(ele));
        return acc;
      } else {
        const existedMenu = acc.find(m => m.menu === ele.menu);
        if (existedMenu) {
          existedMenu.subMenu.push(this.getSubMenu(ele));
          return acc;
        } else {
          acc.push(this.getModifiedMainMenu(ele));
          return acc;
        }
      }
    }, []);
    
    getModifiedMainMenu(obj) {
      return {
        menu: obj.menu,
        subMenu: [this.getSubMenu(obj)]
      };
    }
    
    getSubMenu(obj) {
      return {
        menu: obj.menu,
        subMenuId: obj.subMenuId,
        submenu: obj.submenu,
        url: obj.url,
        icon: obj.icon
      };
    }
    

    And in the template file, now you need 2 ngFors to loop main menu items and respective submenu items like below

    <tr *ngFor="let m of userPermissions;">
      <td>
        <label class="ml-3 form-check-label">{{m.menu}}</label>
      </td>
      <td>
        <div *ngFor="let subMenu of m.subMenu">
          <input (change)="changeSubMenus($event, subMenu.menu, subMenu.subMenuId)" type="checkbox" class="ml-0 form-check-input">
          <label class="ml-3 form-check-label">{{subMenu.submenu}}</label>
        </div>
      </td>
    </tr>
    

    Working Stackblitz