Як зробити відфільтрувати ng-повторний фільтр з повторюваних результатів


131

Я запускаю простий 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}}вже існує, а не створити опцію, якщо вона вже є?

редагувати: У моєму JavaScript $scope.places = JSON data, просто для уточнення


1
Чому ви просто не дебютуєте свої $ range.places? використовувати jquery map api.jquery.com/map
anazimok

яке було ваше остаточне робоче рішення? ЧИ це вище, чи є якийсь JS, який робить
депінг

Я хочу знати рішення цього. Будь ласка, опублікуйте подальші дії. Дякую!
jdstein1

@ jdstein1 Почну з TLDR: Використовуйте відповіді нижче або використовуйте ванільний Javascript для фільтрації лише унікальних значень у масиві. Що я зробив: врешті-решт це була проблема з моєю логікою та моїм розумінням MVC. Я завантажував дані з MongoDB з проханням скинути дані і хотів, щоб Angular магічно відфільтрував їх до просто унікальних місць. Рішення, як би часто траплялося, полягало в тому, щоб перестати лінуватися і виправити мою модель БД - для мене це називало Монго db.collection.distinct("places"), що було набагато краще, ніж робити це в Angular! На жаль, це працює не для всіх.
JVG

дякую за оновлення!
jdstein1

Відповіді:


142

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

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

33
Для тих, хто не може отримати унікальний фільтр AngularUI, що працює: фільтри знаходяться в окремому модулі: Наприклад, ви повинні включити його як додаткову довідку у свій модуль angular.module('yourModule', ['ui', 'ui.filters']);. Був тупований, поки я не заглянув у файл AngularUI js.
GFoley83

8
На сьогоднішній день uniqueфільтр можна знайти як частину утиліти користувальницького інтерфейсу AngularJs
Nick

2
У новій версії утиліти ui ви можете просто включити ui.unique самостійно. використовувати bower install тільки для модуля.
Статистика навчання за прикладом

Якщо ви не хочете включати AngularUI та його сукупність, але хочете використовувати унікальний фільтр, ви можете просто скопіювати вставити джерело unique.js у свій додаток, а потім змінити його angular.module('ui.filters')на ім’я програми.
чакеда

37

Або ви можете написати власний фільтр за допомогою lodash.

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

Hello Mike, виглядає дуже елегантно, але, здається, не працює так, як очікувалося, коли я передаю фільтр на зразок унікального: статус [моя назва поля] він повертає лише перший результат на відміну від потрібного списку всіх унікальних статуй. Будь-які здогадки, чому?
SinSync

3
Хоча я використовував підкреслення, це рішення спрацювало як шарм. Дякую!
Джон Блек

Дуже елегантне рішення.
JD Smith

1
lodash - це вилка підкреслення. Він має кращу продуктивність та більше комунальних послуг.
Майк Уорд

2
in lodash v4 _.uniqBy(arr, field);повинен працювати для вкладених властивостей
ijavid

30

Ви можете використовувати «унікальний» (псевдоніми: uniq) фільтр у модулі angular.filter

використання: 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
bar 20
baz 30


як ваша відповідь, але важко переклавши це на свою проблему. Як би ви обробляли вкладений список. Наприклад, перелік замовлень, що містив перелік позицій для кожного замовлення. У вашому прикладі у вас є один клієнт на замовлення. Я хотів би побачити унікальний перелік товарів у всіх замовленнях. Справа не в тому, але ви можете також думати про це, оскільки у замовника багато замовлень, а в замовлення є багато предметів. Як я можу показати всі попередні товари, які замовив клієнт? Думки?
Грег Гратер

@GregGrater Чи можете ви навести приклад об’єкта, подібного до вашої проблеми?
a8m

Дякую @Ariel M.! Ось приклад даних:
Грег Гратер,

З якими версіями кутових він сумісний?
Icet

15

цей код працює для мене.

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

2
Визначення l повинно бути l = arr! = Undefined? arr.length: 0, оскільки в іншому випадку є помилка розбору в angularjs
Герріт,

6

Якщо ви хочете перерахувати категорії, я думаю, ви повинні прямо вказати свій намір у поданні.

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

ви могли б пояснити це трохи більше. Я хочу повторити ті елементи підряд для кожної унікальної категорії. Чи просто JS переходить у файл JS, де визначено контролер?
mark1234

Якщо ви хочете згрупувати варіанти, це інше питання. Ви можете заглянути в елемент optgroup. Кутові опори optgroup. пошук group byвираження в selectдирективі.
Тош

Чи станеться в тому випадку, якщо місце додається / видаляється? Чи буде додана / видалена пов’язана категорія, якщо вона є єдиним примірником місцями?
Грег Гратер

4

Ось простий та загальний приклад.

Фільтр:

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>

Це прекрасно працює, але це призводить до помилки в консолі Cannot read property 'length' of undefinedв рядкуl = arr.length
Soul Eeater

4

Я вирішив поширити відповідь @ 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>

2

У мене був масив рядків, а не об'єктів, і я використовував такий підхід:

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

  };
}

2

ОНОВЛЕННЯ

Я рекомендував використовувати Set, але вибачте, що це не працює ні для ng-повторів, ні для 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

2

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

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

2

Жоден із перерахованих вище фільтрів не вирішив мою проблему, тому мені довелося скопіювати фільтр з офіційного github doc. А потім використовуйте його, як пояснено у наведених вище відповідях

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

функція повернення (елементи, 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;
  };

});

1

Здається, кожен кидає 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]];
      });
    };
  });

1

Якщо ви хочете отримати унікальні дані на основі вкладеного ключа:

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

0

Додати цей фільтр:

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>

0

Це може бути зайвим, але це працює для мене.

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);де опора - це властивість, яку ви хочете виділити.

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


0

Створіть власний масив.

<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);
    }
});
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.