3

Thanks in advance for reading. I am trying to utilize angular's ng-repeat to render objects from an array into an Nx3 Table. For the sake of example let's consider a 3x3 table.

Here is a simplified example of the array:

objects = [{"text": "One, One"},{"text": "One, Two"},{"text": "One, Three"},
           {"text": "Two, One"},{"text": "Two, Two"},{"text": "Two, Three"},
           {"text": "Three, One"},{"text": "Three, Two"},{"text": "Three, Three"}];

The "text" field describes where in the 3x3 grid matrix each element should appear. I would like to use ng-repeat on objects to generate html that looks like this:

<table>
  <tr>
    <td>One, One</td>
    <td>One, Two</td>
    <td>One, Three</td>
  </tr>
  <tr>
    <td>Two, One</td>
    <td>Two, Two</td>
    <td>Two, Three</td>
  </tr>
  <tr>
    <td>Three, One</td>
    <td>Three, Two</td>
    <td>Three, Three</td>
  </tr>
</table>

Is there any way to achieve this without needing to break up the array into separate arrays for each row?

Cumulo Nimbus
  • 8,785
  • 9
  • 47
  • 68
  • Maybe this article can be helpfull: http://stackoverflow.com/questions/15973985/using-ng-model-within-nested-ng-repeat-directives – arman1991 Jan 21 '15 at 00:27
  • Hello Cumulo are you trying to list ones twoes and threes or something other than this? – katmanco Jan 21 '15 at 00:51
  • 1
    Like this? http://plnkr.co/edit/BthhwY?p=preview – PSL Jan 21 '15 at 00:52
  • Thanks for the reference @arman1991, but that example is using multiple arrays, one for each row; I would like to use only one array for multiple rows. katmanco the actual text does not matter, I am just trying to convey the idea of where each "text" string object should go – Cumulo Nimbus Jan 21 '15 at 00:56
  • @PSL yes exactly! If you could post that answer with a little explanation behind what you are doing I would be happy to accept it. Thanks! – Cumulo Nimbus Jan 21 '15 at 00:59
  • @CumuloNimbus Sure added an answer, see if it is of any use to you.. – PSL Jan 21 '15 at 01:20

2 Answers2

4

Best possibly way would be to alter your view model in the controller and bind that to ng-repeat (But you already said you do not want to do that). If you ever plan to take that route you can also take a look at user @m59 answer where he creates a reusable filter to do it. However this is just a simple answer making use of built in filter's configurable evaluation expression where we can return truthy/falsy value to determine if they need to be repeated or not. This eventually has the only advantage of no need to create 2 ng-repeat blocks (But that is not so bad though). So in your controller add a function on the scope,

$scope.getFiltered= function(obj, idx){
   //Set a property on the item being repeated with its actual index
   //return true only for every 1st item in 3 items
    return !((obj._index = idx) % 3); 
}

and in your view apply the filter:

  <tr ng-repeat="obj in objects | filter:getFiltered">
    <!-- Current item, i.e first of every third item -->
    <td>{{obj.text}}</td> 
    <!-- based on the _index property display the next 2 items (which have been already filtered out) -->
    <td>{{objects[obj._index+1].text}}</td>
    <td>{{objects[obj._index+2].text}}</td>
  </tr>

Plnkr

Community
  • 1
  • 1
PSL
  • 123,204
  • 21
  • 253
  • 243
  • Thanks for the clear solution. I would be curious to know more why "The most reliable and technically correct approach is to transform the data in the controller" and not using a filtering method like implemented here. As long as is avoided within the ng-repeat this filtering method seems equivalent (if not cleaner) than the other approach. – Cumulo Nimbus Jan 21 '15 at 15:13
  • One major reason is performance, when you add a filter in the view, it is always expensive because filters atleast run twice every digest cycle and if you are transforming data in the filter them imagine you are doing it every digest cycle. Also another thing is views are generally hard to test against while doing unit tests, So it is always easy to test against the controller than the view. Another thing is you do not need to guess where and how the data is transformed because you are already doing it in the controller and it is pretty much straight forward and visible. – PSL Jan 21 '15 at 15:16
  • 1
    That's a lot of reasons haha, and totally makes sense. Thanks – Cumulo Nimbus Jan 21 '15 at 15:28
0

I wanted to do the exact same thing.

Convert an array into a matrix/ grid

I have an array which i wanted to convert into a grid/matrix of column size 4. the following implementation worked for me. You can use the two counters : row and col as you like in side the nested ng-repeat

In my case number of columns is 3. But you can replace that 3 with a variable everywhere. h.seats is my array of the objects and i want to print either X or - based on value of element in that array

<div class="table-responsive">
    <table class="table table-bordered">
        <thead>
            <tr>
                <th ng-repeat="n in [].constructor(3 + 1) track by $index">{{$index}}</th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="(row, y) in getNumber(h.seats.length, 3) track by $index">
                <td>{{row+1}}</td>
                <td class="text-primary"
                    ng-repeat="(col, t) in h.seats track by $index"
                    ng-if="col >= (row)*3 && col < (row+1)*3">
                        <span ng-show="t.status"> X </span> 
                        <span ng-show="!t.status"> - </span>
                </td>
            </tr>
        </tbody>
    </table>
</div>

<th ng-repeat="n in [].constructor(3 + 1) track by $index">{{$index}}</th> prints the header row with column number at the top. getNumber(h.seats.length, 3) returns me the number of rows of that table as follows

.controller('CustomViewController', function ($scope, Principal, $state) {

    $scope.getNumber = function(length, columns) {
        return new Array(parseInt(length / columns + 1, 10));   
    }

The line ng-if="col >= (row)*3 && col < (row+1)*3" is important logic to calculate which elements should be put in that row. The output looks like below

0  1  2  3 
1  e1 e2 e3 
2  e4 e5 e6 
3  e7 e8 

Refer to following link for details of how row and col counters are used: https://stackoverflow.com/a/35566132/5076414

Community
  • 1
  • 1
Sacky San
  • 1,535
  • 21
  • 26