since property known isn img angularjs group-by angularjs-directive angularjs-ng-repeat ng-show

property - ng-required angularjs



Grupo AngularJS Por Directiva Sin Dependencias Externas. (9)

Soy nuevo en Angular y me gustaría aprender la mejor manera de manejar un problema. Mi objetivo es tener un medio reutilizable para crear grupos por encabezados. Creé una solución que funciona, pero creo que esto debería ser una directiva en lugar de una función de alcance dentro de mi controlador, pero no estoy seguro de cómo hacerlo, o si una directiva es incluso la forma correcta de hacerlo. 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.

<div ng-app> <div ng-controller=''TestGroupingCtlr''> <div ng-repeat=''item in MyList''> <div ng-show="newGrouping($parent.MyList, ''GroupByFieldName'', $index);"> <h2>{{item.GroupByFieldName}}</h2> </div> {{item.whatever}} </div> </div> </div>

En mi controlador tengo mi función newGrouping () que simplemente compara la corriente con la 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

Parece que debería haber una mejor manera. Quiero que esta sea una función de utilidad común que puedo reutilizar. ¿Debería ser esto una directiva? ¿Hay una forma mejor de hacer referencia al elemento anterior en la lista que mi método de pasar la lista completa y el índice actual? ¿Cómo me acercaría a una directiva para esto?

Cualquier consejo es muy apreciado.

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

Darryl


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

JSFiddle

<div ng-app=''myApp''> <div ng-controller=''TestGroupingCtlr''> <h1>Grouping by FirstFieldName</h1> <div group-with-headers to-group="MyList" group-by="FirstFieldName"> </div> <h1>Grouping by SecondFieldName</h1> <div group-with-headers to-group="MyList" group-by="SecondFieldName"> </div> </div> </div> angular.module(''myApp'', []).directive(''groupWithHeaders'', function() { return { template: "<div ng-repeat=''(group, items) in groups''>" + "<h2>{{group}}</h2>" + "<div ng-repeat=''item in items''>" + "{{item.whatever}}" + "</div>" + "</div>", 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 se muestran los grupos en AngularJS . La esencia de esto es algo como esto:

<body ng-controller="OrdersCtrl"> <div ng-repeat-start="customer in customers" class="header">{{customer.name}}</div> <div ng-repeat="order in customer.orders">{{order.total}} - {{order.description}}</div> <div ng-repeat-end><br /></div> </body>

Directivas bastante poderosas una vez que aprendas a usarlas.


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

HTML

<div ng-app="myApp"> <div ng-controller=''TestGroupingCtlr''> <div ng-repeat=''group in getGroups()''> <h2>{{group}}</h2> <ul> <!-- could use another scope variable as predicate --> <li ng-repeat="item in MyList | groupby:group">{{item.whatever}}</li> </ul> </div> </div> </div>

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


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

Resolví esto agregando el nombre del atributo "CAMBIADO" como en el parámetro de filtro adicional. A continuación se muestra 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 <div ng-repeat="item in MyList | filter:''a'' | groupBy:''groupfield'':''Agroup_CHANGED''" > * <h2 ng-if="item.Agroupfield_CHANGED">{{item.groupfield}}</h2> * <!-- now a differen filtered subset --> * <div ng-repeat="item in MyList | filter:''b'' | groupBy:''groupfield'':''Bgroup_CHANGED''" > * <h2 ng-if="item.Bgroupfield_CHANGED">{{item.groupfield}}</h2> * * 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; }; }]);


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

Ejemplo usando múltiples parámetros anidados

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

HTML

<h1>Multiple Grouping Parameters</h1> <div ng-repeat="item in MyList | orderBy:''groupfield'' | groupBy:[''groupfield'', ''deep.category'']"> <h2 ng-show="item.group_by_CHANGED">{{item.groupfield}} {{item.deep.category}}</h2> <ul> <li>{{item.whatever}}</li> </ul> </div>

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


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

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

Aquí está el HTML.

