Подтвердить что ты не робот

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

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

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

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

<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(), которая просто сравнивает текущий с предыдущим, кроме первого элемента, и возвращает 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

  • ABC
  • Защита

Группа 2

  • ГХИ
  • JKL
  • mno

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

Приветствуются любые советы.

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

Дэррил

4b9b3361

Ответ 1

Это модификация решения Darryl выше, что позволяет несколько групповых параметров. Кроме того, он использует $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;
    };
}]);

Ответ 2

Если вы уже используете 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>

Это отобразит:

английский

  • Edge of Tomorrow
  • X-MEN

Телугу

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

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

Обновление: Группировать по нескольким клавишам

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

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

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

Ответ 3

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

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

Ответ 4

Ниже представлено решение на основе директивы, а также ссылка на демонстрацию 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

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

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

Ответ 6

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

Ответ 7

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

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

Ответ 8

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>  

Ответ 9

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