如何在AngularJS中过滤多个值(进行OR操作)

137
我想使用 Angular 中的 filter 并且想要过滤多个值,如果其中有任意一个则应该显示它。
例如,我有以下结构:
一个对象 movie,它具有属性 genres,我想要过滤 ActionComedy
我知道可以使用 filter:({genres: 'Action'} || {genres: 'Comedy'}),但是如果我想动态过滤呢?例如,filter: variableX
当我需要过滤的类型为数组时,我应该如何在 $scope 中设置 variableX 呢?
我可以将其构造为字符串,然后执行 eval(),但我不想使用 eval()……

17
你确定 filter:({genres: 'Action'} || {genres: 'Comedy'}) 能正常工作吗?在 angular 1.3.16 中它不起作用。 - skilleo
4
1.4.8也一样!它只是不起作用^^ - Alex
20个回答

88

我只需要创建一个自定义过滤器。它们并不难。

angular.module('myFilters', []).
  filter('bygenre', function() {
    return function(movies,genres) {
      var out = [];
      // Filter logic here, adding matches to the out var.
      return out;
    }
  });

模板:

<h1>Movies</h1>

<div ng-init="movies = [
          {title:'Man on the Moon', genre:'action'},
          {title:'Meet the Robinsons', genre:'family'},
          {title:'Sphere', genre:'action'}
       ];" />
<input type="checkbox" ng-model="genrefilters.action" />Action
<br />
<input type="checkbox" ng-model="genrefilters.family" />Family
<br />{{genrefilters.action}}::{{genrefilters.family}}
<ul>
    <li ng-repeat="movie in movies | bygenre:genrefilters">{{movie.title}}: {{movie.genre}}</li>
</ul>

编辑:这里是链接:创建 Angular 过滤器

更新:这里有一个演示,展示了我所建议的内容。


那么,我在输出中返回我想要显示的电影对象? - JustGoscha
抱歉让你久等了,我更新了我的答案并附上了一个具体示例的演示。 - Xesued
好的,谢谢。与此同时,我已经自己解决了,但这肯定会帮助到其他人的 :) - JustGoscha
如果我有两个值,例如活动和非活动,那么如何使用此筛选器分别搜索。我们不想制作自定义筛选器。 - VjyV

82

你可以使用控制器函数进行过滤。

function MoviesCtrl($scope) {

    $scope.movies = [{name:'Shrek', genre:'Comedy'},
                     {name:'Die Hard', genre:'Action'},
                     {name:'The Godfather', genre:'Drama'}];

    $scope.selectedGenres = ['Action','Drama'];

    $scope.filterByGenres = function(movie) {
        return ($scope.selectedGenres.indexOf(movie.genre) !== -1);
    };

}

HTML:

<div ng-controller="MoviesCtrl">
    <ul>
        <li ng-repeat="movie in movies | filter:filterByGenres">
            {{ movie.name }} {{ movie.genre }}
        </li>
    </ul>
</div>

1
除了关注点分离之外,是否有任何性能方面的原因会避免这种情况? - Sean Larkin
1
$scope.filteredMovies = $filter('filter')($scope.movies, $scope.filterByGenres);(如果其他人需要)JavaScript 版本:$scope.filteredMovies = $filter('filter')($scope.movies, $scope.filterByGenres); - Evan

24
创建自定义过滤器可能有些过头了,如果你有多个值,可以只传递一个自定义比较器。例如:
$scope.selectedGenres = "Action, Drama"; 

$scope.containsComparator = function(expected, actual){  
  return actual.indexOf(expected) > -1;
};

然后在过滤器中:

filter:{name:selectedGenres}:containsComparator

1
帮了我很多,谢谢。 - Hristo Enev
@chrismarx 在视图文件中,如何使用 ng-model 与所描述的过滤器进行链接?(抱歉,我还在学习中)-- 我喜欢你的答案的简洁性,并尝试让它工作,但不确定如何标记我的 <input>ng-model。 :) - twknab
你需要阅读这个 - https://docs.angularjs.org/api/ng/filter/filter - chrismarx

18

这是自定义过滤器的实现,它将使用值数组来过滤数据。它支持多键对象,包括键的数组和单个值。如AngularJS filter Doc API中所述,AngularJS支持单个值的多键过滤器,但下面的自定义过滤器将支持与AngularJS相同的功能,并支持值数组和数组与单值键的组合。请参见以下代码片段:

