Bagaimana cara mengelompokkan data dengan filter Angular?

136

Saya memiliki daftar pemain yang masing-masing termasuk dalam kelompok. Bagaimana cara menggunakan filter untuk membuat daftar pengguna per grup?

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

Saya mencari hasil ini:

  • tim alpha
    • Gen
  • tim beta
    • George
    • Paula
  • tim gamma
    • Steve
    • Scruath dari sektor ke-5
Benny Bottema
sumber

Jawaban:

182

Anda dapat menggunakan groupBy dari modul angular.filter .
jadi Anda bisa melakukan sesuatu seperti ini:

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>

HASIL:
Nama grup: alpha
* pemain: Gen
Nama grup: beta
* pemain: George
* pemain: Paula
Nama grup: gamma
* pemain: Steve
* pemain: Scruath

UPDATE: jsbin Ingatlah persyaratan dasar yang akan digunakan angular.filter, khususnya perhatikan Anda harus menambahkannya ke dependensi modul Anda:

(1) Anda dapat memasang filter sudut menggunakan 4 metode berbeda:

  1. klon & buat repositori ini
  2. via Bower: dengan menjalankan $ bower install angular-filter dari terminal Anda
  3. via npm: dengan menjalankan $ npm install angular-filter dari terminal Anda
  4. melalui cdnjs http://www.cdnjs.com/libraries/angular-filter

(2) Sertakan angular-filter.js (atau angular-filter.min.js) di index.html Anda, setelah memasukkan Angular itu sendiri.

(3) Tambahkan 'angular.filter' ke daftar dependensi modul utama Anda.

a8m
sumber
Contoh yang bagus. Namun, kunci mengembalikan nama grup dan bukan kunci sebenarnya ... bagaimana kita bisa mengatasinya?
JohnAndrews
7
Jangan lupa untuk menyertakan angular.filtermodulnya.
Puce
1
Anda dapat menggunakan order-by dengan group-by @erfling, PTAL di: github.com/a8m/angular-filter/wiki/…
a8m
1
Oh wow. Terima kasih. Saya tidak berharap memesan loop bersarang untuk memengaruhi loop luar dengan cara itu. Itu sangat berguna. +1
erfling
1
@Xyroid bahkan saya mencari hal yang sama yang ingin saya jadikan keysebagai objek. semoga beruntung dari sisi Anda
sangat keren
25

Selain jawaban yang diterima di atas, saya membuat filter 'groupBy' umum menggunakan pustaka underscore.js.

JSFiddle (diperbarui): http://jsfiddle.net/TD7t3/

Filter

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

Perhatikan panggilan 'memoize'. Metode garis bawah ini menyimpan hasil dari fungsi dan menghentikan angular dari mengevaluasi ekspresi filter setiap saat, sehingga mencegah angular mencapai batas iterasi intisari.

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>

Kami menerapkan filter 'groupBy' kami pada variabel cakupan teamPlayers, di properti 'tim'. Ng-repeat kita menerima kombinasi (key, values ​​[]) yang bisa kita gunakan dalam iterasi berikut.

Perbarui 11 Juni 2014 Saya memperluas grup dengan filter untuk memperhitungkan penggunaan ekspresi sebagai kunci (misalnya variabel bersarang). Layanan angular parse sangat berguna untuk ini:

Filter (dengan dukungan ekspresi)

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

Pengontrol (dengan objek bersarang)

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 (dengan ekspresi 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
sumber
Anda benar, tautan biola telah diperbarui. Terima kasih telah memberi tahu saya.
chrisv
3
Ini sebenarnya cukup rapi! Jumlah kode paling sedikit.
Benny Bottema
3
satu hal yang perlu diperhatikan dengan ini - secara default memoize menggunakan param pertama (yaitu 'items') sebagai kunci cache - jadi jika Anda mengirimkannya 'item' yang sama dengan 'field' yang berbeda, ia akan mengembalikan nilai cache yang sama. Solusi diterima.
Tom Carver
Saya pikir Anda dapat menggunakan nilai $ id untuk menyiasati ini: item dalam item dilacak oleh $ id (item)
Caspar Harmer
2
"Jawaban yang diterima" yang mana? Di Stack Overflow, hanya ada satu jawaban yang diterima.
Sebastian Mach
19

Pertama, lakukan perulangan menggunakan filter yang hanya akan mengembalikan tim unik, lalu perulangan bersarang yang mengembalikan semua pemain per tim saat ini:

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>

naskah:

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;
    }
}
Benny Bottema
sumber
Sangat sederhana. Bagus @Plantface.
Jeff Yates
brilian. tetapi bagaimana jika saya ingin mendorong objek baru ke $ scope.players saat diklik? as u looping melalui suatu fungsi apakah itu akan ditambahkan?
sangat keren
16

Saya awalnya menggunakan jawaban Plantface, tetapi saya tidak suka tampilan sintaks dalam pandangan saya.

Saya mengerjakannya ulang untuk menggunakan $ q.defer untuk pasca-proses data dan mengembalikan daftar pada tim unik, yang kemudian digunakan sebagai filter.

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

Melihat

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

Kontroler

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);

});
Walter Stabosz
sumber
1
Jawaban ini tidak berfungsi dengan AngularJS> 1.1 karena Dijanjikan tidak lagi dibuka untuk array. Lihat catatan imigrasi
Benny Bottema
6
Tidak perlu Promise dalam solusi ini, karena Anda tidak melakukan apa pun secara asinkron. Dalam hal ini, Anda cukup melewati langkah itu ( jsFiddle ).
Benny Bottema
11

Kedua jawaban itu bagus jadi saya memindahkannya ke arahan sehingga dapat digunakan kembali dan variabel lingkup kedua tidak harus didefinisikan.

Ini biola jika Anda ingin melihatnya diterapkan

Di bawah ini adalah petunjuknya:

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

Maka bisa digunakan sebagai berikut:

<div ng-repeat="team in players|groupBy:'team'">
    <b>{{team}}</b>
    <li ng-repeat="player in players | filter: {team: team}">{{player.name}}</li>        
</div>
Theo
sumber
11

Memperbarui

Saya awalnya menulis jawaban ini karena versi lama solusi yang disarankan oleh Ariel M. bila dikombinasikan dengan lainnya $filters memicu " Kesalahan Infite $ diggest loop " ( infdig) . Untungnya masalah ini telah diselesaikan di versi terbaru angular.filter .

Saya menyarankan penerapan berikut, yang tidak memiliki masalah itu :

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

Namun, implementasi ini hanya akan bekerja dengan versi sebelum Angular 1.3. (Saya akan memperbarui jawaban ini segera memberikan solusi yang berfungsi dengan semua versi.)

Saya sebenarnya telah menulis posting tentang langkah-langkah yang saya ambil untuk mengembangkan ini $filter, masalah yang saya temui dan hal-hal yang saya pelajari darinya .

Josep
sumber
Hai @Josep, lihat angular-filterversi baru - 0.5.0, tidak ada lagi pengecualian. groupBybisa dirantai dengan filter apapun. juga, Anda kasus uji hebat selesai dengan sukses - inilah plunker Terima kasih.
a8m
1
@Josep Memiliki masalah di Angular 1.3
amcdnl
2

Selain jawaban yang diterima, Anda dapat menggunakan ini jika Anda ingin mengelompokkan berdasarkan beberapa kolom :

<ul ng-repeat="(key, value) in players | groupBy: '[team,name]'">
Luis Teijon
sumber