Show the same values only once when using the ngFor directive in Angular, while the remaining values continue to loop

Here is a sample data set retrieved from the 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',
    },

];

My challenge now is to display this data as depicted in the Expected Output image. However, in the current display, main level values are repeating for each sub-level value. Expected Output: https://i.sstatic.net/NadHY.png

My Output: https://i.sstatic.net/pIMcs.png

Here is the 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>

And here is the 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
  })

Do you have any suggestions on how to achieve this successfully? Thanks

Answer №1

To achieve the desired outcome, you will need to make modifications to the objects in the array. One way to do this is by using the reduce method.

It is recommended to have an object structure similar to the following for simplification:

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

The original code snippet is as follows:

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
  };
}

In the template file, you will now need to use two ngFor directives to iterate over main menu items and their respective submenu items:

<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>

Check out the Stackblitz for a live example

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

Mastering Vue3: Typed Component Instance Template Refs with Exposed Methods

In my project, I am working with a component called A that has a method called send. Here is an example of how Component A is structured in A.vue: <script setup lang="ts"> function send(data: string) { console.log(data) } defineExpose({ ...

What is the best way to shorten text in Angular?

I am looking to display smaller text on my website. I have considered creating a custom pipe to truncate strings, but in my situation it's not applicable. Here's what I'm dealing with: <p [innerHTML]="aboutUs"></p> Due to t ...

Upgrading Angular from version 5 to 6 resulted in the Angular.json file not being generated

As I follow the official guide to upgrade my Angular app to version 10, I am currently facing an issue while trying to upgrade to CLI version 6 following the instructions on update.angular.io. It is important to ensure that you are using Node 8 or later. ...

Is CORS necessary when using npm, and is Putty a viable tool for web application development?

Currently, I am immersed in a web application project that relies on the following technologies: Express.js + Node.js MySQL Angular 4 PM2 (Process manager) Here are the libraries utilized on the backend: express body-parser jsonwebtoken bcrypt-nodejs ...

What is the best way to utilize the Moment.js TypeScript definition file in a website that already has moment.min.js integrated?

Currently, I am in the process of transitioning a website to utilize TypeScript by converting one JavaScript file at a time. All pages on my site are already linked to moment.js, such as: <script src="/scripts/moment.min.js"></script> I have ...

TypeScript: Seeking a mechanism akin to ReturnType<...> that specifically targets the type of the initial function parameter

Can TypeScript allow for the declaration of a ReturnType<...> that doesn't fetch the return value's type but instead retrieves the type of the first argument? type SingleArgFunction<A, R> = (arg: A) => R // incorrect - how can th ...

Choose the "toolbar-title" located within the shadow root of ion-title using CSS

Within Ionic, the ion-title component contains its content wrapped in an additional div inside the shadow-dom. This particular div is designated with the class .toolbar-title. How can I target this div using a SCSS selector to modify its overflow behavior? ...

Validation of time input using Angular Material within a ReactiveForm

Currently immersed in Angular 17 along with Material theme, I find myself faced with a scenario involving Reactive Forms housing two time-input fields. The array [minHour, maxHour] should represent a range of Hours, let's say [09:00 , 13:00]. HTML [ ...

Issue with ngStyle function in Internet Explorer

Within my Angular 4 application, I have defined styles as shown below: [ngStyle]="{'border': getInterleaveColor(i)}" The following function is also included: getInterleaveColor(auditNumber) { var borderProperties = '2px solid'; ...

Is there a way to recursively convert property types from one to another in an object that contains optional properties?

The scenario: I am currently working with MongoDB and a REST API that communicates using JSON. MongoDB uses objects instead of identifiers for documents, but when these objects are stringified (such as in a response body), they get converted into strings. ...

Jasmine test case for Angular's for loop (Error: Expected the length of $ to be 11, but it should be 3

While creating test cases for a loop in Angular (Jasmine), I encountered an error: Error: Expected $.length = 11 to equal 3.. The loop is supposed to generate an array of arrays similar to const result. Can someone point out what I might have missed here? ...

What is the best way to perform a query in Angular using Firebase Firestore?

I am attempting to execute queries in Angular 6 using Firebase Firestore. I have this code, and I have already installed the package "npm firebase @angularfire" but it is not working: import { Component } from '@angular/core'; import { A ...

Pre-requisites verification in TypeScript

I have a typescript class with various methods for checking variable types. How can I determine which method to use at the beginning of the doProcess() for processing the input? class MyClass { public static arr : any[] = []; // main method public stati ...

Is it possible for me to generate typing/declaration (.d.ts) and decorator metadata (.metadata.json) files with the help of @ngtools/webpack?

Currently, I am in the process of updating an Angular library to be compatible with AOT compilation. To achieve this, I have set up some gulp tasks using ngc. However, I am considering switching to @ngtools/webpack as it provides a more convenient way fo ...

Pause after the back button is activated

Upon pressing the back button on certain pages, there is a noticeable delay (approximately 1-5 seconds) before the NavigationStart event registers. I am utilizing the Angular RouterExtensions back() function for this action. Initially, I suspected that t ...

The error message "Angular 14 + Jest - typescript_1.default.canHaveDecorators is not a function" indicates that

Upon setting up Jest with Angular 14, I encountered the following error message: Test suite failed to run TypeError: typescript_1.default.canHaveDecorators is not a function at TypeScriptReflectionHost.getDecoratorsOfDeclaration (node_modules/jest-prese ...

Validation in custom components within an Angular reactive form is failing to function correctly

Currently, I am in the process of developing a new component within Angular reactive forms. To view my sample project: https://stackblitz.com/edit/angular-pyi9jn The angular form I created follows this structure: form - simple counter The form output i ...

Zod implements asynchronous validation for minimum, maximum, and length constraints

When working with Zod, setting values can be done as shown below: z.string().max(5); z.string().min(5); z.string().length(5); However, in my scenario, the values (e.g., 5) are not predetermined. They are fetched from an API dynamically. How can I create t ...

What is the best method for saving console.log output to a file?

I have a tree structure containing objects: let tree = {id: 1, children: [{id: 2, children: [{id: 3}]}]} My goal is to save all the id values from this tree in a text file, indenting elements with children: 1 2 3 Currently, I am using the following ...

Angular 2 is throwing an error: Unhandled Promise rejection because it cannot find a provider for AuthService. This error is occurring

My application utilizes an AuthService and an AuthGuard to manage user authentication and route guarding. The AuthService is used in both the AuthGuard and a LoginComponent, while the AuthGuard guards routes using CanActivate. However, upon running the app ...