myApp.filter('filterMultiple',['$filter',function ($filter) {
return function (items, keyObj) {
    var filterObj = {
        data:items,
        filteredData:[],
        applyFilter : function(obj,key){
            var fData = [];
            if (this.filteredData.length == 0)
                this.filteredData = this.data;
            if (obj){
                var fObj = {};
                if (!angular.isArray(obj)){
                    fObj[key] = obj;
                    fData = fData.concat($filter('filter')(this.filteredData,fObj));
                } else if (angular.isArray(obj)){
                    if (obj.length > 0){
                        for (var i=0;i<obj.length;i++){
                            if (angular.isDefined(obj[i])){
                                fObj[key] = obj[i];
                                fData = fData.concat($filter('filter')(this.filteredData,fObj));    
                            }
                        }

                    }
                }
                if (fData.length > 0){
                    this.filteredData = fData;
                }
            }
        }
    };
    if (keyObj){
        angular.forEach(keyObj,function(obj,key){
            filterObj.applyFilter(obj,key);
        });
    }
    return filterObj.filteredData;
}
}]);

用法:

arrayOfObjectswithKeys | filterMultiple:{key1:['value1','value2','value3',...etc],key2:'value4',key3:[value5,value6,...etc]}

这里是一个演示如何实现上文提到的自定义过滤器"filterMutiple" 的JSFiddle示例。 :::Fiddle 示例:::


1
你的意图似乎是正确的,但你的例子似乎没有按预期工作。表格中的输出似乎相当不一致。 - Hultner
这不适用于具有嵌套对象的JSON。如果它能工作的话,那就太理想了。同时,控制台会抛出“length属性未定义”的错误。 - SinSync
它对我有效,但如果在列表中找不到搜索的属性,它会返回所有结果。我该怎么做才能反转这种行为并在列表中不存在值时返回无结果? - Zakaria Belghiti
@ZakariaBelghiti 如果你想返回零结果,那么请将代码更改为:this.filteredData = fData;而不是:if (fData.length > 0){ this.filteredData = fData; } - pconnor88
@Gerfried 我认为那个网站实际上是在抓取Stackoverflow的内容,所以他们窃取了他的答案,而不是相反。 - Chris

