如何使用引导程序将ng-repeat数据分为三列


122

我在我的代码中使用ng-repeat我有基于ng-repeat的n个文本框。我想将文本框与三列对齐。

这是我的代码

<div class="control-group" ng-repeat="oneExt in configAddr.ext">
    {{$index+1}}. 
    <input type="text" name="macAdr{{$index+1}}" 
           id="macAddress" ng-model="oneExt.newValue" value=""/>
</div>

1
所以假设您有九(9)行或一列想要1,2,3?
Gabriele Petrioli 2014年

Answers:


254

最可靠,技术上最正确的方法是在控制器中转换数据。这是一个简单的块函数和用法。

function chunk(arr, size) {
  var newArr = [];
  for (var i=0; i<arr.length; i+=size) {
    newArr.push(arr.slice(i, i+size));
  }
  return newArr;
}

$scope.chunkedData = chunk(myData, 3);

然后您的视图将如下所示:

<div class="row" ng-repeat="rows in chunkedData">
  <div class="span4" ng-repeat="item in rows">{{item}}</div>
</div>

如果您在中有任何输入ng-repeat,则可能需要在修改数据或提交数据后取消整理/重新加入数组。这是在中的外观$watch,以便始终以原始合并格式提供数据:

$scope.$watch('chunkedData', function(val) {
  $scope.data = [].concat.apply([], val);
}, true); // deep watch

许多人喜欢在视图中使用过滤器来完成此操作。这是可能的,但只能用于显示目的!如果在此过滤后的视图中添加输入,将导致可以解决的问题,但不是很漂亮或不可靠

该过滤器的问题在于它每次都会返回新的嵌套数组。Angular正在监视过滤器的返回值。过滤器第一次运行时,Angular会知道该值,然后再次运行它以确保完成更改。如果两个值相同,则循环结束。如果不是这样,过滤器将一次又一次触发,直到它们相同为止,否则Angular意识到正在发生无限摘要循环并关闭。由于Angular以前没有跟踪新的嵌套数组/对象,因此它总是将返回值视为与以前的值不同。要修复这些“不稳定”的过滤器,必须将过滤器包装在一个memoize函数中。lodash具有memoize功能,而lodash的最新版本也包含chunk功能,因此我们可以使用以下方法非常简单地创建此过滤器npm模块并使用browserify或编译脚本webpack

记住:仅显示!如果使用输入,请过滤控制器!

安装lodash:

npm install lodash-node

创建过滤器:

var chunk = require('lodash-node/modern/array/chunk');
var memoize = require('lodash-node/modern/function/memoize');

angular.module('myModule', [])
.filter('chunk', function() {
  return memoize(chunk);
});

这是带有此过滤器的示例:

<div ng-repeat="row in ['a','b','c','d','e','f'] | chunk:3">
  <div class="column" ng-repeat="item in row">
    {{($parent.$index*row.length)+$index+1}}. {{item}}
  </div>
</div>

垂直订购商品

1  4
2  5
3  6

对于垂直列(从上到下)而不是水平列(从左到右),确切的实现取决于所需的语义。划分不均匀的列表可以以不同的方式分布。这是一种方法:

<div ng-repeat="row in columns">
  <div class="column" ng-repeat="item in row">
    {{item}}
  </div>
</div>
var data = ['a','b','c','d','e','f','g'];
$scope.columns = columnize(data, 3);
function columnize(input, cols) {
  var arr = [];
  for(i = 0; i < input.length; i++) {
    var colIdx = i % cols;
    arr[colIdx] = arr[colIdx] || [];
    arr[colIdx].push(input[i]);
  }
  return arr;
}

但是,获取列的最直接,最简单的方法是使用CSS列

.columns {
  columns: 3;
}
<div class="columns">
  <div ng-repeat="item in ['a','b','c','d','e','f','g']">
    {{item}}
  </div>
</div>

谢谢你的帮助。但是使用带有角度的ui图表的轮播无法绘制图表bt轮播效果很好。我的代码是<carousel interval =“ myInterval”> <slide ng-repeat =“ mileStone中的里程碑| split:4” active =“ slide.active”> <div class =“ col-md-3” ng-repeat =“里程碑中的milestone1“> <div style =” text-align:center;“> <div ui-chart =” data“ chart-options =” chartOptions“> </ div> </ div> </ div> </ slide > </ carousel>然后,我将范围变量初始化为里程碑JSON数据。
kuppu 2014年

1
@ m59现在,此功能在第一行a b c第二行显示数据d e f。我可以在第二列和第三列的a b 第一列c d中显示e f吗?谢谢

