Как я могу сгруппировать данные с угловым фильтром?

136

У меня есть список игроков, которые принадлежат к группе каждый. Как я могу использовать фильтр для отображения списка пользователей в группе?

[{name: 'Gene', team: 'team alpha'},
 {name: 'George', team: 'team beta'},
 {name: 'Steve', team: 'team gamma'},
 {name: 'Paula', team: 'team beta'},
 {name: 'Scruath of the 5th sector', team: 'team gamma'}];

Я ищу этот результат:

  • команда альфа
    • Ген
  • командная бета
    • Джордж
    • Паула
  • командная гамма
    • Стив
    • Scruath 5-го сектора
Бенни Боттема
источник

Ответы:

182

Вы можете использовать groupBy модуля angular.filter .
так что вы можете сделать что-то вроде этого:

JS:

$scope.players = [
  {name: 'Gene', team: 'alpha'},
  {name: 'George', team: 'beta'},
  {name: 'Steve', team: 'gamma'},
  {name: 'Paula', team: 'beta'},
  {name: 'Scruath', team: 'gamma'}
];

HTML:

<ul ng-repeat="(key, value) in players | groupBy: 'team'">
  Group name: {{ key }}
  <li ng-repeat="player in value">
    player: {{ player.name }} 
  </li>
</ul>

РЕЗУЛЬТАТ:
Название группы: альфа
* игрок: Джин
Имя группы: бета
* игрок: Джордж
* игрок: Паула
Название группы: гамма
* игрок: Стив
* игрок: Скраат

ОБНОВЛЕНИЕ: jsbin Запомните основные требования к использованию angular.filter, особенно обратите внимание, что вы должны добавить его к зависимостям вашего модуля:

(1) Вы можете установить угловой фильтр, используя 4 различных метода:

  1. клонировать и построить этот репозиторий
  2. через Bower: запустив $ bower, установите angular-filter со своего терминала
  3. через npm: запустив $ npm, установите angular-filter со своего терминала
  4. через cdnjs http://www.cdnjs.com/libraries/angular-filter

(2) Включите angular-filter.js (или angular-filter.min.js) в ваш index.html после включения самого Angular.

(3) Добавьте «angular.filter» в список зависимостей вашего основного модуля.

a8m
источник
Отличный пример. Однако ключ возвращает имя группы, а не фактический ключ ... как мы можем решить это?
Джон Эндрюс
7
Не забудьте включить angular.filterмодуль.
Puce
1
Вы можете использовать заказ по группам @erfling, PTAL на: github.com/a8m/angular-filter/wiki/…
a8m
1
Ух ты. Спасибо. Я не ожидал, что порядок вложенного цикла повлияет на внешний цикл таким образом. Это действительно полезно. +1
беспорядочно
1
@Xyroid даже я ищу то же самое, что хочу сделать keyкак объект. удачи с твоей стороны
супер круто
25

В дополнение к принятым выше ответам я создал общий фильтр 'groupBy', используя библиотеку underscore.js.

JSFiddle (обновлено): http://jsfiddle.net/TD7t3/

Фильтр

app.filter('groupBy', function() {
    return _.memoize(function(items, field) {
            return _.groupBy(items, field);
        }
    );
});

Обратите внимание на «запомни» звонок. Этот метод подчеркивания кэширует результат функции и останавливает угловое вычисление выражения фильтра каждый раз, предотвращая тем самым угловое достижение предела итераций дайджеста.

HTML

<ul>
    <li ng-repeat="(team, players) in teamPlayers | groupBy:'team'">
        {{team}}
        <ul>
            <li ng-repeat="player in players">
                {{player.name}}
            </li>
        </ul>
    </li>
</ul>

Мы применяем наш фильтр «groupBy» к переменной области teamPlayers, к свойству «team». Наш ng-repeat получает комбинацию (key, values ​​[]), которую мы можем использовать в наших следующих итерациях.

Обновление 11 июня 2014 г. Я расширил группу с помощью фильтра, чтобы учесть использование выражений в качестве ключа (например, вложенных переменных). Сервис углового разбора очень удобен для этого:

Фильтр (с поддержкой выражений)

app.filter('groupBy', function($parse) {
    return _.memoize(function(items, field) {
        var getter = $parse(field);
        return _.groupBy(items, function(item) {
            return getter(item);
        });
    });
});

Контроллер (с вложенными объектами)

app.controller('homeCtrl', function($scope) {
    var teamAlpha = {name: 'team alpha'};
    var teamBeta = {name: 'team beta'};
    var teamGamma = {name: 'team gamma'};

    $scope.teamPlayers = [{name: 'Gene', team: teamAlpha},
                      {name: 'George', team: teamBeta},
                      {name: 'Steve', team: teamGamma},
                      {name: 'Paula', team: teamBeta},
                      {name: 'Scruath of the 5th sector', team: teamGamma}];
});

HTML (с выражением sortBy)

<li ng-repeat="(team, players) in teamPlayers | groupBy:'team.name'">
    {{team}}
    <ul>
        <li ng-repeat="player in players">
            {{player.name}}
        </li>
    </ul>
</li>

JSFiddle: http://jsfiddle.net/k7fgB/2/

chrisv
источник
Вы правы, ссылка на скрипку обновлена. Спасибо, что уведомили меня.
Крис
3
Это довольно опрятно на самом деле! Наименьшее количество кода.
Бенни Боттема
3
При этом следует отметить одну вещь - по умолчанию memoize использует первый параметр (то есть «элементы») в качестве ключа кэша - поэтому, если вы передадите ему те же «элементы» с другим «полем», он вернет то же кэшированное значение. Решения приветствуются.
Том Карвер
Я думаю, что вы можете использовать значение $ id, чтобы обойти это: элемент в отслеживании элементов по $ id (item)
Caspar Harmer
2
Какие "принятые ответы"? В переполнении стека может быть только один принятый ответ.
Себастьян Мах
19

Сначала выполните цикл, используя фильтр, который будет возвращать только уникальные команды, а затем вложенный цикл, который возвращает всех игроков в текущей команде:

http://jsfiddle.net/plantface/L6cQN/

HTML:

<div ng-app ng-controller="Main">
    <div ng-repeat="playerPerTeam in playersToFilter() | filter:filterTeams">
        <b>{{playerPerTeam.team}}</b>
        <li ng-repeat="player in players | filter:{team: playerPerTeam.team}">{{player.name}}</li>        
    </div>
</div>

сценарий:

function Main($scope) {
    $scope.players = [{name: 'Gene', team: 'team alpha'},
                    {name: 'George', team: 'team beta'},
                    {name: 'Steve', team: 'team gamma'},
                    {name: 'Paula', team: 'team beta'},
                    {name: 'Scruath of the 5th sector', team: 'team gamma'}];

    var indexedTeams = [];

    // this will reset the list of indexed teams each time the list is rendered again
    $scope.playersToFilter = function() {
        indexedTeams = [];
        return $scope.players;
    }

    $scope.filterTeams = function(player) {
        var teamIsNew = indexedTeams.indexOf(player.team) == -1;
        if (teamIsNew) {
            indexedTeams.push(player.team);
        }
        return teamIsNew;
    }
}
Бенни Боттема
источник
Так просто. Хороший один @Plantface.
Джефф Йейтс
просто великолепно. но что если я захочу нажать новый объект в $ scope.players при нажатии? как вы проходите через функцию, она будет добавлена?
супер круто
16

Изначально я использовал ответ Plantface, но мне не понравилось, как выглядит синтаксис на мой взгляд.

Я переработал его, чтобы использовать $ q.defer для пост-обработки данных и возврата списка по уникальным командам, который затем используется в качестве фильтра.

http://plnkr.co/edit/waWv1donzEMdsNMlMHBa?p=preview

Посмотреть

<ul>
  <li ng-repeat="team in teams">{{team}}
    <ul>
      <li ng-repeat="player in players | filter: {team: team}">{{player.name}}</li> 
    </ul>
  </li>
