Render hierarchical data in a table

1,215 views
Skip to first unread message

Slava Fomin

unread,
Apr 29, 2015, 2:53:07 PM4/29/15
to ang...@googlegroups.com
Hello!

I've encountered an advanced use-case in my practice that I have problems implementing in Angular.js, so I'm in need of assistance.

It is required to render a hierarchical data in a single HTML table.

I've found this Q/A: http://stackoverflow.com/questions/11854514/is-it-possible-to-make-a-tree-view-with-angular and decided to use the offered approach, i.e. to extract repeatable part of the table (row) into a separate template and then render it recursively.

The problem is that HTML table has a pretty strict structure (according to the standard): table > tbody > tr > td and I can't add elements in the middle in order to apply some directives to it.

I've extracted the <tr> into a separate template and applied a ngRepeat to it. In the main template I've applied ngInclude to the tbody. But where do I place a recursive call with additional ngInclude?

To work correctly this ngInclude should be a sibling of <tr>, but I can't make it a sibling cause ngRepeat is applied to the <tr>. So in order to make it work I will need another element (parent to both <tr> and ngInclude) to which I will be able to apply ngRepeat, but HTML standard doesn't provide such an element (there is no element to group rows together beside the tbody and I don't really want to use it multiple times in the table).

Looks like Angular is fighting with HTML in this use-case instead of cooperating and extending it.

It would be possible to solve this if ngRepeat could be used as a separate element like this: <ng-repeat>, but it's an "A" directive only.

Are there any options here I'm not seeing? How should we approach such category of problems? Maybe Angular could be improved to address such issues?

Thanks!

Slava Fomin

unread,
Apr 29, 2015, 2:59:38 PM4/29/15
to ang...@googlegroups.com
Actually, I can't even use <ng-include> inside of the table cause browser will force this foreign element out of the table during DOM rendering.

Slava Fomin

unread,
Apr 29, 2015, 3:22:03 PM4/29/15
to ang...@googlegroups.com
I've created a Plunk: http://plnkr.co/edit/0fkNLfxgQFt5KBcLzvEx?p=preview to demonstrate the issue.

Abhijeet Kadam

unread,
Apr 30, 2015, 3:38:17 AM4/30/15
to ang...@googlegroups.com
http://plnkr.co/edit/KiiloY9wMpEtFBtEa2zG?p=preview
Let me know if this helps

Regards,
Abhijeet

Guilherme Meireles

unread,
Apr 30, 2015, 6:49:45 AM4/30/15
to ang...@googlegroups.com
Hi,

You can process the data to transform the recursive data in an array and then just use ng-repeat as you normally would.

Ex.:
itemTree = [{id: 1, name: 'parent', childList: [{id: 2, name: 'child 1'}, {id: 3, name: 'child 2'}] }
itemList = [{id: 1, name: 'parent', level: 1}, {id: 2, name: 'child 1', level: 2, parentId: 1}, {id: 3, name: 'child 2', level: 2, parentId: 1} ]

Pete Ford

unread,
Mar 11, 2017, 12:38:47 PM3/11/17
to Angular and AngularJS discussion
I'm having a similar problem. I have a list of items, each of which has a name and one or more "category" objects attached; each category has a name and zero or more "comment" objects. I need to render this in a table, so I have this:

<table>
  <thead>
    <th>Item</th>
    <th>Category</th>
    <th>Comment</th>
  </thead>
  <tbody>
    <ng-repeat ng-repeat="item in itemList">
      <ng-repeat ng-repeat="category in item.categoryList">
        <tr ng-repeat="comment in category.commentList">
          <td>{{item.name}}</td>
          <td>{{category.name}}</td>
          <td>{{comment}}</td>
        </tr>
      </ng-repeat>
    </ng-repeat>
  </tbody>
</table>

I expect to see something like this:

Item  Category  Comment
item1 cat1  comment1
item1 cat1 comment2
item1 cat2 comment1
Item2 cat1 comment1
...etc.

This doesn't work; no matter what data is in my list of items, I get an empty table. I can show the nested ng-repeat outside a table works -- this works exactly the way I expected:

    <ng-repeat ng-repeat="item in itemList">
      <ng-repeat ng-repeat="category in item.categoryList">
        <p ng-repeat="comment in category.commentList">
          {{item.name}} - {{category.name}} - {{comment}}
        </p>
      ...

So it looks like you can't use ng-repeat inside a table structure at all, although as far as I can see there's no reason why this wouldn't work. (I'd prefer to use something other than an HTML table, but it's what the client wants so I'm stuck with it.)

I really need to get this working. I'm using AngularJS 1.5.9 at the moment; is this something that's been fixed in a later version?

Guilherme Meireles

unread,
Mar 11, 2017, 5:21:04 PM3/11/17
to Angular and AngularJS discussion
I recommend you try to flatten your structure to create a row list.

Try something like this:

var flattedList = itemList.reduce((state, item) => {
    return state.concat(item.categoryList.reduce((nestedState, category) => {
        var flattedState = category.commentList.map(comment => ({
            item,
            category,
            comment,
        }));
        return nestedState.concat(flattedState)
    }, []));
}, []);

//flattedList: {item,category,comment}[]

<table>
    <thead>
        <th>Item</th>
        <th>Category</th>
        <th>Comment</th>
    </thead>
    <tbody>
        <tr ng-repeat="flatItem in flattedList">
            <td>{{ flatItem.item.name }}</td>
            <td>{{ flatItem.category.name }}</td>
            <td>{{ flatItem.comment }}</td>
        </tr>
    </tbody>
</table>

Pete Ford

unread,
Mar 11, 2017, 5:43:41 PM3/11/17
to ang...@googlegroups.com
I did try that -- I got an Angular exception (something about a potential infinite loop, if I remember correctly). I should point out that my example is a simplification of the real page; in the real thing there are controls to select the items to be included in the top-level list, so the list can change dynamically. I think this was causing some recursion in event handlers, or something like that.

In any case, I'd rather avoid a messy workaround if I can. As I understand it, there's no reason why the ng-repeat tag shouldn't work inside a table so long as it doesn't generate any HTML tags that are invalid inside <tbody> tags. It really should just replicate the markup enclosed in the <ng-repeat>, I thought, but using the Chrome Developer Tools to inspect the page shows that it's generating nothing other than a <!-- ng-repeat .... --> comment inside the <tbody>. I didn't see anything in the documentation to suggest that ng-repeat can't be used inside a table, which to me implies that this is a bug.

--
You received this message because you are subscribed to the Google Groups "Angular and AngularJS discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to angular+unsubscribe@googlegroups.com.
To post to this group, send email to ang...@googlegroups.com.
Visit this group at https://groups.google.com/group/angular.
For more options, visit https://groups.google.com/d/optout.

Pete Ford

unread,
Mar 12, 2017, 10:26:09 AM3/12/17
to Angular and AngularJS discussion
It turns out this is not a bug in Angular... it's actually being caused by weird behaviour in the browser. It doesn't like the <ng-repeat> tags inside <table> so during parsing of HTML it moves them *outside* the <table> in the DOM before Angular fires up. By the time Angular gets to see the page structure, it's too late - the DOM is borked. This isn't something that can be fixed in Angular.

So I guess I can try a couple of things to flatten the structure (I have three approaches I can try) -- it's possible the "recursion" error I was seeing was caused by something I did in the code in my earlier attempts, so I'll give it another shot and see if I can make it work. If not, I'll just have to tell the client that if he wants this display it'll have to be done with something other than a table.
Reply all
Reply to author
Forward
0 new messages