1
@ Ifch0o1如果那是正确的[].concat.call([], val)。您必须考虑做什么applyval是一个数组数组,我们希望将其中的每个数组作为参数传递给concat。我认为您正在关注的上下文[],这不是重点。我们想要的是[].concat(val[1], val[2], val[3], etc),所以我们需要apply([], val)
m59,2015年

1
@bahadir我更新了答案,使其更加清晰。看看如果您制作一个仅返回[]vs [[]]和的过滤器会发生什么[{}]。嵌套的数组/对象使Angular在每个上看到不同的结果filter,并不断重复查找结果以保持相同(稳定)。
2015年

1
您确实是在垃圾评论:)自己尝试一下。var x = [1,2,3]; var y = [1,2,3]; console.log(x === y);深度检查意味着对字符串进行字符串化和比较(由于对象可能无法字符串化而存在风险),或者意味着递归地检查每个属性。我认为Angular 可以为过滤器实现该功能,但是我敢肯定他们没有这样做是有充分理由的。可能是因为它们主要是为了简化数据集的修改而设计的过滤器,而不是对结构的更改进行彻底的检查。
2015年

64

这个解决方案很简单:

JSON:

[{id:"1",name:"testA"},{id:"2",name:"test B"},{id:"3",name:"test C"},{id:"4",name:"test D"},{id:"5",name:"test E"}]

HTML:

<div ng-controller="MyCtrl">
  <table>
    <tr ng-repeat="item in items" ng-switch on="$index % 3">
      <td ng-switch-when="0">
        {{items[$index].id}} {{items[$index].name}}
      </td>
      <td ng-switch-when="0">
        <span ng-show="items[$index+1]">
          {{items[$index+1].id}} {{items[$index+1].name}}
        </span>
      </td>
      <td ng-switch-when="0">
        <span ng-show="items[$index+2]">    
          {{items[$index+2].id}} {{items[$index+2].name}}
        </span>
      </td>
    </tr>
  </table>
</div>

演示在FIDDLE

在此处输入图片说明


3
最佳答案!并执行本机指令所需的操作!恭喜!
雷南弗兰卡

1
此解决方案有效,但是就最佳实践而言,您可能不应该使用表来定义标记中的布局:)
Dave Cooper

2
正如@DaveCooper提到的,这是不带表的替代布局:plnkr.co/edit/81oj8mHaNyfQpCmRBWO4?p=preview。考虑使用表中的表格数据,但是,对于移动设备和可变的屏幕尺寸,这是更好的布局。
Zymotik '16

1
到目前为止,这是最好的解决方案。使用新框架时,找到所有怪癖需要花费时间,但是ng-switch是天赐之物
Zoran

如果您要使用div而不是表格,则可以ng-switch-when="0"在外部div上放置一次,而不是放在所有3个项目上。
汉斯·沃特斯

19

干净,适应性强的解决方案 不需要数据处理

HTML:

<div class="control-group" class="label"
    ng-repeat="oneExt in configAddr.ext"
    ng-class="{'new-row': startNewRow($index, columnBreak) }">
    {{$index+1}}. 
    <input type="text" name="macAdr{{$index+1}}" 
       id="macAddress{{$index}}" ng-model="oneExt.newValue" />
</div>

CSS:

.label {
    float: left;
    text-align: left;
}
.new-row {
    clear: left;
}

JavaScript:

$scope.columnBreak = 3; //Max number of colunms
$scope.startNewRow = function (index, count) {
    return ((index) % count) === 0;
};

这是一种简单的解决方案,可以动态地将数据呈现为行和列,而无需操纵您要显示的数据数组。另外,如果尝试调整浏览器窗口的大小,则可以看到网格动态地适应屏幕/ div的大小。

(我还为您的ID添加了一个{{$ index}}后缀,因为ng-repeat会尝试创建多个具有相同ID的元素(如果不这样做)。)

一个类似的工作例子


1
我相信OP希望对Twitter Bootstrap行/列进行标记,但这可能对某人有所帮助。但是,您需要在答案中放入代码和说明。仅链接答案将被删除。
m59 2015年

@ m59更新了答案。感谢您的建议
Cumulo Nimbus 2015年

1
大+1 不需要数据操作。这非常适合我想要使用ng-repeat将跨度中的数据分为2列的需求,因此我的Bootstrap模版不会太长!我很惊讶这实现起来多么简单!<span ng-repeat="z in waiverModalLinks" ng-class="{'new-row':startNewRow($index, columnBreak)}" class="{{z.id}}"><a href="{{z.link}}{{z.title}}">{{z.title}}</a></span>那是在“模态主体”内部。
Christine268

