28

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

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

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

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

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

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

  • GHI
  • JKL
  • MnO

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

Любые советы приветствуются.

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

Дэррил

+0

Это интересная проблема. Ваш предоставленный пример создаст избыточные группы, если группировки не будут смежными. Другими словами, если мы, например, добавили еще один элемент в конце, который находится в группе 1, будет создан дополнительный заголовок группы 1. Является ли это преднамеренным, или они должны быть сгруппированы, как следует из названия? Если вы поясните это, я могу предоставить директивное решение. Вы правы, что директива - это путь. – jelinson

+0

Всякий раз, когда групповое использование используется в программировании, порядок данных, конечно, влияет на него, как вы описали. Я сделал мой пример максимально простым, поэтому предположим, что данные отсортированы правильно. – Darryl

+0

Вы проверили угловой фильтр? https://github.com/a8m/angular-filter#groupby – hcarreras

ответ

34

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

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

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> 

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

Awesome! Спасибо за улучшение. Почему вы изменили «var new_field = group_by +» _CHANGED '; на "var new_field = 'group_by_CHANGED';"? Кажется, что вам нелегко будет увидеть, какое поле изменилось в представлении. – Darryl

+0

В моей ситуации мне было все равно, какое поле было изменено ... Если кто-то из них был изменен, мне нужен новый заголовок. – JoshMB

+0

Сегодня я работал с этим кодом и обнаружил, что он не работает хорошо в нескольких отфильтрованных версиях одного и того же набора данных. Это можно решить, добавив дополнительный параметр фильтра с именем атрибута, где должна быть сохранена эта отфильтрованная версия group_CHANGED (см. Полный код ниже в моем ответе). – Matthijs

0

EDIT: вот пользовательский фильтр подход. Groups создается функцией фильтра в области видимости для создания массива групп из текущего списка. Добавление/удаление элементов списка свяжет обновление массива групп, когда он сбрасывается каждый цикл дайджеста.

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

+0

Я подумал об этом, но если позже вы захотите сгруппировать по другому полю, вам придется повторно отобразить данные. Групповая информация является общей потребностью при представлении данных, поэтому я хотел бы найти средство для решения этой проблемы. Я также надеюсь понять, как этот опытный разработчик Angular увидит эту проблему. Это что-то, что нужно решить с помощью директивы, с фильтром, с функцией RootScope или чем-то еще? – Darryl

+0

также можно использовать переназначение данных – charlietfl

+0

Есть ли способ сделать это без переназначения данных? И/или с функцией, которая была бы повторно использована? – Darryl

1

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

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

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

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

Адрес электронной почты: 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> 

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

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

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

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

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

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

веселит, Дэррил

0

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> 
+0

Это не сработает, если вы добавите orderBy для повтора. Причина в том, что индекс соответствует исходному порядку массива, а не порядку заказа orderBy, поэтому это решение довольно ограничено. – Darryl

-2
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 
+0

аналогичное решение было предложено выше. проблема в том, что он не будет работать, если вы добавите orderBy или любой тип фильтра, поскольку массив поддерживает исходный индекс. – Darryl

+0

сложность этого метода меньше, и упоминается ли это, что заказ должен поддерживаться? Если да, то почему? – kartik

+0

Моя потребность была в использовании функции многократной библиотеки, которая будет работать во всем приложении, поэтому для работы с отфильтрованными данными было основным требованием. Я отказался от вашего решения просто потому, что, похоже, вы не читали предыдущие решения, чтобы убедиться, что он почти идентичен предыдущему сообщению (без первого ng-init). – Darryl

1

AngularJS имеет три директивы, чтобы помочь вам отобразить группы информации. Этими директивами являются ngRepeat, ngRepeatStart и ngRepeatEnd. Я нашел сообщение в блоге, которое показывает, как show groups in AngularJS. Суть его что-то вроде этого:

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

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

1

Код 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  <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; 
    }; 
}]); 
23

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


В контроллере:

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

В шаблоне:

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

Это, оказывает:

Engl ISH

  • Край Завтра
  • X-MEN

телугу

  • Gabbar Singh 2
  • RESU Gurram

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

Update: Группа несколько ключей

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

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

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

+0

Я использую LoDash, и это очень полезный и практичный ответ. – Darryl

+0

красивое решение, спасибо! –

+0

плюс один для underscore.js. Это отличная библиотека и хорошо рекомендованная – dreftymac

Смежные вопросы