27

我是 Angular 的新手,想学习处理问题的最佳方法。我的目标是有一种可重用的方法来按标题创建组。我创建了一个可行的解决方案,但我认为这应该是一个指令,而不是我的控制器中的范围函数,但我不确定如何实现这一点,或者指令是否是正确的方法。任何输入将不胜感激。

查看我目前在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 组

  • 美国广播公司
  • 定义

第 2 组

  • jkl
  • mno

感觉应该有更好的方法。我希望这是一个我可以重用的通用实用程序功能。这应该是一个指令吗?有没有比我传递完整列表和当前索引的方法更好的方法来引用列表中的前一项?我将如何处理这个指令?

任何意见是极大的赞赏。

更新:寻找不需要外部依赖的答案。使用 underscore/lodash 或 angular-filter 模块有很好的解决方案。

达里尔

4

9 回答 9

34

这是对上述 Darryl 解决方案的修改,它允许多个 group by 参数。此外,它利用 $parse 允许使用嵌套属性作为分组参数。

使用多个嵌套参数的示例

http://jsfiddle.net/4Dpzj/6/

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>  

过滤器(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;
    };
}]);
于 2013-12-17T22:22:46.047 回答
24

如果您已经在使用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战警

泰卢固语

  • 加巴尔·辛格 2
  • 瑞苏·古拉姆

更好的是,这也可以很容易地转换为过滤器,而无需太多样板代码来按属性对元素进行分组。

更新:按多个键分组

通常使用多个键进行分组非常有用。例如,使用 LoDash ( source ):

$scope.movies = _.groupBy(movies, function(m) {
    return m.lang+ "-" + m.movieName;
});

更新我推荐这种方法的原因:ng-repeat在/ 上使用过滤器ng-options会导致严重的性能问题,除非该过滤器快速执行。谷歌过滤器性能问题。你会知道的!

于 2014-06-30T18:41:35.807 回答
4

这是我最终决定在 ng-repeat 中处理分组的内容。我阅读了更多关于指令和过滤器的内容,虽然您可以使用其中任何一种来解决这个问题,但过滤器方法似乎是更好的选择。原因是过滤器更适合只需要处理数据的情况。当需要 DOM 操作时,指令会更好。在这个例子中,我真的只需要操作数据并且不理会 DOM。我觉得这提供了最大的灵活性。

请参阅我在jsFiddle上进行分组的最终方法。我还添加了一个小表单来演示动态添加数据时列表的工作方式。

这是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;
    };
})

对于我在其中使用它的应用程序,我将过滤器设置为整个应用程序中的可重用过滤器。

我不喜欢指令方法的地方在于 HTML 在指令中,因此感觉不可重用。

我喜欢以前的过滤器方法,但它似乎效率不高,因为列表必须在任何摘要循环中遍历两次。我处理长列表,所以这可能是一个问题。此外,它看起来不像对前一项进行简单检查以查看它是否更改那样直观。另外,我希望能够轻松地对多个字段使用过滤器,这个新过滤器只需使用另一个字段名称再次管道到过滤器即可处理。

关于我的 groupBy 过滤器的另一条评论——我确实意识到多个分组会导致数组被多次遍历,所以我计划修改它以接受多个 group by 字段的数组,以便它只需要遍历数组一次.

非常感谢您的投入。它确实帮助我更多地了解 Angular 中的指令和过滤器。

干杯,达里尔

于 2013-11-26T03:00:02.360 回答
1

下面是一个基于指令的解决方案,以及演示它的 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'}
  ];
}
于 2013-11-16T06:57:59.147 回答
1

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>

一旦你学会了如何使用它们,它们就会变得非常强大。

于 2014-04-27T14:52:07.360 回答
1

JoshMB 的代码将无法正常工作,因为您在同一视图中的同一数据集上有多个过滤器。第二次对数据集的过滤版本进行分组时,它将更改原始对象中的相同属性,从而打破先前过滤版本中的分组分隔符。

我通过添加“CHANGED”属性的名称作为额外的过滤器参数来解决这个问题。以下是我的代码更新版本。

/*
 * 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;
    };
}]);
于 2014-05-22T13:27:14.160 回答
0

编辑:这是一个自定义过滤器方法。Groups由范围内的过滤器函数创建,以从当前列表生成组数组。添加/删除列表项将绑定组数组的更新,因为它会在每个摘要周期重置。

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

于 2013-11-15T02:29:15.523 回答
0

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>  
于 2014-01-11T11:48:22.543 回答
-2
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
于 2014-04-09T00:43:23.177 回答