Grupo AngularJS por Diretiva sem Dependências Externas

Sou novo no Angular e gostaria de aprender a melhor maneira de lidar com um problema. Meu objective é ter um meio reutilizável para criar grupos por headers. Eu criei uma solução que funciona, mas acho que isso deve ser uma diretiva em vez de uma function de escopo dentro do meu controlador, mas não tenho certeza de como conseguir isso, ou se uma diretiva é mesmo o caminho certo a seguir. Quaisquer insumos seriam muito apreciados.

Veja minha abordagem atual trabalhando no jsFiddle

No HTML é uma lista simples usando ng-repeat onde eu chamo minha function newGrouping () no ng-show. A function passa uma referência para a lista completa, o campo pelo qual desejo agrupar e o índice atual.

{{item.GroupByFieldName}}

{{item.whatever}}

No meu controlador eu tenho minha function newGrouping () que simplesmente compara a atual com a anterior, exceto no primeiro item, e retorna true ou false dependendo de uma correspondência.

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

A saída ficará assim.

Grupo 1

  • abc
  • def

Grupo 2

  • ghi
  • jkl
  • mno

Parece que deveria haver um caminho melhor. Eu quero que isso seja uma function de utilidade comum que eu possa reutilizar. Isso deveria ser uma diretriz? Existe uma maneira melhor de referenciar o item anterior na lista do que meu método de passar a lista completa e o índice atual? Como eu abordaria uma diretiva para isso?

Qualquer conselho é muito apreciado.

UPDATE: Procurando por uma resposta que não exija dependencies externas. Existem boas soluções usando sublinhado / lodash ou o módulo de filtro angular.

Darryl

Esta é uma modificação da solução de Darryl acima, que permite múltiplos grupos por parâmetros. Além disso, faz uso de $ parse para permitir o uso de propriedades aninhadas como grupo por parâmetros.

Exemplo usando vários parâmetros nesteds

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

HTML

 

Multiple Grouping Parameters

{{item.groupfield}} {{item.deep.category}}

  • {{item.whatever}}

Filtro (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; }; }]); 

Se você já estiver usando o LoDash / Underscore, ou qualquer biblioteca funcional, você pode fazer isso usando a function _.groupBy () (ou nome similar).


No controlador :

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

No modelo :

 
    {{lang}}
  • {{mov.movieName}}

Isso renderiza :

Inglês

  • Limite do amanhã
  • X-MEN

Télugo

  • Gabbar Singh 2
  • Resu Gurram

Melhor ainda, isso também pode ser convertido em um filtro com muita facilidade, sem muito código clichê para agrupar elementos por uma propriedade.

Atualização: Agrupar por várias chaves

Muitas vezes, agrupar usando várias chaves é muito útil. Ex, usando LoDash ( fonte ):

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

Atualize por que eu recomendo esta abordagem: Usar filtros em ng-repeat / ng-options causa sérios problemas de desempenho, a menos que esse filtro seja executado rapidamente. Google para o problema perf filtros. Você saberá!

Aqui está o que eu finalmente decidi lidar com agrupamentos dentro da repetição do ng. Eu li mais sobre diretivas e filtros e enquanto você pode resolver este problema com qualquer um, a abordagem de filtro parecia uma escolha melhor. O motivo é que os filtros são mais adequados para situações em que apenas os dados precisam ser manipulados. Diretivas são melhores quando são necessárias manipulações DOM. Neste exemplo, eu realmente só precisava manipular os dados e deixar o DOM sozinho. Eu senti que isso dava a maior flexibilidade.

Veja minha abordagem final para agrupamentos trabalhando no jsFiddle . Também adicionei um pequeno formulário para demonstrar como a lista funcionará ao adicionar dados dinamicamente.

Aqui está o HTML.

 

{{item.groupfield}}

  • {{item.whatever}}

Aqui está o 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 
*

{{item.groupfield}}

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

Para o aplicativo em que estou usando, configurei o filtro como um filtro reutilizável em todo o aplicativo.

O que eu não gostei sobre a abordagem da diretiva foi que o HTML estava na diretiva, por isso não se sentia reutilizável.

Gostei da abordagem de filtro anterior, mas não pareceu eficiente, pois a lista teria que ser percorrida duas vezes em cada ciclo de digestão. Eu lido com longas listas, então pode ser um problema. Além disso, não parece tão intuitivo quanto uma simples verificação em relação ao item anterior para ver se ele mudou. Além disso, eu queria poder usar o filtro em vários campos com facilidade, o que esse novo filtro manipula apenas canalizando o filtro novamente com outro nome de campo.

Um outro comentário no meu filtro groupBy – eu percebo que vários agrupamentos poderiam fazer com que o array fosse percorrido várias vezes, então eu planejo revisá-lo para aceitar um array de vários grupos por campos para que ele tenha que atravessar o array apenas uma vez .

Muito obrigado pelas inputs. Isso realmente me ajudou a aprender mais sobre diretivas e filtros em Angular.

felicidades, Darryl

Abaixo está uma solução baseada em diretivas, assim como um link para um JSFiddle que a demonstra. A diretiva permite que cada instância especifique o nome do campo dos itens pelos quais deve agrupar, portanto, há um exemplo usando dois campos diferentes. Tem tempo de execução linear no número de itens.

JSFiddle

 

Grouping by FirstFieldName

Grouping by SecondFieldName

angular.module('myApp', []).directive('groupWithHeaders', function() { return { template: "
" + "

{{group}}

" + "
" + "{{item.whatever}}" + "
" + "
", 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'} ]; }

O AngularJS possui três diretivas para ajudá-lo a exibir grupos de informações. Essas diretivas são ngRepeat, ngRepeatStart e ngRepeatEnd. Eu encontrei uma postagem no blog que mostra como mostrar grupos em AngularJS . A essência disso é algo assim:

  
{{customer.name}}
{{order.total}} - {{order.description}}

Diretivas bastante poderosas, uma vez que você aprenda a usá-las.

O código de JoshMB não funcionará corretamente, pois você tem vários filtros no mesmo dataset na mesma visualização. Na segunda vez que você agrupar uma versão filtrada do dataset, ela alterará o mesmo atributo no object original, interrompendo assim as quebras de grupo nas versões filtradas anteriormente.

Eu resolvi isso adicionando o nome do atributo “CHANGED” como um parâmetro de filtro extra. Abaixo está minha versão atualizada do código.

 /* * 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 
*

{{item.groupfield}}

* *
*

{{item.groupfield}}

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

EDIT: aqui está uma abordagem de filtro personalizado. Groups é criado por uma function de filtro no escopo para gerar uma matriz de grupos da lista atual. A adição / exclusão de itens da lista vinculará a atualização da matriz do grupo à medida que ela for redefinida a cada ciclo de digitação.

HTML

 

{{group}}

  • {{item.whatever}}

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

http://blog.csdn.net/violet_day/article/details/17023219#t2

        
    {{ a.name }}
  • {{ b.id }}
 try this:     Example - example-example58-production     
{{friend.age}}
{{friend.name}} {{friend.phone}} {{friend.age==friendx[$index].age}}
enter code here [http://plnkr.co/edit/UhqKwLx1yo2ua44HjY59?p=preview][1] [1]: http://plnkr.co/edit/UhqKwLx1yo2ua44HjY59?p=preview