15

m59的答案很好。我不喜欢的一件事是它使用div s表示可能是表的数据。

因此,结合m59的过滤器(在上面的某处回答),这里是如何在表格中显示它。

<table>
    <tr class="" ng-repeat="rows in foos | chunk:2">
        <td ng-repeat="item in rows">{{item}}</td>
    </tr>
</table>

7

以下是更简单的方法:

<table>
    <tr ng-repeat="item in lists" ng-hide="$index%2!==0">
        <td>
            <label>{{ lists[$index].name}}</label>
        </td>
        <td ng-hide="!lists[$index+1]">
            <label>{{ lists[$index+1].name}}</label>
        </td>
    </tr>
</table>

Cumulo Nimbus的答案对我很有用,但是我希望这个由div包裹的网格可以在列表过长时显示滚动条。

为了实现这一点,我style="height:200px; overflow:auto"在表格周围添加了一个div,使它显示为单列。

现在适用于数组长度为1的数组。


当数组中只有一项时,它将不会显示第一行。修复此更改 ng-hide="$index%2!==0"
Anuj Pandey

4

我有一个功能并将其存储在服务中,因此可以在我的应用程序中使用它:

服务:

app.service('SplitArrayService', function () {
return {
    SplitArray: function (array, columns) {
        if (array.length <= columns) {
            return [array];
        };

        var rowsNum = Math.ceil(array.length / columns);

        var rowsArray = new Array(rowsNum);

        for (var i = 0; i < rowsNum; i++) {
            var columnsArray = new Array(columns);
            for (j = 0; j < columns; j++) {
                var index = i * columns + j;

                if (index < array.length) {
                    columnsArray[j] = array[index];
                } else {
                    break;
                }
            }
            rowsArray[i] = columnsArray;
        }
        return rowsArray;
    }
}

});

控制器:

$scope.rows   = SplitArrayService.SplitArray($scope.images, 3); //im splitting an array of images into 3 columns

标记:

 <div class="col-sm-12" ng-repeat="row in imageRows">
     <div class="col-sm-4" ng-repeat="image in row">
         <img class="img-responsive" ng-src="{{image.src}}">
     </div>
 </div>

虽然这很酷,但在使用其他过滤器(例如orderBy或filter)时却无济于事。
罗比·史密斯

2

我的方法是多种多样的。

我的目标是让网格适应屏幕尺寸。我想要3列lg,对于sm和分别需要2列md和1列xs

首先,我使用角度$window服务创建了以下范围函数:

$scope.findColNumberPerRow = function() {
    var nCols;
    var width = $window.innerWidth;

    if (width < 768) {
        // xs
        nCols = 1;
    }
    else if(width >= 768 && width < 1200) {
         // sm and md
         nCols = 2
    } else if (width >= 1200) {
        // lg
        nCols = 3;
    }
    return nCols;
};

然后,我使用了@Cumulo Nimbus提出的课程:

.new-row {
    clear: left;
}

本页中所述ng-repeat,在包含的div中,我添加了resizable指令,以便每次调整窗口大小时,使用新值更新angular 服务。$window

最终,在重复的div中,我得到了:

ng-repeat="user in users" ng-class="{'new-row': ($index % findColNumberPerRow() === 0) }"

请让我知道这种方法的任何缺陷。

希望对您有所帮助。


2

Bootstrap建议使用“ clearfix” CSS的简单技巧:

<div class="row">
      <div ng-repeat-start="value in values" class="col-md-4">
        {{value}}
      </div>
      <div ng-repeat-end ng-if="$index % 3 == 0" class="clearfix"></div>
</div>

许多优点:高效,快速,使用Boostrap建议,避免可能的$ digest问题以及不更改Angular模型。


1

这个例子产生了一个嵌套的转发器,其中外部数据包括一个内部数组,我想在两列中列出。对于三列或更多列,该概念将适用。

在内部栏中,我重复“行”直到达到限制。通过将项目数组的长度除以所需的列数(在这种情况下为2)来确定限制。除法位于控制器上,并以当前数组长度作为参数传递。然后,JavaScript slice(0,array.length / columnCount)函数将限制应用于转发器。

然后调用第二个列中继器,并重复slice(array.length / columnCount,array.length),这将在第二列中生成数组的后半部分。

