Как сделать ng-repeat фильтровать повторяющиеся результаты

Я запускаю простой ng-repeat поверх JSON файла и хочу получить имена категорий. Существует около 100 объектов, каждая из которых относится к категории, но всего около 6 категорий.

Мой текущий код:

<select ng-model="orderProp" >
  <option ng-repeat="place in places" value="{{place.category}}">{{place.category}}</option>
</select>

Вывод - это 100 различных опций, в основном дублирующих. Как использовать Angular, чтобы проверить, существует ли {{place.category}}, а не создать параметр, если он уже существует?

edit: В моем javascript, $scope.places = JSON data, просто для уточнения

Ответ 1

Вы можете использовать фильтр уникальный от AngularUI (исходный код доступен здесь: уникальный фильтр AngularUI) и использовать его непосредственно в ng-вариантах (или ng-repeat).

<select ng-model="orderProp" ng-options="place.category for place in places | unique:'category'">
    <option value="0">Default</option>
    // unique options from the categories
</select>

Ответ 2

Или вы можете написать свой собственный фильтр, используя lodash.

app.filter('unique', function() {
    return function (arr, field) {
        return _.uniq(arr, function(a) { return a[field]; });
    };
});

Ответ 3

Вы можете использовать фильтр 'unique' (aliases: uniq) в angular.filter module

использование: colection | uniq: 'property'
вы также можете фильтровать по вложенным свойствам: colection | uniq: 'property.nested_property'

Что вы можете сделать, это что-то вроде этого.

function MainController ($scope) {
 $scope.orders = [
  { id:1, customer: { name: 'foo', id: 10 } },
  { id:2, customer: { name: 'bar', id: 20 } },
  { id:3, customer: { name: 'foo', id: 10 } },
  { id:4, customer: { name: 'bar', id: 20 } },
  { id:5, customer: { name: 'baz', id: 30 } },
 ];
}

HTML: Мы фильтруем по идентификатору клиента, т.е. удаляем дубликатов клиентов

<th>Customer list: </th>
<tr ng-repeat="order in orders | unique: 'customer.id'" >
   <td> {{ order.customer.name }} , {{ order.customer.id }} </td>
</tr>

результат
Список клиентов:
foo 10
бар 20
baz 30

Ответ 4

этот код работает для меня.

app.filter('unique', function() {

  return function (arr, field) {
    var o = {}, i, l = arr.length, r = [];
    for(i=0; i<l;i+=1) {
      o[arr[i][field]] = arr[i];
    }
    for(i in o) {
      r.push(o[i]);
    }
    return r;
  };
})

а затем

var colors=$filter('unique')(items,"color");

Ответ 5

Если вы хотите перечислить категории, я думаю, вы должны явно указать свой намерения в представлении.

<select ng-model="orderProp" >
  <option ng-repeat="category in categories"
          value="{{category}}">
    {{category}}
  </option>
</select>

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

$scope.categories = $scope.places.reduce(function(sum, place) {
  if (sum.indexOf( place.category ) < 0) sum.push( place.category );
  return sum;
}, []);

Ответ 6

UPDATE

Я рекомендовал использовать Set, но жаль, что это не работает для ng-repeat, или Map, поскольку ng-repeat работает только с массивом. Поэтому игнорируйте этот ответ. в любом случае, если вам нужно отфильтровать дубликаты в одном направлении, как сказал другой, используя angular filters, вот ссылка для него в раздел "Начало работы" ..


Старый ответ

Yo может использовать стандарт ECMAScript 2015 (ES6) Установить структуру данных вместо структуры данных массива таким образом, вы фильтруете повторяющиеся значения при добавлении к набору. (Помните, что наборы не позволяют повторять значения). Действительно прост в использовании:

var mySet = new Set();

mySet.add(1);
mySet.add(5);
mySet.add("some text");
var o = {a: 1, b: 2};
mySet.add(o);

mySet.has(1); // true
mySet.has(3); // false, 3 has not been added to the set
mySet.has(5);              // true
mySet.has(Math.sqrt(25));  // true
mySet.has("Some Text".toLowerCase()); // true
mySet.has(o); // true

mySet.size; // 4

mySet.delete(5); // removes 5 from the set
mySet.has(5);    // false, 5 has been removed

mySet.size; // 3, we just removed one value

Ответ 7

Вот простой и общий пример.

Фильтр:

sampleApp.filter('unique', function() {

  // Take in the collection and which field
  //   should be unique
  // We assume an array of objects here
  // NOTE: We are skipping any object which
  //   contains a duplicated value for that
  //   particular key.  Make sure this is what
  //   you want!
  return function (arr, targetField) {

    var values = [],
        i, 
        unique,
        l = arr.length, 
        results = [],
        obj;

    // Iterate over all objects in the array
    // and collect all unique values
    for( i = 0; i < arr.length; i++ ) {

      obj = arr[i];

      // check for uniqueness
      unique = true;
      for( v = 0; v < values.length; v++ ){
        if( obj[targetField] == values[v] ){
          unique = false;
        }
      }

      // If this is indeed unique, add its
      //   value to our values and push
      //   it onto the returned array
      if( unique ){
        values.push( obj[targetField] );
        results.push( obj );
      }

    }
    return results;
  };
})

Разметка:

<div ng-repeat = "item in items | unique:'name'">
  {{ item.name }}
</div>
<script src="your/filters.js"></script>

Ответ 8

Я решил продлить ответ @thethakuri, чтобы разрешить любую глубину для уникального участника. Вот код. Это касается тех, кто не хочет включать весь модуль AngularUI только для этой функции. Если вы уже используете AngularUI, проигнорируйте этот ответ:

app.filter('unique', function() {
    return function(collection, primaryKey) { //no need for secondary key
      var output = [], 
          keys = [];
          var splitKeys = primaryKey.split('.'); //split by period


      angular.forEach(collection, function(item) {
            var key = {};
            angular.copy(item, key);
            for(var i=0; i<splitKeys.length; i++){
                key = key[splitKeys[i]];    //the beauty of loosely typed js :)
            }

            if(keys.indexOf(key) === -1) {
              keys.push(key);
              output.push(item);
            }
      });

      return output;
    };
});

Пример

<div ng-repeat="item in items | unique : 'subitem.subitem.subitem.value'"></div>

Ответ 9

У меня был массив строк, а не объектов, и я использовал этот подход:

ng-repeat="name in names | unique"

с этим фильтром:

angular.module('app').filter('unique', unique);
function unique(){
return function(arry){
        Array.prototype.getUnique = function(){
        var u = {}, a = [];
        for(var i = 0, l = this.length; i < l; ++i){
           if(u.hasOwnProperty(this[i])) {
              continue;
           }
           a.push(this[i]);
           u[this[i]] = 1;
        }
        return a;
    };
    if(arry === undefined || arry.length === 0){
          return '';
    }
    else {
         return arry.getUnique(); 
    }

  };
}

Ответ 10

Кажется, все бросают свою собственную версию фильтра unique в кольцо, поэтому я сделаю то же самое. Критика очень приветствуется.

angular.module('myFilters', [])
  .filter('unique', function () {
    return function (items, attr) {
      var seen = {};
      return items.filter(function (item) {
        return (angular.isUndefined(attr) || !item.hasOwnProperty(attr))
          ? true
          : seen[item[attr]] = !seen[item[attr]];
      });
    };
  });

Ответ 11

Если вы хотите получить уникальные данные на основе вложенного ключа:

app.filter('unique', function() {
        return function(collection, primaryKey, secondaryKey) { //optional secondary key
          var output = [], 
              keys = [];

          angular.forEach(collection, function(item) {
                var key;
                secondaryKey === undefined ? key = item[primaryKey] : key = item[primaryKey][secondaryKey];

                if(keys.indexOf(key) === -1) {
                  keys.push(key);
                  output.push(item);
                }
          });

          return output;
        };
    });

Назовите его следующим образом:

<div ng-repeat="notify in notifications | unique: 'firstlevel':'secondlevel'">

Ответ 12

Создайте свой собственный массив.

<select name="cmpPro" ng-model="test3.Product" ng-options="q for q in productArray track by q">
    <option value="" >Plans</option>
</select>

 productArray =[];
angular.forEach($scope.leadDetail, function(value,key){
    var index = $scope.productArray.indexOf(value.Product);
    if(index === -1)
    {
        $scope.productArray.push(value.Product);
    }
});

Ответ 13

Вот только шаблонный способ сделать это (хотя и не поддерживая порядок). Кроме того, будет также скорректирован результат, который полезен в большинстве случаев:

<select ng-model="orderProp" >
   <option ng-repeat="place in places | orderBy:'category' as sortedPlaces" data-ng-if="sortedPlaces[$index-1].category != place.category" value="{{place.category}}">
      {{place.category}}
   </option>
</select>

Ответ 14

Добавьте этот фильтр:

app.filter('unique', function () {
return function ( collection, keyname) {
var output = [],
    keys = []
    found = [];

if (!keyname) {

    angular.forEach(collection, function (row) {
        var is_found = false;
        angular.forEach(found, function (foundRow) {

            if (foundRow == row) {
                is_found = true;                            
            }
        });

        if (is_found) { return; }
        found.push(row);
        output.push(row);

    });
}
else {

    angular.forEach(collection, function (row) {
        var item = row[keyname];
        if (item === null || item === undefined) return;
        if (keys.indexOf(item) === -1) {
            keys.push(item);
            output.push(row);
        }
    });
}

return output;
};
});

Обновите свою разметку:

<select ng-model="orderProp" >
   <option ng-repeat="place in places | unique" value="{{place.category}}">{{place.category}}</option>
</select>

Ответ 15

Это может быть излишним, но это работает для меня.

Array.prototype.contains = function (item, prop) {
var arr = this.valueOf();
if (prop == undefined || prop == null) {
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] == item) {
            return true;
        }
    }
}
else {
    for (var i = 0; i < arr.length; i++) {
        if (arr[i][prop] == item) return true;
    }
}
return false;
}

Array.prototype.distinct = function (prop) {
   var arr = this.valueOf();
   var ret = [];
   for (var i = 0; i < arr.length; i++) {
       if (!ret.contains(arr[i][prop], prop)) {
           ret.push(arr[i]);
       }
   }
   arr = [];
   arr = ret;
   return arr;
}

Отличительная функция зависит от функции, указанной выше. Его можно назвать array.distinct(prop);, где prop - свойство, которое вы хотите отличать.

Итак, вы можете просто сказать $scope.places.distinct("category");

Ответ 16

Ни один из вышеперечисленных фильтров не исправил мою проблему, поэтому мне пришлось скопировать фильтр из официального github doc. Затем используйте его, как описано в приведенные выше ответы

angular.module('yourAppNameHere').filter('unique', function () {

функция возврата (items, filterOn) {

if (filterOn === false) {
  return items;
}

if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) {
  var hashCheck = {}, newItems = [];

  var extractValueToCompare = function (item) {
    if (angular.isObject(item) && angular.isString(filterOn)) {
      return item[filterOn];
    } else {
      return item;
    }
  };

  angular.forEach(items, function (item) {
    var valueToCheck, isDuplicate = false;

    for (var i = 0; i < newItems.length; i++) {
      if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) {
        isDuplicate = true;
        break;
      }
    }
    if (!isDuplicate) {
      newItems.push(item);
    }

  });
  items = newItems;
}
return items;
  };

});