Làm cách nào để nhóm dữ liệu với bộ lọc Angular?


136

Tôi có một danh sách những người chơi thuộc về một nhóm. Làm cách nào tôi có thể sử dụng bộ lọc để liệt kê người dùng mỗi nhóm?

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

Tôi đang tìm kiếm kết quả này:

  • đội alpha
    • Gen
  • nhóm beta
    • George
    • Paula
  • đội gamma
    • Steve
    • Scruath của ngành thứ 5

Câu trả lời:


182

Bạn có thể sử dụng nhómBy của mô-đun angular.filter .
vì vậy bạn có thể làm một cái gì đó như thế này:

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>

KẾT QUẢ:
Tên nhóm: alpha
* player: Gene
Tên nhóm: beta
* player: George
* player: Paula
Tên nhóm: gamma
* player: Steve
* player: Scruath

CẬP NHẬT: jsbin Hãy nhớ các yêu cầu cơ bản để sử dụng angular.filter, đặc biệt lưu ý bạn phải thêm nó vào phần phụ thuộc của mô-đun:

(1) Bạn có thể cài đặt bộ lọc góc bằng 4 phương pháp khác nhau:

  1. nhân bản và xây dựng kho lưu trữ này
  2. thông qua Bower: bằng cách chạy $ bower cài đặt bộ lọc góc từ thiết bị đầu cuối của bạn
  3. thông qua npm: bằng cách chạy $ npm cài đặt bộ lọc góc từ thiết bị đầu cuối của bạn
  4. thông qua cdnjs http://www.cdnjs.com/lologists/angular-filter

(2) Bao gồm angular-filter.js (hoặc angular-filter.min.js) trong index.html của bạn, sau khi bao gồm chính Angular.

(3) Thêm 'angular.filter' vào danh sách phụ thuộc của mô-đun chính của bạn.


Ví dụ tuyệt vời. Tuy nhiên, khóa trả về tên nhóm chứ không phải khóa thực tế ... làm thế nào chúng ta có thể giải quyết điều đó?
JohnAndrews 18/03/2015

7
Đừng quên bao gồm các angular.filtermô-đun.
Puce

1
bạn có thể sử dụng theo thứ tự với nhóm @erfling, PTAL trên: github.com/a8m/angular-filter/wiki/
phỏng

1
Tuyệt vời. Cảm ơn. Tôi không mong đợi việc đặt vòng lặp lồng nhau sẽ ảnh hưởng đến vòng ngoài theo cách đó. Điều đó thực sự hữu ích. +1
xóa sổ

1
@Xyroid thậm chí tôi đang tìm kiếm cùng một keyđối tượng tôi muốn làm đối tượng. bất kỳ may mắn từ phía bạn
siêu mát mẻ vào

25

Ngoài các câu trả lời được chấp nhận ở trên, tôi đã tạo bộ lọc 'groupBy' chung bằng thư viện underscore.js.

JSFiddle (đã cập nhật): http://jsfiddle.net/TD7t3/

Bộ lọc

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

Lưu ý cuộc gọi 'ghi nhớ'. Phương pháp gạch dưới này lưu trữ kết quả của hàm và ngăn không cho đánh giá biểu thức bộ lọc mỗi lần, do đó ngăn không cho góc tới đạt giới hạn lặp lại tiêu hóa.

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>

Chúng tôi áp dụng bộ lọc 'groupBy' của chúng tôi trên biến phạm vi teamPlayers, trên thuộc tính 'nhóm'. Lặp lại ng của chúng tôi nhận được kết hợp (khóa, giá trị []) mà chúng tôi có thể sử dụng trong các lần lặp sau.

Cập nhật ngày 11 tháng 6 năm 2014 Tôi đã mở rộng nhóm bằng bộ lọc để tính đến việc sử dụng biểu thức làm khóa (ví dụ: các biến lồng nhau). Dịch vụ phân tích góc cạnh khá tiện lợi cho việc này:

Bộ lọc (có hỗ trợ biểu thức)

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

Bộ điều khiển (với các đối tượng lồng nhau)

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 (với biểu thức sortBy)

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

Mã thông báo: http://jsfiddle.net/k7fgB/2/


