AngularJS Group por directiva sin dependencias externas

Soy nuevo en Angular y me gustaría aprender la mejor manera de manejar un problema. Mi objective es tener un medio reutilizable para crear grupos por encabezados. Creé una solución que funciona, pero creo que debería ser una directiva en lugar de una función de scope dentro de mi controlador, pero no estoy seguro de cómo lograrlo, o si una directiva es incluso la correcta. Cualquier entrada sería muy apreciada.

Ver mi enfoque actual trabajando en jsFiddle

En el HTML, es una lista simple usando ng-repeat donde llamo a mi función newGrouping () en ng-show. La función pasa una referencia a la lista completa, el campo por el que quiero agrupar y el índice actual.

{{item.GroupByFieldName}}

{{item.whatever}}

En mi controlador tengo mi nueva función Agrupar () que simplemente compara el actual con el anterior, excepto en el primer elemento, y devuelve verdadero o falso dependiendo de una coincidencia.

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

La salida se verá así.

Grupo 1

  • a B C
  • def

Grupo 2

  • ghi
  • jkl
  • mno

Se siente como si hubiera una mejor manera. Quiero que sea una función de utilidad común que pueda reutilizar. ¿Debería ser esto una directiva? ¿Hay una mejor manera de hacer referencia al elemento anterior en la lista que mi método de pasar la lista completa y el índice actual? ¿Cómo abordaría una directiva para esto?

Cualquier consejo es muy apreciado.

ACTUALIZACIÓN: buscando una respuesta que no requiera dependencias externas. Hay buenas soluciones que utilizan subrayado / lodash o el módulo de filtro angular.

Darryl

Esta es una modificación de la solución de Darryl anterior, que permite múltiples grupos por parámetros. Además, usa $ parse para permitir el uso de propiedades anidadas como grupo por parámetros.

Ejemplo usando múltiples 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; }; }]); 

Si ya está utilizando LoDash / Underscore, o cualquier biblioteca funcional, puede hacerlo utilizando la función _.groupBy () (o similarmente llamada).


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

En plantilla :

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

Esto hará que :

Inglés

  • La era de El Mañana
  • X MEN

Telugu

  • Gabbar Singh 2
  • Resu Gurram

Aún mejor, esto también se puede convertir en un filtro muy fácilmente, sin mucho código repetitivo para agrupar elementos por una propiedad.

Actualización: agrupar por claves múltiples

A menudo, la agrupación con varias teclas es muy útil. Por ejemplo, usando LoDash ( fuente ):

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

Actualización sobre por qué recomiendo este enfoque: Usar filtros en ng-repeat / ng-options causa serios problemas de rendimiento a menos que ese filtro se ejecute rápidamente. Google para el problema de perf de filtros. ¡Tu sabrás!

Esto es lo que finalmente decidí manejar agrupaciones dentro de ng-repeat. Leí más sobre directivas y filtros y aunque puede resolver este problema con cualquiera de los dos, el enfoque de filtro parecía una mejor opción. La razón es que los filtros son más adecuados para situaciones en las que solo se deben manipular los datos. Las directivas son mejores cuando se necesitan manipulaciones DOM. En este ejemplo, realmente solo necesitaba manipular los datos y dejar el DOM solo. Sentí que esto le dio la mayor flexibilidad.

Ver mi aproximación final a las agrupaciones que trabajan en jsFiddle . También agregué un pequeño formulario para demostrar cómo funcionará la lista al agregar datos dinámicamente.

Aquí está el HTML.

 

{{item.groupfield}}

  • {{item.whatever}}

Aquí está el 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 la aplicación en la que estoy usando esto, configuro el filtro como un filtro reutilizable en toda la aplicación.

Lo que no me gustó del enfoque directivo fue que el HTML estaba en la directiva, por lo que no se sentía reutilizable.

Me gustó el enfoque de filtro anterior, pero no parecía eficiente ya que la lista tendría que atravesarse dos veces en el ciclo de digestión. Me ocupo de listas largas, por lo que podría ser un problema. Además, no parecía tan intuitivo como una simple comprobación del elemento anterior para ver si cambiaba. Además, quería poder usar el filtro fácilmente contra múltiples campos, que este nuevo filtro maneja simplemente al canalizar el filtro nuevamente con otro nombre de campo.

Otro comentario en mi filtro groupBy: me doy cuenta de que múltiples agrupaciones harían que la matriz se atraviese varias veces, por lo que planeo revisarla para aceptar una matriz de múltiples grupos por campos, de modo que solo tenga que atravesar la matriz una vez .

Muchas gracias por las entradas. Realmente me ayudó a aprender más sobre directivas y filtros en Angular.

aplausos, Darryl

A continuación se muestra una solución basada en directivas, así como un enlace a un JSFiddle demosándolo. La directiva permite que cada instancia especifique el nombre de campo de los elementos por los que debe agruparse, de modo que hay un ejemplo que usa dos campos diferentes. Tiene tiempo de ejecución lineal en el número de elementos.

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

AngularJS tiene tres directivas para ayudarlo a mostrar grupos de información. Esas directivas son ngRepeat, ngRepeatStart y ngRepeatEnd. Encontré una publicación de blog que muestra cómo mostrar grupos en AngularJS . La esencia de esto es algo como esto:

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

Directivas bastante poderosas una vez que aprendes cómo usarlas.

El código de JoshMB no funcionará correctamente si tienes varios filtros en el mismo conjunto de datos en la misma vista. La segunda vez que agrupa una versión filtrada del conjunto de datos, cambiará el mismo atributo en el objeto original, rompiendo así los saltos de grupo en las versiones previamente filtradas.

Lo resolví agregando el nombre del atributo “CAMBIADO” como parámetro de filtro adicional. Debajo está mi versión actualizada del 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; }; }]);

EDITAR: aquí hay un enfoque de filtro personalizado. Groups se crea mediante una función de filtro en el scope para generar una matriz de grupos a partir de la lista actual. Agregar / eliminar elementos de la lista vinculará la actualización de la matriz de grupo, ya que se restablece cada ciclo de resumen.

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

MANIFESTACIÓN

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

 < !doctype html>       
    {{ a.name }}
  • {{ b.id }}
 try this: < !doctype html>    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