<div class="row" ng-repeat="GroupAccess in UsersController.SelectedUser.Access" ng-class="{even: $even, odd: $odd}">
    <div class="col-md-12 col-xs-12" style=" padding-left:15px;">
        <label style="margin-bottom:2px;"><input type="checkbox" ng-model="GroupAccess.isset" />{{GroupAccess.groupname}}</label>
    </div>
    <div class="row" style=" padding-left:15px;">
        <div class="col-md-1 col-xs-0"></div>
        <div class="col-md-3 col-xs-2">
            <div style="line-height:11px; margin-left:2px; margin-bottom:2px;" ng-repeat="Feature in GroupAccess.features.slice(0, state.DivideBy2(GroupAccess.features.length))">
                <span class="GrpFeature">{{Feature.featurename}}</span>
            </div>
        </div>
        <div class="col-md-3 col-xs-2">
            <div style="line-height:11px; margin-left:2px; margin-bottom:2px;" ng-repeat="Feature in GroupAccess.features.slice( state.DivideBy2(GroupAccess.features.length), GroupAccess.features.length )">
                <span class="GrpFeature">{{Feature.featurename}}</span>
            </div>
        </div>
        <div class="col-md-5 col-xs-8">
        </div>
    </div>
</div>


// called to format two columns
state.DivideBy2 = function(nValue) {
    return Math.ceil(nValue /2);
};

希望这有助于以另一种方式看待解决方案。(PS这是我在这里的第一篇文章!:-))


1

我没有.row修复

<div class="col col-33 left" ng-repeat="photo in photos">
   Content here...
</div>

和CSS

.left {
  float: left;
}

1

这是一种简单易行的方法。它更具手动性,并且最终会出现混乱的标记。我不建议这样做,但是在某些情况下这可能有用。

这是一个小提琴链接http://jsfiddle.net/m0nk3y/9wcbpydq/

HTML:

<div ng-controller="myController">
    <div class="row">
        <div class="col-sm-4">
            <div class="well">
                <div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3)">
                {{ person.name }}
                </div>
            </div>
        </div>
        <div class="col-sm-4">
            <div class="well">
                <div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3):Math.ceil(data.length/3)">
                {{ person.name }}
                </div>
            </div>
        </div>
        <div class="col-sm-4">
            <div class="well">
                <div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3):Math.ceil(data.length/3)*2">
                {{ person.name }}
                </div>
            </div>
        </div>
    </div>
</div>

JS:

var app = angular.module('myApp', []);

app.controller('myController', function($scope) {

    $scope.Math = Math;
    $scope.data = [
        {"name":"Person A"},
        ...
    ];
});

此设置要求我们在标记:/上使用一些数学运算,因此您需要通过添加以下行来注入数学运算: $scope.Math = Math;


1

所有这些答案似乎都过度设计。

到目前为止,最简单的方法是在col-md-4列引导程序中设置输入div,然后由于引导程序的12列性质,引导程序将自动将其格式化为3列:

<div class="col-md-12">
    <div class="control-group" ng-repeat="oneExt in configAddr.ext">
        <div class="col-md-4">
            <input type="text" name="macAdr{{$index}}"
                   id="macAddress" ng-model="oneExt.newValue" value="" />
        </div>
    </div>
</div>

1
<div class="row">
  <div class="col-md-4" ng-repeat="remainder in [0,1,2]">
    <ul>
      <li ng-repeat="item in items" ng-if="$index % 3 == remainder">{{item}}</li>
    </ul>
  </div>
</div>

0

基于m59的非常好的答案。我发现如果更改模型输入,它们将变得模糊,因此您一次只能更改一个字符。这是一个新的对象列表,可用于需要它的任何人:

编辑已更新,可在一页上处理多个过滤器

app.filter('partition', function() {
  var cache = {}; // holds old arrays for difference repeat scopes
  var filter = function(newArr, size, scope) {
    var i,
      oldLength = 0,
      newLength = 0,
      arr = [],
      id = scope.$id,
      currentArr = cache[id];
    if (!newArr) return;

    if (currentArr) {
      for (i = 0; i < currentArr.length; i++) {
        oldLength += currentArr[i].length;
      }
    }
    if (newArr.length == oldLength) {
      return currentArr; // so we keep the old object and prevent rebuild (it blurs inputs)
    } else {
      for (i = 0; i < newArr.length; i += size) {
        arr.push(newArr.slice(i, i + size));
      }
      cache[id] = arr;
      return arr;
    }
  };
  return filter;
}); 

这就是用法:

<div ng-repeat="row in items | partition:3:this">
  <span class="span4">
    {{ $index }}
  </span>
</div>

据我所知,这在大多数情况下都应该起作用,但是缓存逻辑看起来并不完全可靠,并且传递范围很尴尬。我真的建议仅在需要输入时才在控制器内进行过滤。无论如何,这是技术上正确的方法。
2014年