</ul>

контроллер

app.controller('MainCtrl', function($scope, $q) {

  $scope.players = []; // omitted from SO for brevity

  // create a deferred object to be resolved later
  var teamsDeferred = $q.defer();

  // return a promise. The promise says, "I promise that I'll give you your
  // data as soon as I have it (which is when I am resolved)".
  $scope.teams = teamsDeferred.promise;

  // create a list of unique teams. unique() definition omitted from SO for brevity
  var uniqueTeams = unique($scope.players, 'team');

  // resolve the deferred object with the unique teams
  // this will trigger an update on the view
  teamsDeferred.resolve(uniqueTeams);

});
Вальтер Стабош
источник
1
Этот ответ не работает с AngularJS> 1.1, так как Promised больше не разворачивается для массивов. См. Примечания
Бенни Боттема
6
Обещание в этом решении не требуется, поскольку вы ничего не делаете асинхронно. В этом случае вы можете просто пропустить этот шаг ( jsFiddle ).
Бенни Боттема
11

Оба ответа были хорошими, поэтому я переместил их в директиву, чтобы ее можно было многократно использовать, и не нужно было определять вторую переменную области видимости.

Вот скрипка, если вы хотите, чтобы она была реализована

Ниже приведена директива:

var uniqueItems = function (data, key) {
    var result = [];
    for (var i = 0; i < data.length; i++) {
        var value = data[i][key];
        if (result.indexOf(value) == -1) {
            result.push(value);
        }
    }
    return result;
};

myApp.filter('groupBy',
            function () {
                return function (collection, key) {
                    if (collection === null) return;
                    return uniqueItems(collection, key);
        };
    });

Тогда это можно использовать следующим образом:

<div ng-repeat="team in players|groupBy:'team'">
    <b>{{team}}</b>
    <li ng-repeat="player in players | filter: {team: team}">{{player.name}}</li>        
</div>
Тео
источник
11

Обновить

Первоначально я написал этот ответ , потому что старая версия решения , предложенного Ариэлем М. в сочетании с другими $filterс вызвало « Ошибка Infite $ diggest Loop » ( infdig) . К счастью, эта проблема была решена в последней версии angular.filter .

Я предложил следующую реализацию, у которой не было этой проблемы :

angular.module("sbrpr.filters", [])
.filter('groupBy', function () {
  var results={};
    return function (data, key) {
        if (!(data && key)) return;
        var result;
        if(!this.$id){
            result={};
        }else{
            var scopeId = this.$id;
            if(!results[scopeId]){
                results[scopeId]={};
                this.$on("$destroy", function() {
                    delete results[scopeId];
                });
            }
            result = results[scopeId];
        }

        for(var groupKey in result)
          result[groupKey].splice(0,result[groupKey].length);

        for (var i=0; i<data.length; i++) {
            if (!result[data[i][key]])
                result[data[i][key]]=[];
            result[data[i][key]].push(data[i]);
        }

        var keys = Object.keys(result);
        for(var k=0; k<keys.length; k++){
          if(result[keys[k]].length===0)
            delete result[keys[k]];
        }
        return result;
    };
});

Однако эта реализация будет работать только с версиями, предшествующими Angular 1.3. (Я скоро обновлю этот ответ, предоставив решение, которое работает со всеми версиями.)

Я действительно написал пост о шагах, которые я предпринял, чтобы развить это $filter, проблемах, с которыми я столкнулся, и вещах, которые я изучил из этого .

Хосеп
источник
Привет @Josep, взгляни на новую angular-filterверсию - 0.5.0, больше нет исключений. groupByможет быть цепным с любым фильтром. Кроме того, вы отлично завершили тестовые задания - вот вам спасибо.
a8m
1
@Josep Проблемы с Angular 1.3
amcdnl
2

В дополнение к принятому ответу вы можете использовать это, если вы хотите сгруппировать по нескольким столбцам :

<ul ng-repeat="(key, value) in players | groupBy: '[team,name]'">
Луис Тейджон
источник