3

Say I have the following data structure

{
    'Key 1': {
        'Value 1': ['a', 'b', 'c'],
        'Value 2': ['d', 'e']
    },
    'Key 2': {
        'Value 3': ['f'],
        'Value 4': ['g', 'h']
    }
}

How, with AngularJS, can I render it in a table similar to the following:

|-------|---------|---|
| Key 1 | Value 1 | a |
|       |         |---|
|       |         | b |
|       |         |---|
|       |         | c |
|       |---------|---|
|       | Value 2 | d |
|       |         |---|
|       |         | e |
|-------|---------|---|
| Key 2 | Value 3 | f |
|       |---------|---|
|       | Value 4 | g |
|       |         |---|
|       |         | h |
|-------|---------|---|

The keys are done via rowspan.

2
  • Can you post actual data object? Commented Oct 25, 2014 at 19:45
  • Alright. See the edit. Commented Oct 25, 2014 at 19:50

3 Answers 3

4

If you really, really need to do it with rowspans this is a way to do it, it's beyond tricky and almost impossible to read/follow unless you are the author (sorry about that), but it works. You just need the support of a couple $filters

Like this:

angular.module('testApp', [])
.controller('testController', function ($scope) {
    $scope.testData = {
        'Key 1': {
            'Value 1': ['a', 'b', 'c'],
            'Value 2': ['d', 'e']
        },
        'Key 2': {
            'Value 3': ['f'],
            'Value 4': ['g', 'h']
        }
    };
})
.filter('nNestedElements', function(){
    var nNestedElements = function(collection, currentLevel, stopLevel){
        var total = 0;
        if(stopLevel==currentLevel){
            if(Object.prototype.toString.call(collection) === '[object Array]')
                total += collection.length;
            else
                total += Object.keys(collection);
        }else{
            angular.forEach(collection, function(value){
                total += nNestedElements(value, currentLevel+1, stopLevel);                
            });
        }
        return total;
    };
    return function(object, level){                
        return nNestedElements(object, 0, level);
    }
})
.filter('objectKeys', function(){
    return function(object){
        return Object.keys(object);
    };
});

View:

<table ng-app="testApp" ng-controller="testController">
    <tr ng-repeat-start="(key, val) in testData">
        <td rowspan="{{val|nNestedElements:1}}">{{key}}</td>
        <td rowspan="{{val[(val|objectKeys)[0]].length}}">{{(val|objectKeys)[0]}}</td>
        <td>{{ val[(val|objectKeys)[0]][0]}}</td>
    </tr>
    <tr ng-repeat="val2 in val[(val|objectKeys)[0]].slice(1)">
        <td>{{val2}}</td>
    </tr>
    <tr ng-repeat-start="subkey in (val|objectKeys).slice(1)">
        <td rowspan="{{val[subkey].length}}">{{subkey}}</td>
        <td>{{ val[subkey][0] }}</td>
    </tr>
    <tr ng-repeat="value3 in val[subkey].slice(1)" ng-repeat-end>        
        <td>{{ value3 }}</td>
    </tr>
    <tr ng-repeat-end ng-if="false" ><td></td></tr>
</table>

Example

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

1 Comment

Your solution is pretty cool! Mine is more like workaround, yours actually builds all rowspans. I'm impressed! This should be accepted answer!
4

It would be really cool to render this data structure with rowspan's. However I'm not sure it is going to be easy to do, even using ng-repeat-start/end directives. Looks like much simpler to go with table and a few nested table/divs. In this case markup stays relatively simple:

<table>
    <tr ng-repeat="(key, value) in data">
        <td>{{key}}</td>
        <td class="inner">
            <table>
                <tr ng-repeat="(skey, svalue) in value">
                    <td>{{skey}}</td>
                    <td class="inner">
                        <div ng-repeat="val in svalue">{{val}}</div>
                    </td>
                </tr>
            </table>
        </td>
    </tr>
</table>

Demo: http://plnkr.co/edit/4LCr4PUcZn95WUKxlUMk?p=preview

3 Comments

I like this solution, although I'm using Bootstrap, so it messes up the layout! Thanks anyway!
Well even if you use Bootstrap you can always style the table whatever you want. See I updated demo now it uses Bootstrap.
@dfsq +1, the way that you are doing it it's by far cleaner and easier to read than my solution... But I found a way to make it work with rowspans and ng-repeat-start/end. Not that I would like to have that piece of code in a real app, but it was fun to find the hack, you should check it out. ;)
0

YOu will have to change the data structure of the data you are having. Here I parsed the data and amid fetched the information of how much rowspan I will need in td during ng-repeat over tr

angular.module('app', [])
.controller('testCtrl', ['$scope', function($scope){
  $scope.item = {
    "main1": {
      "proj1": ["comp1", "comp2"],
      "proj2": ["comp3", "comp4", "comp5"]
    },
    "main2": {
      "proj3": ["comp1", "comp2"],
      "proj4": ["comp3", "comp4"],
      "proj5": ["comp1"]
    }
  };
  var parsedDs = [];
  var currRow = [];
  for(var mainKey in $scope.item){
    var main = $scope.item[mainKey];
    var mainDs = {
      val: mainKey,
      span: 0
    }
    currRow.push(mainDs);
    for(var projKey in main){
      var proj = main[projKey];
      var projDs = {
        val: projKey,
        span: 0
      }
      currRow.push(projDs);
      for(var compKey in proj){
        mainDs.span++;
        projDs.span++;
        var comp = proj[compKey];
        currRow.push({
          val: comp,
          span: 1
        });
        parsedDs.push(currRow);
        currRow = [];
      }
    }
  }
  $scope.suitableDs = parsedDs;
}])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.9/angular.min.js"></script>
<div ng-app='app' ng-controller='testCtrl'>
  <table border="1">
    <tr>
      <th width="100">Main</th>
      <th width="100">Project</th>
      <th width="100">Company</th>
    </tr>
    <tr ng-repeat="row in suitableDs">
      <td ng-repeat="col in row" rowspan="{{col.span}}">
        {{ col.val }}
      </td>
    </tr>
  </table>
</div>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.