Как сделать 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, просто уточните

16 ответов


можно использовать уникальный фильтр от AngularUI (исходный код доступен здесь:уникальный фильтр AngularUI) и использовать его непосредственно в NG-options (или 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>

или вы можете написать свой собственный фильтр с помощью lodash.

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

вы можете использовать "уникальный" (псевдонимы: uniq) фильтр в угловое.фильтр модуль

использование: 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: мы фильтруем id клиента, i.e удаление дубликатов клиентов

<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
баз 30


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

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

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

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

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

фильтр:

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>

Я решил расширить ответ @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>

обновление

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


ответ

вы можете использовать структура данных стандартного набора 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

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

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

  };
}

Кажется, все бросают свою собственную версию 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]];
      });
    };
  });

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

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

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

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

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

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

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

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

функция return (предметы, 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;
  };

});

добавить такой фильтр:

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>

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

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

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

Так что вы могли бы просто сказать $scope.places.distinct("category");