Як можна групувати дані за допомогою кутового фільтра?


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'}];

Я шукаю цей результат:

  • командна альфа
    • Гена
  • бета команда
    • Джордж
    • Паула
  • командна гамма
    • Стів
    • Скрут 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" до списку залежностей вашого основного модуля.


Чудовий приклад. Однак ключ повертає назву групи, а не власне ключ ... як ми можемо це вирішити?
JohnAndrews

7
Не забудьте включити angular.filterмодуль.
Пуче

1
ви можете використовувати замовлення з групою @erfling, PTAL на сайті: github.com/a8m/angular-filter/wiki/…
a8m

1
Ух ти. Дякую. Я не сподівався, що замовлення вкладеного циклу вплине на зовнішній таким чином. Це справді корисно. +1
erfling

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-повторення отримує комбінацію (ключ, значення []), яку ми можемо використовувати в наступних ітераціях.

Оновлення 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
одне, що слід зазначити з цього приводу - пам’ятка за замовчуванням використовує перший параметр (тобто «елементи») як кеш-ключ - тому, якщо ви передасте йому ті самі «елементи» з іншим 'полем', він поверне те саме кешоване значення. Рішення вітаються.
Том Карвер

Я думаю, ви можете використовувати значення $ 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.
Jeff Yates

просто геніальний. але що робити, якщо я хочу натиснути новий об’єкт на $ range.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, оскільки обіцяні більше не розкручуються для масивів. Дивіться записки
Benny Bottema

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

Оновлення

Я спочатку написав цю відповідь, тому що стара версія рішення, запропонована Аріелем М. у поєднанні з іншими $filters, ініціювала " Нескінченну помилку циклу перекопувача $ " ( 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]'">

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.