0

我是bootstrap和angularjs的新手,但是这也可以使每4个数组作为一组,结果几乎像3列。此技巧使用自举中断线原理。

<div class="row">
 <div class="col-sm-4" data-ng-repeat="item in items">
  <div class="some-special-class">
   {{item.XX}}
  </div>
 </div>
</div>

0

Lodash现在内置了一个块方法,我个人使用:https ://lodash.com/docs#chunk

基于此,控制器代码可能如下所示:

$scope.groupedUsers = _.chunk( $scope.users, 3 )

查看代码:

<div class="row" ng-repeat="rows in groupedUsers">
  <div class="span4" ng-repeat="item in rows">{{item}}</div>
</div>

0

另一种方法是将width:33.33%; float:left包装器div 设置如下:

<div ng-repeat="right in rights" style="width: 33.33%;float: left;">
    <span style="width:60px;display:inline-block;text-align:right">{{$index}}</span>
    <input type="number" style="width:50px;display:inline-block" ">
</div>

0

我发现自己处于类似情况,想要生成每个3列的显示组。但是,尽管我正在使用引导程序,但是我试图将这些组划分为不同的父div。我还想做一些通用的事情。

我用2 ng-repeat来处理它,如下所示:

<div ng-repeat="items in quotes" ng-if="!($index % 3)">
  <div ng-repeat="quote in quotes" ng-if="$index <= $parent.$index + 2 && $index >= $parent.$index">
                ... some content ...
  </div>
</div>

这使得更改为不同数量的列并分成几个父div非常容易。


0

万一有人想要Angular 7(及更高版本),以下是我在一个应用程序中使用的示例:

HTML:

                   <div class="container-fluid">
                    <div class="row" *ngFor="let reports of chunkData; index as i">
                        <div *ngFor="let report of reports" class="col-4 col-sm-4"
                            style="border-style:solid;background-color: antiquewhite">{{report}}</div>
                    </div>
                </div>

.TS文件

export class ConfirmationPageComponent implements OnInit {
  chunkData = [];
  reportsArray = ["item 1", "item 2", "item 3", "item 4", "item 5", "item 6"];
  
  constructor() {}

  ngOnInit() {
    this.chunkData = this.chunk(this.reportsArray, 3);
  }

  chunk(arr, size) {
    var newArr = [];
    for (var i = 0; i < arr.length; i += size) {
      newArr.push(arr.slice(i, i + size));
    }
    return newArr;
  }
}

这是一个很棒的解决方案,用于根据您从数据库中迭代的项目数来动态创建新的列/行。谢谢!


-1

这回答了原始问题,即如何在列中获得1,2,3。–由kuppu询问,2014年2月8日,13:47

angularjs代码:

function GetStaffForFloor(floor) {
    var promiseGet = Directory_Service.getAllStaff(floor);
    promiseGet.then(function (pl) {
        $scope.staffList = chunk(pl.data, 3); //pl.data; //
    },
    function (errorOD) {
        $log.error('Errored while getting staff list.', errorOD);
    });
}
function chunk(array, columns) {
    var numberOfRows = Math.ceil(array.length / columns);

    //puts 1, 2, 3 into column
    var newRow = []; //array is row-based.
    for (var i = 0; i < array.length; i++) {
        var columnData = new Array(columns);
        if (i == numberOfRows) break;
        for (j = 0; j < columns; j++)
        {
            columnData[j] = array[i + numberOfRows * j];
        }
        newRow.push(columnData); 
    }
    return newRow;

    ////this works but 1, 2, 3 is in row
    //var newRow = [];
    //for (var i = 0; i < array.length; i += columns) {
    //    newRow.push(array.slice(i, i + columns)); //push effectively does the pivot. array is row-based.
    //}
    //return newRow;
};

查看代码(注意:使用引导程序3):

<div class="staffContainer">
    <div class="row" ng-repeat="staff in staffList">
        <div class="col-md-4" ng-repeat="item in staff">{{item.FullName.length > 0 ? item.FullName + ": Rm " + item.RoomNumber : ""}}</div>
    </div>
</div>

-1

此代码将有助于在lg和md模式下将元素与三列对齐,在sm模式下将两列对齐,而将xs模式作为单列

<div class="row">
<div ng-repeat="oneExt in configAddr.ext">
    <div class="col-xs-12 col-sm-6 col-md-4 col-lg-4">
        {$index+1}}. 
        <input type="text" name="macAdr{{$index+1}}" 
       id="macAddress" ng-model="oneExt.newValue" value=""/>
    </div>
</div>

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.