0

I'm trying to use a nested *ngFor in my Angular project to render a dynamic menu. I'm trying something like this:

<li class="treeview" *ngFor="let pm of parentMenu">

    <a href="#">
      <i class="fa fa-edit"></i> <span>{{pm.MenuTitle}}</span>
      <span class="pull-right-container">
        <i class="fa fa-angle-left pull-right"></i>
      </span>
    </a>
    <ul class="treeview-menu" *ngFor="let cm of childMenu">
      <li *ngIf="cm.ParentMenuId == pm.Id">{{cm.MenuTitle}}</li>
    </ul>
  </li>

I'm only getting the first element of childMenu, my understanding was *ngFor works similar to foreach in C#, but clearly, that's not the case. Could someone please help me fix the code and understand it?

7
  • *ngIf works similar to foreach I guess you mean *ngFor, which does indeed work like a forEach. *ngIf works like an if (no surprises here) Commented Aug 28, 2018 at 11:51
  • Yes, sorry, will update the question. Commented Aug 28, 2018 at 11:52
  • The inner *ngFor would generate several li-elements but the *ngIf picks out just one. Or is the condition true for several of them? Commented Aug 28, 2018 at 11:53
  • so how it's working, the first iteration from the outer loop and then all iteration from the inner loop, and then second iteration from the outer loop and again all iteration from the inner loop and so on? Commented Aug 28, 2018 at 11:54
  • yes, exactly like an outer and an inner for loop Commented Aug 28, 2018 at 11:55

5 Answers 5

3

I don't know the content of your .ts file, but your code works fine for me here:

https://stackblitz.com/edit/angular-sduuwm

Notice that I have defined my arrays like this:

  parentMenu = [
    {
      Id: 1,
      MenuTitle: "One",
    },
    {
      Id: 2,
      MenuTitle: "Two",
    },
    {
      Id: 3,
      MenuTitle: "Three",
    }
  ];

  childMenu = [
    { 
      ParentMenuId: 1,
      MenuTitle: "One quarter"
    },
    {
      ParentMenuId: 1,
      MenuTitle: "One half"
    },
    {
      ParentMenuId: 2,
      MenuTitle: "Two half"
    },
    {
      ParentMenuId: 3,
      MenuTitle: "Three half"
    }
  ];

However, that said, if I were you I would rather define my arrays like this:

  parentMenu = [
    {
      Id: 1,
      MenuTitle: "One",
      childMenu: [
        { MenuTitle: "One quarter" },
        { MenuTitle: "One half" },
      ]
    },
    {
      Id: 2,
      MenuTitle: "Two",
      childMenu: [
        { MenuTitle: "Two half" },
      ]
    },
    {
      Id: 3,
      MenuTitle: "Three",
      childMenu: [
        { MenuTitle: "Three half" },
      ]
    }
  ];

and do the HTML like this:

<a href="#">
  <i class="fa fa-edit"></i> <span>{{pm.MenuTitle}}</span>
  <span class="pull-right-container">
    <i class="fa fa-angle-left pull-right"></i>
  </span>
</a>
<ul class="treeview-menu" *ngFor="let cm of pm.childMenu">
  <li>{{cm.MenuTitle}}</li>
</ul>

And you won't need the *ngIf test in your code.

Sign up to request clarification or add additional context in comments.

3 Comments

Sorry, I must downvote. if you has parent and child has an uniq object. If you has an uniq object you must use *ngFor="let child of pm .children"
Can't make sense of your comment, dude, please justify your downvote more comprehensibly. I agree the alternative solution wouldn't work with his HTML, but it does show him alternative solutions to his problem.
i's better not use *ngIf to show only one element. You can use let i=index and show parentMenu[i].MenuTitle (using only the last *ngFor) or transform the two arrays in only one. Anyway I just remove the downvote (but I feel that the answer is not correct)
0

You are correct that ngFor behaves like a foreach or for loop.

I suspect your problem is the object you are binding to.

using basically your code there should be a property on the component called parentItem that is an array with nested properties including childMenu

parentMenu = {
    'Parent1':[{'menuTitle':'Menu 1', 'childItem':[
      {'menuTitle':'sub item 1'}]
      },
     {'menuTitle':'A cool menu', 'childItem':[
       {'menuTitle':'sub item 1'},
     {'menuTitle': 'sub item 2'}]
     }]
  }

and then your HTML should work much like:

<li class="treeview" *ngFor="let pm of parentMenu.Parent1">

    <a href="#">
      <i class="fa fa-edit"></i> <span>{{pm.menuTitle}}</span>
      <span class="pull-right-container">
        <i class="fa fa-angle-left pull-right"></i>
      </span>
    </a>
    <ul class="treeview-menu" *ngFor="let cm of pm.childItem">
      <li>{{cm.menuTitle}}</li>
    </ul>
  </li>

And for reference in action a working stackblitz from your code:

3 Comments

Even after refactoring the code according to your suggestion, I'm still only getting the first element of the child menu. When rendering the length, it's showing 2 but only the first element is getting rendered. I'm total confused now. It should just work.
Seems there was a problem with my html template. after removing all the bootstrap classes and putting just plain html for, everything rendered correctly.
I had to put the inner *ngFor in the <li> rather than <ul> like bellow: <ul class="treeview-menu"> <li *ngFor="let cm of pm.ChildMenu"> <a href={{cm.MenuUrl}}><i class="fa fa-circle-o"></i>{{cm.MenuTitle}}</a> </li> </ul>
0

try this code

 <ul class="treeview-menu" *ngFor="let cm of pm.childMenu">
      <li *ngIf="cm.ParentMenuId == pm.Id">{{cm.MenuTitle}}</li>
    </ul>

2 Comments

pm doesn't have a childMenu property. I'm reading from two different array, parentMenu and childMenu.
Why you have two arrays?
0
<ul>    
          <ng-template #recursiveMenu let-parentMenu>
             <li class="treeview" *ngFor="let pm of parentMenu">
               <a href="#">
                 <i class="fa fa-edit"></i> <span>{{pm.MenuTitle}}</span>
                 <span class="pull-right-container">
                   <i class="fa fa-angle-left pull-right"></i>
                  </span>
               </a>
               <ul class="treeview-menu">
                 <ng-container *ngTemplateOutlet="recursiveMenu; context:{ 
                   $implicit: pm.childMenu}">
                 </ng-container>
               </ul>
             </li>
          </ng-template>
          <ng-container *ngTemplateOutlet="recursiveMenu; context:{ 
            $implicit: parentMenu}">
          </ng-container>
</ul>

1 Comment

Could you please add a little explanation to you code.
-1

You can achieve recursive menu by using template, containers or recursive calling of a component. There are different approaches that you can follow.

Angular2 Navigation Menu with Recursive Templates

Recursion in Angular Components

Angular *ngFor recursive list tree template

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.