AngularJS集团指令没有外部依赖
我是Angular的新手,想要学习处理问题的最佳方法。 我的目标是有一个可重用的手段来创build组头。 我创build了一个解决scheme,但我认为这应该是一个指令,而不是在我的控制器范围function,但我不知道如何做到这一点,或者如果指令是正确的路要走。 任何投入将不胜感激。
看看我目前的做法在jsFiddle上工作
在HTML中,这是一个使用ng-repeat的简单列表,我在ng-show上调用了我的newGrouping()函数。 该函数将一个引用传递给完整列表,我想要分组的字段以及当前索引。
<div ng-app> <div ng-controller='TestGroupingCtlr'> <div ng-repeat='item in MyList'> <div ng-show="newGrouping($parent.MyList, 'GroupByFieldName', $index);"> <h2>{{item.GroupByFieldName}}</h2> </div> {{item.whatever}} </div> </div> </div>
在我的控制器中,我有我的newGrouping()函数,只是比较当前和前一个,除了第一个项目,并返回true或false取决于匹配。
function TestGroupingCtlr($scope) { $scope.MyList = [ {GroupByFieldName:'Group 1', whatever:'abc'}, {GroupByFieldName:'Group 1', whatever:'def'}, {GroupByFieldName:'Group 2', whatever:'ghi'}, {GroupByFieldName:'Group 2', whatever:'jkl'}, {GroupByFieldName:'Group 2', whatever:'mno'} ]; $scope.newGrouping = function(group_list, group_by, index) { if (index > 0) { prev = index - 1; if (group_list[prev][group_by] !== group_list[index][group_by]) { return true; } else { return false; } } else { return true; } }; }
输出将如下所示。
第1组
- ABC
- 高清
第2组
- GHI
- JKL
- MNO
感觉应该有更好的方法。 我希望这是一个常用的实用function,我可以重用。 这应该是一个指示? 有没有更好的方式来引用列表中的前一个项目比传递完整列表和当前索引的方法? 我将如何处理这个指令?
任何build议,不胜感激。
更新:寻找一个不需要外部依赖的答案。 使用下划线/ lodash或angular度filter模块有很好的解决scheme。
达里尔
这是上面Darryl的解决scheme的一个修改,允许多个参数组合。 另外,它使用$ parse来允许使用嵌套属性作为参数组。
使用多个嵌套参数的示例
HTML
<h1>Multiple Grouping Parameters</h1> <div ng-repeat="item in MyList | orderBy:'groupfield' | groupBy:['groupfield', 'deep.category']"> <h2 ng-show="item.group_by_CHANGED">{{item.groupfield}} {{item.deep.category}}</h2> <ul> <li>{{item.whatever}}</li> </ul> </div>
filter(Javascript)
app.filter('groupBy', ['$parse', function ($parse) { return function (list, group_by) { var filtered = []; var prev_item = null; var group_changed = false; // this is a new field which is added to each item where we append "_CHANGED" // to indicate a field change in the list //was var new_field = group_by + '_CHANGED'; - JB 12/17/2013 var new_field = 'group_by_CHANGED'; // loop through each item in the list angular.forEach(list, function (item) { group_changed = false; // if not the first item if (prev_item !== null) { // check if any of the group by field changed //force group_by into Array group_by = angular.isArray(group_by) ? group_by : [group_by]; //check each group by parameter for (var i = 0, len = group_by.length; i < len; i++) { if ($parse(group_by[i])(prev_item) !== $parse(group_by[i])(item)) { group_changed = true; } } }// otherwise we have the first item in the list which is new else { group_changed = true; } // if the group changed, then add a new field to the item // to indicate this if (group_changed) { item[new_field] = true; } else { item[new_field] = false; } filtered.push(item); prev_item = item; }); return filtered; }; }]);
如果您已经在使用LoDash / Underscore或任何函数库,则可以使用_.groupBy() (或类似名称)函数执行此操作。
在控制器中 :
var movies = [{"movieId":"1","movieName":"Edge of Tomorrow","lang":"English"}, {"movieId":"2","movieName":"X-MEN","lang":"English"}, {"movieId":"3","movieName":"Gabbar Singh 2","lang":"Telugu"}, {"movieId":"4","movieName":"Resu Gurram","lang":"Telugu"}]; $scope.movies = _.groupBy(movies, 'lang');
在模板中 :
<ul ng-repeat="(lang, langMovs) in movies">{{lang}} <li ng-repeat="mov in langMovs">{{mov.movieName}}</li> </ul>
这将呈现 :
英语
- 明天之边缘
- X战警
泰卢固语
- Gabbar Singh 2
- Resu Gurram
更好的是,这也可以很容易地转换成一个filter,没有太多的样板代码来按属性对元素进行分组。
更新:按多个键分组
通常使用多个键分组是非常有用的。 例如,使用LoDash( 来源 ):
$scope.movies = _.groupBy(movies, function(m) { return m.lang+ "-" + m.movieName; });
更新为什么我推荐这种方法:在ng-repeat
/ ng-options
上使用filter会导致严重的性能问题,除非该filter执行得很快。 谷歌的filter性能问题。 你会知道的!
这是我最后决定在ng-repeat内处理分组。 我读了更多关于指令和filter的信息,虽然你可以用这两种方法来解决这个问题,但是过滤方法似乎是一个更好的select。 原因是filter更适合于只有数据需要被操纵的情况。 当需要DOM操作时,指令更好。 在这个例子中,我真的只需要操纵数据,并保持独立的DOM。 我觉得这是最大的灵活性。
看看我最后的方法来在jsFiddle工作的分组。 我还添加了一些表单来演示dynamic添加数据时列表的工作方式。
这是HTML。
<div ng-app="myApp"> <div ng-controller='TestGroupingCtlr'> <div ng-repeat="item in MyList | orderBy:'groupfield' | groupBy:'groupfield'" > <h2 ng-show="item.groupfield_CHANGED">{{item.groupfield}}</h2> <ul> <li>{{item.whatever}}</li> </ul> </div> <form role="form" ng-submit="AddItem()"> <input type="text" data-ng-model="item.groupfield" placeholder="Group"> <input type="text" data-ng-model="item.whatever" placeholder="Item"> <input class="btn" type="submit" value="Add Item"> </form> </div> </div>
这是Javascript。
var app=angular.module('myApp',[]); app.controller('TestGroupingCtlr',function($scope) { $scope.MyList = [ {groupfield: 'Group 1', whatever: 'abc'}, {groupfield: 'Group 1', whatever: 'def'}, {groupfield: 'Group 2', whatever: 'ghi'}, {groupfield: 'Group 2', whatever: 'jkl'}, {groupfield: 'Group 2', whatever: 'mno'} ]; $scope.AddItem = function() { // add to our js object array $scope.MyList.push({ groupfield:$scope.item.groupfield, whatever:$scope.item.whatever }); }; }) /* * groupBy * * Define when a group break occurs in a list of items * * @param {array} the list of items * @param {String} then name of the field in the item from the list to group by * @returns {array} the list of items with an added field name named with "_new" * appended to the group by field name * * @example <div ng-repeat="item in MyList | groupBy:'groupfield'" > * <h2 ng-if="item.groupfield_CHANGED">{{item.groupfield}}</h2> * * Typically you'll want to include Angular's orderBy filter first */ app.filter('groupBy', function(){ return function(list, group_by) { var filtered = []; var prev_item = null; var group_changed = false; // this is a new field which is added to each item where we append "_CHANGED" // to indicate a field change in the list var new_field = group_by + '_CHANGED'; // loop through each item in the list angular.forEach(list, function(item) { group_changed = false; // if not the first item if (prev_item !== null) { // check if the group by field changed if (prev_item[group_by] !== item[group_by]) { group_changed = true; } // otherwise we have the first item in the list which is new } else { group_changed = true; } // if the group changed, then add a new field to the item // to indicate this if (group_changed) { item[new_field] = true; } else { item[new_field] = false; } filtered.push(item); prev_item = item; }); return filtered; }; })
对于我正在使用的应用程序,我将filter设置为整个应用程序中的可重用filter。
我不喜欢的指令方法是,HTML是在指令,所以它不觉得可重用。
我喜欢之前的过滤方法,但是效率并不高,因为列表在遍历过程中必须遍历两次。 我处理长列表,所以这可能是一个问题。 另外,它看起来并不像对上一个项目的简单检查那样直观,看它是否改变了。 另外我想能够很容易地使用多个领域的filter,这个新的filter处理只是通过pipe道再次与另一个字段名称的filter。
对我的groupByfilter的其他评论 – 我意识到,多个分组会导致数组被遍历多次,所以我打算修改它接受字段的多个组的数组,所以它只需要遍历数组一次。
非常感谢投入。 这真的帮助我更多地了解Angular的指令和filter。
干杯,Darryl
下面是一个基于指令的解决scheme,以及一个JSFiddle演示它的链接。 该指令允许每个实例指定它应该分组的项目的字段名称,所以有一个使用两个不同字段的示例。 它的项目数量具有线性运行时间。
的jsfiddle
<div ng-app='myApp'> <div ng-controller='TestGroupingCtlr'> <h1>Grouping by FirstFieldName</h1> <div group-with-headers to-group="MyList" group-by="FirstFieldName"> </div> <h1>Grouping by SecondFieldName</h1> <div group-with-headers to-group="MyList" group-by="SecondFieldName"> </div> </div> </div> angular.module('myApp', []).directive('groupWithHeaders', function() { return { template: "<div ng-repeat='(group, items) in groups'>" + "<h2>{{group}}</h2>" + "<div ng-repeat='item in items'>" + "{{item.whatever}}" + "</div>" + "</div>", scope: true, link: function(scope, element, attrs) { var to_group = scope.$eval(attrs.toGroup); scope.groups = {}; for (var i = 0; i < to_group.length; i++) { var group = to_group[i][attrs.groupBy]; if (group) { if (scope.groups[group]) { scope.groups[group].push(to_group[i]); } else { scope.groups[group] = [to_group[i]]; } } } } }; }); function TestGroupingCtlr($scope) { $scope.MyList = [ {FirstFieldName:'Group 1', SecondFieldName:'Group a', whatever:'abc'}, {FirstFieldName:'Group 1', SecondFieldName:'Group b', whatever:'def'}, {FirstFieldName:'Group 2', SecondFieldName:'Group c', whatever:'ghi'}, {FirstFieldName:'Group 2', SecondFieldName:'Group a', whatever:'jkl'}, {FirstFieldName:'Group 2', SecondFieldName:'Group b', whatever:'mno'} ]; }
AngularJS有三个指示来帮助你显示信息组。 这些指令是ngRepeat,ngRepeatStart和ngRepeatEnd。 我发现了一篇博客文章,展示了如何在AngularJS中展示团体 。 它的要点是这样的:
<body ng-controller="OrdersCtrl"> <div ng-repeat-start="customer in customers" class="header">{{customer.name}}</div> <div ng-repeat="order in customer.orders">{{order.total}} - {{order.description}}</div> <div ng-repeat-end><br /></div> </body>
一旦你学会了如何使用它们,这个命令就非常强大。
JoshMB的代码将无法正确工作,您在同一视图中的同一数据集上有多个filter。 第二次对数据集的过滤版本进行分组时,它将更改原始对象中的相同属性,从而打破之前过滤版本中的分组中断。
我通过添加“CHANGED”属性的名称作为额外的filter参数解决了这个问题。 以下是我的代码更新版本。
/* * groupBy * * Define when a group break occurs in a list of items * * @param {array} the list of items * @param {String} then name of the field in the item from the list to group by * @param {String} then name boolean attribute that indicated the group changed for this filtered version of the set * @returns {array} the list of items with an added field name named with "_new" * appended to the group by field name * * @example <div ng-repeat="item in MyList | filter:'a' | groupBy:'groupfield':'Agroup_CHANGED'" > * <h2 ng-if="item.Agroupfield_CHANGED">{{item.groupfield}}</h2> * <!-- now a differen filtered subset --> * <div ng-repeat="item in MyList | filter:'b' | groupBy:'groupfield':'Bgroup_CHANGED'" > * <h2 ng-if="item.Bgroupfield_CHANGED">{{item.groupfield}}</h2> * * Typically you'll want to include Angular's orderBy filter first */ app.filter('groupBy', ['$parse', function ($parse) { return function (list, group_by, group_changed_attr) { var filtered = []; var prev_item = null; var group_changed = false; // this is a new field which is added to each item where we append "_CHANGED" // to indicate a field change in the list //var new_field = group_by + '_CHANGED'; //- JB 12/17/2013 var new_field = 'group_by_CHANGED'; if(group_changed_attr != undefined) new_field = group_changed_attr; // we need this of we want to group different filtered versions of the same set of objects ! // loop through each item in the list angular.forEach(list, function (item) { group_changed = false; // if not the first item if (prev_item !== null) { // check if any of the group by field changed //force group_by into Array group_by = angular.isArray(group_by) ? group_by : [group_by]; //check each group by parameter for (var i = 0, len = group_by.length; i < len; i++) { if ($parse(group_by[i])(prev_item) !== $parse(group_by[i])(item)) { group_changed = true; } } }// otherwise we have the first item in the list which is new else { group_changed = true; } // if the group changed, then add a new field to the item // to indicate this if (group_changed) { item[new_field] = true; } else { item[new_field] = false; } filtered.push(item); prev_item = item; }); return filtered; }; }]);
编辑:这是一个自定义filter的方法。 Groups
由范围内的filter函数创build,以从当前列表中生成组的数组。 添加/删除列表项将绑定组数组的更新,因为它在每个摘要循环中被重置。
HTML
<div ng-app="myApp"> <div ng-controller='TestGroupingCtlr'> <div ng-repeat='group in getGroups()'> <h2>{{group}}</h2> <ul> <!-- could use another scope variable as predicate --> <li ng-repeat="item in MyList | groupby:group">{{item.whatever}}</li> </ul> </div> </div> </div>
JS
var app=angular.module('myApp',[]); app.filter('groupby', function(){ return function(items,group){ return items.filter(function(element, index, array) { return element.GroupByFieldName==group; }); } }) app.controller('TestGroupingCtlr',function($scope) { $scope.MyList = [{ GroupByFieldName: 'Group 1', whatever: 'abc'}, {GroupByFieldName: 'Group 1',whatever: 'def'}, {GroupByFieldName: 'Group 2',whatever: 'ghi' }, {GroupByFieldName: 'Group 2',whatever: 'jkl'}, {GroupByFieldName: 'Group 2',whatever: 'mno' } ]; $scope.getGroups = function () { var groupArray = []; angular.forEach($scope.MyList, function (item, idx) { if (groupArray.indexOf(item.GroupByFieldName) == -1) groupArray.push(item.GroupByFieldName) }); return groupArray.sort(); } })
DEMO
http://blog.csdn.net/violet_day/article/details/17023219#t2
<!doctype html> <html ng-app> <head> <script src="lib/angular/angular.min.js"></script> <script> function TestCtrl($scope) { $scope.items = [ { id: 0, name: "Red"}, { id: 1, name: "Red"}, { id: 2, name: "Red"}, { id: 3, name: "Red"}, { id: 4, name: "Yellow"}, { id: 5, name: "Orange"} ]; } </script> </head> <body ng-controller="TestCtrl"> <ul ng-repeat="a in items" ng-if="a.name!=items[$index-1].name"> {{ a.name }} <li ng-repeat="b in items" ng-if="a.name==b.name"> {{ b.id }} </li> </ul> </body> </html>
try this: <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Example - example-example58-production</title> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.5/angular.min.js"></script> </head> <body ng-app=""> <script> function Ctrl($scope) { $scope.friends = [{name:'John', phone:'555-1212', age:10}, {name:'Mary', phone:'555-9876', age:19}, {name:'Mike', phone:'555-4321', age:21}, {name:'Adam', phone:'555-5678', age:35}, {name:'John', phone:'555-1212', age:10}, {name:'John', phone:'555-1212', age:10}, {name:'John', phone:'555-1212', age:10}, {name:'John', phone:'555-1212', age:10}, {name:'Julie', phone:'555-8765', age:29}, {name:'Mike', phone:'555-4321', age:21}, {name:'Adam', phone:'555-5678', age:35}, {name:'Mike', phone:'555-4321', age:21}, {name:'Adam', phone:'555-5678', age:35}, {name:'Mike', phone:'555-4321', age:21}, {name:'Adam', phone:'555-5678', age:35}] } </script> <div ng-controller="Ctrl"> <div ng-init="friendx=(friends|orderBy:'age')"> </div> <table class="friend" ng-repeat="friend in friendx"> <tr> <td ng-if="friendx[$index].age!=friendx[$index-1].age">{{friend.age}}</td> </tr> <tr> <td>{{friend.name}}</td> <td>{{friend.phone}}</td> <td>{{friend.age==friendx[$index].age}}</td> </tr> </table> </div> </body>enter code here </html> [http://plnkr.co/edit/UhqKwLx1yo2ua44HjY59?p=preview][1] [1]: http://plnkr.co/edit/UhqKwLx1yo2ua44HjY59?p=preview