Группа AngularJS по директиве без внешних зависимостей

Я новичок в Angular и хотел бы узнать лучший способ справиться с проблемой. Моя цель – использовать многоразовые средства для создания группы по заголовкам. Я создал решение, которое работает, но я думаю, что это должна быть директива вместо функции области видимости внутри моего controllerа, но я не уверен, как это сделать, или если директива – это даже правильный путь. Любые входы были бы весьма полезны.

См. Мой текущий подход, работающий над jsFiddle

В HTML это простой список с использованием ng-repeat, где я вызываю функцию newGrouping () на ng-show. Функция передает ссылку на полный список, поле, которое я хочу сгруппировать, и текущий индекс.

{{item.GroupByFieldName}}

{{item.whatever}}

В моем controllerе у меня есть функция newGrouping (), которая просто сравнивает текущий с предыдущим, кроме первого элемента, и возвращает true или false в зависимости от соответствия.

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

Результат будет выглядеть следующим образом.

Группа 1

  • азбука
  • Защита

Группа 2

  • ГХИ
  • JKL
  • MnO

Похоже, что должен быть лучший способ. Я хочу, чтобы это была общая функция утилиты, которую я могу повторно использовать. Должна ли быть директива? Есть ли лучший способ ссылаться на предыдущий элемент в списке, чем мой метод передачи полного списка и текущего индекса? Как я подхожу к директиве для этого?

Любые советы высоко ценится.

UPDATE: поиск ответа, который не требует внешних зависимостей. Существуют хорошие решения с использованием underscore / lodash или модуля углового фильтра.

Дэррил

    Это модификация решения Дэррила выше, что позволяет несколько групповых параметров. Кроме того, он использует $ parse, чтобы разрешить использование вложенных свойств в виде группы по параметрам.

    Пример использования нескольких вложенных параметров

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

    HTML

     

    Multiple Grouping Parameters

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

    • {{item.whatever}}

    Фильтр (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; }; }]); 

    Если вы уже используете LoDash / Underscore или любую функциональную библиотеку, вы можете сделать это с помощью функции _.groupBy () (или аналогично названной).


    В controllerе :

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

    В шаблоне :

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

    Это позволит :

    английский

    • Грань будущего
    • X-MEN

    телугу

    • Габбар Сингх 2
    • Resu Gurram

    Еще лучше, это также можно легко преобразовать в фильтр, без большого кода шаблона, чтобы группировать элементы по свойству.

    Обновление: группа с несколькими ключами

    Часто группировка с использованием нескольких ключей очень полезна. Ex, используя LoDash ( источник ):

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

    Обновите вопрос о том, почему я рекомендую этот подход: использование фильтров на ng-repeat / ng-options вызывает серьезные проблемы с перфорацией, если этот фильтр не выполняется быстро. Google для проблем с фильтрами. Вы будете знать!

    Вот что я, наконец, решил обработать группировки в ng-repeat. Я читал больше о директивах и фильтрах, и, хотя вы также можете решить эту проблему, подход фильтра стал лучшим выбором. Причина в том, что фильтры лучше подходят для ситуаций, когда нужно манипулировать только данными. Директивы лучше, когда нужны DOM-манипуляции. В этом примере мне действительно нужно было только манипулировать данными и оставить DOM самостоятельно. Я чувствовал, что это обеспечило максимальную гибкость.

    Посмотрите мой окончательный подход к группам, работающим над jsFiddle . Я также добавил небольшую форму, чтобы продемонстрировать, как этот список будет работать при динамическом добавлении данных.

    Вот HTML.

     

    {{item.groupfield}}

    • {{item.whatever}}

    Вот 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; }; })
    от 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; }; })

    Для приложения, в котором я использую это, я настраиваю фильтр как фильтр многократного использования в приложении.

    То, что мне не понравилось в директивном подходе, было то, что HTML был в директиве, поэтому он не чувствовал себя многоразовым.

    Мне понравился предыдущий подход к фильтру, но он не казался эффективным, так как список должен проходить дважды в цикле переваривания. Я имею дело с длинными списками, так что это может быть проблемой. Кроме того, это просто не показалось интуитивным, как простая проверка против предыдущего элемента, чтобы увидеть, изменилось ли оно. Плюс я хотел бы иметь возможность использовать фильтр для нескольких полей легко, который этот новый фильтр обрабатывает только путем соединения с фильтром снова с другим именем поля.

    Еще один комментарий к моему фильтру groupBy – я понимаю, что несколько групп заставляют массив перемещаться несколько раз, поэтому я планирую пересмотреть его, чтобы принять массив из нескольких групп по полям, так что ему нужно только пересечь массив ,

    Большое спасибо за входные данные. Это действительно помогло мне узнать больше о директивах и фильтрах в Angular.

    ура, Дэррил

    Ниже приведено решение на основе директивы, а также ссылка на демонстрацию JSFiddle. Директива позволяет каждому экземпляру указать имя поля элементов, которые он должен группировать, поэтому есть пример с использованием двух разных полей. Он имеет линейное время выполнения в количестве элементов.

    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 есть три директивы, которые помогут вам отображать группы информации. Этими директивами являются ngRepeat, ngRepeatStart и ngRepeatEnd. Я нашел сообщение в блоге, в котором показано, как показывать группы в AngularJS . Суть его в следующем:

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

    Довольно мощные директивы, когда вы узнаете, как их использовать.

    Код JoshMB не будет работать правильно, поскольку у вас есть несколько фильтров на одном наборе данных в одном представлении. Во второй раз, когда вы группируете отфильтрованную версию набора данных, он изменит тот же атрибут в исходном объекте, тем самым сломав групповые перерывы в ранее отфильтрованных версиях.

    Я решил это, добавив имя атрибута «CHANGED» в качестве дополнительного параметра фильтра. Ниже приведена моя обновленная версия кода.

     /* * 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: здесь используется специальный фильтр. Groups создаются функцией фильтра в области видимости для создания массива групп из текущего списка. Добавление / удаление элементов списка свяжет обновление массива групп, когда он сбрасывается каждый цикл дайджеста.

    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
    Давайте будем гением компьютера.