How do I populate an MenuItem by iterating over an array in typescript and group items in a label?

Viewed 584

I have the following array:

array = [
    {active: true, category: "Category A", name: "One A"}, 
    {active: true, category: "Category B", name: "One B"}, 
    {active: true, category: "Category A", name: "Two A"}, 
    {active: true, category: "Category B", name: "Two B"}];

I want to loop over this array and build the following MenuItem:

menuItems = [
      {
          label: 'Category A',
          items: [
            {label: 'One A'},
            {label: 'Two A'}
        ]
      },
      {
          label: 'Category B',
          items: [
            {label: 'One B'},
            {label: 'Two B'}
        ]
      }
    ];

I started with

array.forEach((item) => {
    if(item.active) {
        menuItems.push({})            
    }
});

I figured I have to some how use the label as a key but I don't know how. I search for a while and the closest I got was this example, but here they are building the menu with ul and li and I want to just bind the array (menuItem) to a p-tieredMenu (from PrimeNG).

Can you guys help me out? I thank you in advance.

Update: I tried to output the result to see if I figure out how to achieve my goal:

if (value) {
    value.forEach((item) => {
        if(item.active) {
            // this.addFieldItems.push({})            
            console.log('category: ' + item.category + '\nname: ' + item.name);
        }
    });
}   

what I find difficult is how do I push the right item into the right place. Right now I can populate the list by just pushing each object unto the menuitem array by how do I group it so that each category have subcategories.

UPDATE:
I almost have it but I get a cannot read property of undefined error:

if (value) {
  let catArray: any = [];
  value.forEach((item) => {
    if(item.active) {
      if (catArray.indexOf(item.category) < 0) {
        catArray.push(item.category);
      }
    }
  });
  catArray.forEach((cat: any) => {
    let arrItems: any = [];
    value.forEach((item) => {
      if(item.category == cat) {
        arrItems.push(
          { label: item.name
          }
        );
      }
    });
    this.addFieldItems.push({label: cat, items: arrItems});
  });
}

In the above code value is the original array and addFieldItems is the desired array. What is mistake here?

1 Answers

Using Array.reduce() to map one structure to another

In order to group values together in a new array under some key which exists on each element in the flat structure (original array) we need to do the following:

  • Go through each element in the original array
  • Check where in the output array our grouped property item is located at
  • Add our item as a sub-item.
  • If our grouping item does not exist, we push a new item into our output array, creating the grouping.

To do this we will utilize Array.reduce and Array.findIndex

function flatToMenuStructure(flatArray) {
  return flatArray.reduce((out, item) => {
    if (item.active) {
      // which array item do we group our current item under.
      let index = out.findIndex(e => e.label == item.category);
      // first time seeing this category, if index is -1.
      if (index === -1) {
        out.push({
          label: item.category,
          items: []
        });
        index = out.length - 1;
      }
      // add menu item to category.
      out[index].items.push({
        label: item.name
      })
    }
    return out;
  }, []);
}
Related