ng-repeatで重複した結果を除外する方法


131

ng-repeatJSONファイルを介して単純なを実行していて、カテゴリ名を取得したいと考えています。約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
$ scope.placesを重複排除しないでください。jqueryマップapi.jquery.com/mapを
anazimok

最終的な解決策は何でしたか?それは上か、
それとも重複除外を

これに対する解決策を知りたい。フォローアップを投稿してください。ありがとう!
jdstein1

@ jdstein1 TLDRから始めます。以下の回答を使用するか、バニラJavascriptを使用して、配列内の一意の値のみを除外します。私がしたこと:結局、それは私の論理とMVCに対する私の理解の問題でした。私はMongoDBからデータをロードして、データのダンプを要求し、Angularが魔法のようにそれを魔法のようにユニークな場所だけにフィルタリングすることを望んでいました。よくあるケースのように、解決策は怠惰であるのをやめてDBモデルを修正することでした-私にとっては、db.collection.distinct("places")Angularで行うよりもはるかに優れたMongoを呼び出すことでした!悲しいことに、これは誰にとってもうまくいきません。
JVG 2015

更新していただきありがとうございます!
jdstein1 2015年

回答:


142

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>

33
AngularUIの一意のフィルターを機能させることができない人のために:フィルターは別のモジュールにあります:たとえば、モジュールに追加の参照としてそれを含める必要がありますangular.module('yourModule', ['ui', 'ui.filters']);。AngularUI jsファイルの内部を見るまで困惑しました。
GFoley83 2013

8
uniqueフィルタは、現在の一環として求めることができるAngularJs UI Utilsの
ニック・

2
新しいバージョンのui utilsでは、ui.uniqueを単独で含めることができます。モジュール専用のbower installを使用します。
例による統計の学習2015

AngularUIとその全体を含めたくないが、uniqueフィルターを使用したい場合は、unique.jsソースをコピーしてアプリに貼り付け、angular.module('ui.filters')アプリ名に変更します。
chakeda

37

または、lodashを使用して独自のフィルターを作成することもできます。

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

こんにちはマイク、本当にエレガントに見えますが、unique:status [my field name]のようなフィルターを渡すと期待どおりに動作しないようです。使用可能なすべての一意の像の望ま​​しいリストではなく、最初の結果のみを返します。なぜだと思いますか?
SinSync 14

3
アンダースコアを使用しましたが、このソリューションは魅力的に機能しました。ありがとう!
ジョンブラック

非常にエレガントなソリューション。
JD Smith

1
lodashはアンダースコアの分岐です。それはより良いパフォーマンスとより多くのユーティリティを持っています。
マイクワード

2
lodash V4は_.uniqBy(arr, field);、ネストされた性質のために働く必要があります
ijavid

30

angular.filterモジュールで'unique'(エイリアス: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でフィルタリングします。つまり、重複する顧客を削除します

<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


あなたの答えのようですが、それを私の問題に翻訳するのに苦労しています。ネストされたリストをどのように処理しますか?たとえば、各注文のアイテムのリストを含む注文のリスト。この例では、注文ごとに1人の顧客がいます。すべての注文にわたってアイテムの一意のリストを表示したいと思います。重要なことではありませんが、顧客の注文が多く、注文のアイテムが多いと考えることもできます。顧客が注文した以前のアイテムをすべて表示するにはどうすればよいですか?考え?
グレッグおろし金2014

@GregGrater問題に似たサンプルオブジェクトを提供できますか?
a8m 2014

ありがとう@Ariel M.!ここではデータの例があります:
グレッグおろし金

どのバージョンのangularと互換性がありますか?
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 2014年

オプションをグループ化する場合は、別の質問です。optgroup要素を調べてみてください。Angularはoptgroupをサポートしています。ディレクティブでgroup by式を検索しますselect
Tosh

場所が追加/削除された場合はどうなりますか?関連するカテゴリが唯一のインスタンスである場合、追加/削除されますか?
グレッグおろし金2014

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
ソウル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-repeatは配列でしか機能しないため、これはng-repeatでもMapでも機能しません。したがって、この答えは無視してください。あなたが他の使用して言ったように、1つの方法がある重複を除外する必要がある場合は、とにかく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 () {

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

});

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

個別の関数は、上記で定義されたcontains関数に依存します。それは次のように呼び出すことができる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);
    }
});
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.