Bạn đã đúng, liên kết fiddle đã được cập nhật. Cảm ơn bạn đã thông báo cho tôi.
chrisv

3
Điều này là khá gọn gàng thực sự! Số lượng mã ít nhất.
Benny Bottema

3
một điều cần lưu ý với điều này - theo mặc định, việc ghi nhớ sử dụng tham số đầu tiên (tức là 'mục') làm khóa bộ đệm - vì vậy nếu bạn chuyển nó cùng 'mục' với một 'trường' khác, nó sẽ trả về cùng một giá trị được lưu. Giải pháp hoan nghênh.
Tom Carver

Tôi nghĩ rằng bạn có thể sử dụng giá trị $ id để giải quyết vấn đề này: mục trong các mục được theo dõi bởi $ id (mục)
Caspar Harmer

2
Mà "câu trả lời được chấp nhận"? Trên Stack Overflow, chỉ có thể có một câu trả lời được chấp nhận.
Sebastian Mach

19

Trước tiên, thực hiện một vòng lặp bằng bộ lọc sẽ chỉ trả về các nhóm duy nhất và sau đó là một vòng lặp lồng nhau trả về tất cả người chơi cho mỗi nhóm hiện tại:

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>

kịch bản:

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

Quá đơn giản. Đẹp một @Plantface.
Jeff Yates

chỉ rực rỡ. Nhưng nếu tôi muốn đẩy một đối tượng mới lên $ scope.players khi nhấp thì sao? Khi bạn đang lặp qua một chức năng, nó sẽ được thêm vào?
siêu mát mẻ

16

Ban đầu tôi đã sử dụng câu trả lời của Plantface, nhưng tôi không thích cách nhìn cú pháp trong chế độ xem của tôi.

Tôi đã làm lại nó để sử dụng $ q.defer để xử lý sau dữ liệu và trả về một danh sách trên các nhóm duy nhất, sau đó sử dụng làm bộ lọc.

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

Lượt xem

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

Bộ điều khiển

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
Câu trả lời này không hoạt động với AngularJS> 1.1 vì Hứa sẽ không được mở ra nữa cho các mảng. Xem ghi chú nhập cư
Benny Bottema

6
Không cần Promise trong giải pháp này, vì bạn không làm bất cứ điều gì không đồng bộ. Trong trường hợp này, bạn chỉ cần bỏ qua bước đó ( jsFiddle ).
Benny Bottema

11

Cả hai câu trả lời đều tốt vì vậy tôi đã chuyển chúng sang một lệnh để nó có thể được sử dụng lại và một biến phạm vi thứ hai không cần phải xác định.

Đây là câu đố nếu bạn muốn xem nó được thực hiện

Dưới đây là chỉ thị:

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

Sau đó, nó có thể được sử dụng như sau:

<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

Cập nhật

Ban đầu tôi đã viết câu trả lời này bởi vì phiên bản cũ của giải pháp được đề xuất bởi Ariel M. khi kết hợp với các$filter phiên bản khác đã kích hoạt " Lỗi vòng lặp Infest $ digest " ( infdig) . May mắn là vấn đề này đã được giải quyết trong phiên bản mới nhất của angular.filter .

Tôi đề nghị thực hiện sau đây, không có vấn đề đó :

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

Tuy nhiên, việc triển khai này sẽ chỉ hoạt động với các phiên bản trước Angular 1.3. (Tôi sẽ cập nhật câu trả lời này trong thời gian ngắn để cung cấp giải pháp hoạt động với tất cả các phiên bản.)

Tôi thực sự đã viết một bài về các bước mà tôi đã thực hiện để phát triển $filtervấn đề này , những vấn đề mà tôi gặp phải và những điều tôi học được từ nó .


Xin chào @Josep, hãy xem angular-filterphiên bản mới - 0.5.0, không có ngoại lệ nào nữa. groupBycó thể được chuỗi với bất kỳ bộ lọc. Ngoài ra, bạn là trường hợp thử nghiệm tuyệt vời kết thúc thành công - đây là một plunker Cảm ơn.
a8m

1
@Josep Có vấn đề trong Angular 1.3
amcdnl

2

Ngoài câu trả lời được chấp nhận, bạn có thể sử dụng câu hỏi này nếu bạn muốn nhóm theo nhiều cột :

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.