angular.js conditional markup in ng-repeat

前端 未结 3 749
遇见更好的自我
遇见更好的自我 2020-12-13 07:37

I\'m using angular.js and (for the sake of argument) bootstrap. Now I need to iterate on \"things\" and display them in \'\'rows\'\' :

相关标签:
3条回答
  • 2020-12-13 08:10

    Why not use something simple like this? http://jsfiddle.net/everdimension/ECCL7/3/

    <div ng-controller="MyCtrl as ctr">
        <div class="row" ng-repeat="project in ctr.projects" ng-if="$index % 3 == 0">
             <h4 class="col-sm-4" ng-repeat="project in ctr.projects.slice($index, $index+3)">
                {{project}}
            </h4>
        </div>
    </div>
    
    0 讨论(0)
  • 2020-12-13 08:27

    I recommend a directive for a couple of reasons:

    • it can be reused and parameterized in the HTML (i.e., "every 3rd thing" can be a directive attribute)
    • it does not require any controller code/$scope properties, and hence it does not require recalculation of controller $scope properties if the "things" array changes in size

    Here is a suggested element directive:

    <row-generator row-data=objects col-count=3></row-generator>
    

    In the implementation I used code similar to your server-side example:

    myApp.directive('rowGenerator', function() {
        var rowTemplate = '<div class="row">',
            colTemplate = '<div class="span4">';
        return {
            restrict: 'E',
            // use '=' for colCount instead of '@' so that we don't
            // have to use attr.$observe() in the link function
            scope: { rowData: '=', colCount: '='},
            link: function(scope, element) {
                // To save CPU time, we'll just watch the overall
                // length of the rowData array for changes.  You
                // may need to watch more.
                scope.$watch('rowData.length', function(value) {
                    var html = rowTemplate;
                    for(var i=0; i < scope.rowData.length; i++) {
                        html += colTemplate + scope.rowData[i].key + '</div>';
                        if (i % scope.colCount == scope.colCount - 1) {
                            html += '</div>' + rowTemplate;
                        }
                    }
                    html += '</div>';
                    // don't use replaceWith() as the $watch 
                    // above will not work -- use html() instead
                    element.html(html);
                })
            }
        }
    });
    

    Data:

    $scope.things = [
        {key: 'one'},
        {key: 'two'},
        {key: 3},
        {key: 4},
        {key: 'five'},
        {key: 'six'},
        {key: 77},
        {key: 8}    
    ];
    

    Fiddle

    To be efficient, the directive as shown only looks for changes to the length of the rowData (i.e., things) array. If you want to have the directive update the view if the value of one of the array elements changes, you'll need a more expensive $watch:

    scope.$watch('rowData', function(value){ ... }, true)
    

    The true at the end does "shallow" dirty checking ("compares the object for equality rather than for reference) -- see docs.

    There's one thing I don't like about the directive -- it needs to know that rowData entries have a property with name key:

    html += colTemplate + scope.rowData[i].key + '</div>';
    
    0 讨论(0)
  • 2020-12-13 08:28

    Edit Nov 12, 2013

    It seems that not only did angular change a little in 1.2, but that there is an even better method. I've created two filters. I tried to combine them into one but got digest errors. Here are the two filters:

    .filter("mySecondFilter", function(){
        return function(input, row, numColumns){
            var returnArray = [];
            for(var x = row * numColumns; x < row * numColumns + numColumns; x++){
                if(x < input.length){
                    returnArray.push(input[x]);                    
                }
                else{
                    returnArray.push(""); //this is used for the empty cells
                }
            }
            return returnArray;   
        }
    })
    .filter("myFilter", function(){
        return function(input, numColumns){
            var filtered = [];
            for(var x = 0; x < input.length; x++){
                if(x % numColumns === 0){
                    filtered.push(filtered.length);
                }
            }
            return filtered;
        }
    });
    

    And now the html will look like this:

    <table border="1">
         <tr data-ng-repeat="rows in (objects | myFilter:numColumns)">
              <td data-ng-repeat="column in (objects | mySecondFilter:rows:numColumns)">{{ column.entry }}</td>
         </tr>  
    </table>
    

    jsFiddle: http://jsfiddle.net/W39Q2/


    Edit Sept 20, 2013

    While working with lots of data that needed dynamic columns I've come up with a better method.

    HTML:

    <table border="1">
        <tr data-ng-repeat="object in (objects | myFilter:numColumns.length)">
            <td data-ng-repeat="column in numColumns">{{ objects[$parent.$index * numColumns.length + $index].entry }}</td>
        </tr>  
    </table>
    

    Javascript:

    $scope.objects = [ ];
    for(var x = 65; x < 91; x++){
        $scope.objects.push({
            entry: String.fromCharCode(x)
        });
    }
    
    $scope.numColumns = [];
    $scope.numColumns.length = 3;
    

    New Filter:

    .filter("myFilter", function(){
        return function(input, columns){
            var filtered = [];
            for(var x = 0; x < input.length; x+= columns){
                 filtered.push(input[x]);   
            }
            return filtered;
        }
    });
    

    This allows it to be dynamic. To change the columns just change the numColumns.length. In the js fiddle you can see I've wired it up to a dropdown.

    jsFiddle: http://jsfiddle.net/j4MPK/


    Your html markup would look like this:

    <div data-ng-repeat="row in rows">
        <div data-ng-repeat="col in row.col">{{col}}</div>
    </div>
    

    And then you could make a variable in your controller like so:

    $scope.rows = [
        {col: [ 1,2,3,4 ]},
        {col: [ 5,6,7 ]},
        {col: [ 9,10,11,12 ]}
    ]; 
    

    This way, you can have any number of columns you want.

    jsfiddle http://jsfiddle.net/rtCP3/39/


    Edit I've modified the fiddle to now support having a flat array of objects:

    jsfiddle: http://jsfiddle.net/rtCP3/41/

    The html now looks like this:

    <div class="row" data-ng-repeat="row in rows">
        <div class="col" data-ng-repeat="col in cols">
            {{objects[$parent.$index * numColumns + $index].entry}}
        </div>
    </div>  
    

    And then in the controller i have:

    $scope.objects = [
        {entry: 'a'},
        {entry: 'b'},
        {entry: 'c'},
        {entry: 'd'},
        {entry: 'e'},
        {entry: 'f'},
        {entry: 'g'},
        {entry: 'h'}    
    ];
    
    $scope.numColumns = 3;
    $scope.rows = [];
    $scope.rows.length = Math.ceil($scope.objects.length / $scope.numColumns);
    $scope.cols = [];
    $scope.cols.length = $scope.numColumns;
    

    The $scope.numColumns variable is used to specify how many columns you want in each row.


    To handle dynamic array size changes, put a watch on the length of the array (not the whole array, that would be redundent)

    $scope.numColumns = 3;  
    $scope.rows = [];    
    $scope.cols = [];    
    $scope.$watch("objects.length", function(){
        $scope.rows.length = Math.ceil($scope.objects.length / $scope.numColumns);
        $scope.cols.length = $scope.numColumns;        
    });
    

    jsfiddle: http://jsfiddle.net/rtCP3/45/

    0 讨论(0)
提交回复
热议问题