15
如果您想在对象数组上进行筛选,则可以使用以下方法:filter:({genres: 'Action', key :value }。特定属性将由该属性的过滤器过滤。
但是,如果您希望按单个属性进行过滤,并对所有属性进行全局过滤,则可以尝试以下方法:
<tr ng-repeat="supp in $data | filter : filterObject |  filter : search">
其中,“filterObject”是用于搜索单个属性的对象,“Search”将在全局搜索每个属性。

~Atul


这对我来说效果很好,比其他建议将过滤卸载到函数中更好,因为AngularJS在对字符串进行过滤时执行部分文本匹配。 - Snaver

9

我花了一些时间研究,感谢@chrismarx的帮助,我发现angular的默认filterFilter允许您传递自己的比较器。以下是用于多个值的编辑比较器:

  function hasCustomToString(obj) {
        return angular.isFunction(obj.toString) && obj.toString !== Object.prototype.toString;
  }
  var comparator = function (actual, expected) {
    if (angular.isUndefined(actual)) {
      // No substring matching against `undefined`
      return false;
    }
    if ((actual === null) || (expected === null)) {
      // No substring matching against `null`; only match against `null`
      return actual === expected;
    }
    // I edited this to check if not array
    if ((angular.isObject(expected) && !angular.isArray(expected)) || (angular.isObject(actual) && !hasCustomToString(actual))) {
      // Should not compare primitives against objects, unless they have custom `toString` method
      return false;
    }
    // This is where magic happens
    actual = angular.lowercase('' + actual);
    if (angular.isArray(expected)) {
      var match = false;
      expected.forEach(function (e) {
        e = angular.lowercase('' + e);
        if (actual.indexOf(e) !== -1) {
          match = true;
        }
      });
      return match;
    } else {
      expected = angular.lowercase('' + expected);
      return actual.indexOf(expected) !== -1;
    }
  };

如果我们想为DRY制作自定义过滤器:

angular.module('myApp')
    .filter('filterWithOr', function ($filter) {
      var comparator = function (actual, expected) {
        if (angular.isUndefined(actual)) {
          // No substring matching against `undefined`
          return false;
        }
        if ((actual === null) || (expected === null)) {
          // No substring matching against `null`; only match against `null`
          return actual === expected;
        }
        if ((angular.isObject(expected) && !angular.isArray(expected)) || (angular.isObject(actual) && !hasCustomToString(actual))) {
          // Should not compare primitives against objects, unless they have custom `toString` method
          return false;
        }
        console.log('ACTUAL EXPECTED')
        console.log(actual)
        console.log(expected)

        actual = angular.lowercase('' + actual);
        if (angular.isArray(expected)) {
          var match = false;
          expected.forEach(function (e) {
            console.log('forEach')
            console.log(e)
            e = angular.lowercase('' + e);
            if (actual.indexOf(e) !== -1) {
              match = true;
            }
          });
          return match;
        } else {
          expected = angular.lowercase('' + expected);
          return actual.indexOf(expected) !== -1;
        }
      };
      return function (array, expression) {
        return $filter('filter')(array, expression, comparator);
      };
    });

然后我们可以在任何地方使用它:

$scope.list=[
  {name:'Jack Bauer'},
  {name:'Chuck Norris'},
  {name:'Superman'},
  {name:'Batman'},
  {name:'Spiderman'},
  {name:'Hulk'}
];


<ul>
  <li ng-repeat="item in list | filterWithOr:{name:['Jack','Chuck']}">
    {{item.name}}
  </li>
</ul>

最后这里有一个plunkr

注意:期望的数组应该只包含像StringNumber等简单对象。


谢谢,这对我很有帮助。要检查单个值,请使用filterWithOr:{name: 'Jack'} 如果有人和我需要相同的... - Juan

7
您也可以使用ngIf,如果情况允许的话:
<div ng-repeat="p in [
 { name: 'Justin' }, 
 { name: 'Jimi' }, 
 { name: 'Bob' }
 ]" ng-if="['Jimi', 'Bob'].indexOf(e.name) > -1">
 {{ p.name }} is cool
</div>

1
好的,简单的解决方案! - Leopoldo Sanczyk

7
你可以使用angular.filter的searchField过滤器来进行搜索。 JS:
$scope.users = [
 { first_name: 'Sharon', last_name: 'Melendez' },
 { first_name: 'Edmundo', last_name: 'Hepler' },
 { first_name: 'Marsha', last_name: 'Letourneau' }
];

HTML:

<input ng-model="search" placeholder="search by full name"/> 
<th ng-repeat="user in users | searchField: 'first_name': 'last_name' | filter: search">
  {{ user.first_name }} {{ user.last_name }}
</th>
<!-- so now you can search by full name -->

3
你也可以使用 angular.filter 中的 "where"。 <tr ng-repeat="obj in collection | where:{id: 1}"> {{ obj.name }} </tr> (说明:这段内容是关于 Angular.js 的代码,意思是使用“where”筛选器来过滤集合中 id 属性为 1 的对象,并在页面中显示它们的名称。) - hcarreras
1
搜索到此结束。 - Aakash
1
我尝试了这个,将模块和脚本都设置在我的Angular应用程序中,并在我的index HTML中进行了设置,但我仍然可以搜索所有字段,而不仅仅是我指定的一个 :( - twknab

7

我发现最快速的解决方案是使用angular-filter中的filterBy过滤器,例如:

<input type="text" placeholder="Search by name or genre" ng-model="ctrl.search"/>   
<ul>
  <li ng-repeat="movie in ctrl.movies | filterBy: ['name', 'genre']: ctrl.search">
    {{movie.name}} ({{movie.genre}}) - {{movie.rating}}
  </li>
</ul>

优点是angular-filter是一个相当受欢迎的库(在GitHub上有约2.6k个星),并且仍在积极开发和维护,因此将其作为依赖项添加到您的项目中应该没问题。

1
迄今为止最好的答案。谢谢。 - Daniel Gruszczyk

4
我相信这就是你要找的内容:

我认为这就是你需要的:

<div>{{ (collection | fitler1:args) + (collection | filter2:args) }}</div>

1
它只是连接数组,而不返回一个联合。 - Muaaz Khalid
除非我误解了实现,否则我看不出这是个问题。 - jKlaus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接