<div ng-app="myApp"> <div ng-controller=''TestGroupingCtlr''> <div ng-repeat="item in MyList | orderBy:''groupfield'' | groupBy:''groupfield''" > <h2 ng-show="item.groupfield_CHANGED">{{item.groupfield}}</h2> <ul> <li>{{item.whatever}}</li> </ul> </div> <form role="form" ng-submit="AddItem()"> <input type="text" data-ng-model="item.groupfield" placeholder="Group"> <input type="text" data-ng-model="item.whatever" placeholder="Item"> <input class="btn" type="submit" value="Add Item"> </form> </div> </div>

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 <div ng-repeat="item in MyList | groupBy:''groupfield''" > * <h2 ng-if="item.groupfield_CHANGED">{{item.groupfield}}</h2> * * 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 de la directiva 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 ser recorrida dos veces en cada ciclo de resumen. Trato con listas largas, por lo que podría ser un problema. Además, simplemente no parecía tan intuitivo como una simple comprobación del artículo anterior para ver si había cambiado. Además, quería poder usar el filtro en múltiples campos fácilmente, lo que este nuevo filtro maneja simplemente al canalizar el filtro nuevamente con otro nombre de campo.

Otro comentario sobre mi filtro de grupo: me doy cuenta de que varias agrupaciones causan 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.

Saludos, Darryl


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

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 :

<ul ng-repeat="(lang, langMovs) in movies">{{lang}} <li ng-repeat="mov in langMovs">{{mov.movieName}}</li> </ul>

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: Grupo por teclas múltiples

A menudo es muy útil agrupar usando varias teclas. Ex, usando LoDash ( source ):

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

Actualice por qué recomiendo este enfoque: el uso de filtros en ng-repeat / ng-options provoca graves problemas de rendimiento a menos que el filtro se ejecute rápidamente. Google para los filtros perf problema. ¡Tu sabrás!


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

<!doctype html> <html ng-app> <head> <script src="lib/angular/angular.min.js"></script> <script> function TestCtrl($scope) { $scope.items = [ { id: 0, name: "Red"}, { id: 1, name: "Red"}, { id: 2, name: "Red"}, { id: 3, name: "Red"}, { id: 4, name: "Yellow"}, { id: 5, name: "Orange"} ]; } </script> </head> <body ng-controller="TestCtrl"> <ul ng-repeat="a in items" ng-if="a.name!=items[$index-1].name"> {{ a.name }} <li ng-repeat="b in items" ng-if="a.name==b.name"> {{ b.id }} </li> </ul> </body> </html>


try this: <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Example - example-example58-production</title> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.5/angular.min.js"></script> </head> <body ng-app=""> <script> function Ctrl($scope) { $scope.friends = [{name:''John'', phone:''555-1212'', age:10}, {name:''Mary'', phone:''555-9876'', age:19}, {name:''Mike'', phone:''555-4321'', age:21}, {name:''Adam'', phone:''555-5678'', age:35}, {name:''John'', phone:''555-1212'', age:10}, {name:''John'', phone:''555-1212'', age:10}, {name:''John'', phone:''555-1212'', age:10}, {name:''John'', phone:''555-1212'', age:10}, {name:''Julie'', phone:''555-8765'', age:29}, {name:''Mike'', phone:''555-4321'', age:21}, {name:''Adam'', phone:''555-5678'', age:35}, {name:''Mike'', phone:''555-4321'', age:21}, {name:''Adam'', phone:''555-5678'', age:35}, {name:''Mike'', phone:''555-4321'', age:21}, {name:''Adam'', phone:''555-5678'', age:35}] } </script> <div ng-controller="Ctrl"> <div ng-init="friendx=(friends|orderBy:''age'')"> </div> <table class="friend" ng-repeat="friend in friendx"> <tr> <td ng-if="friendx[$index].age!=friendx[$index-1].age">{{friend.age}}</td> </tr> <tr> <td>{{friend.name}}</td> <td>{{friend.phone}}</td> <td>{{friend.age==friendx[$index].age}}</td> </tr> </table> </div> </body>enter code here </html> [http://plnkr.co/edit/UhqKwLx1yo2ua44HjY59?p=preview][1] [1]: http://plnkr.co/edit/UhqKwLx1yo2ua44HjY59?p=preview