How to filter multiple values (OR operation) in angularJS
Asked Answered
G

20

137

I want to use the filter in angular and want to filter for multiple values, if it has either one of the values then it should be displayed.

I have for example this structure:

An object movie which has the property genres and I want to filter for Action and Comedy.

I know I can do filter:({genres: 'Action'} || {genres: 'Comedy'}), but what to do if I want to filter it dynamically. E.g. filter: variableX

How do I set variableX in the $scope, when I have an array of the genres I have to filter?

I could construct it as a string and then do an eval() but I don't want to use eval()...

Grovel answered 7/4, 2013 at 21:48 Comment(2)
are you sure that filter:({genres: 'Action'} || {genres: 'Comedy'}) even works? it doesn't in angular 1.3.16.Griffe
same for 1.4.8 ! it's just does not work ^^Faris
S
88

I would just create a custom filter. They are not that hard.

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

template:

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

Edit here is the link: Creating Angular Filters

UPDATE: Here is a fiddle that has an exact demo of my suggestion.

Savdeep answered 8/4, 2013 at 0:16 Comment(4)
so then in the out I return the movie objects that I want to display?Grovel
Sorry it took me awhile to get back. I updated my answer with a fiddle of a concrete example.Savdeep
ok, thanks, in the meantime I figured it out myself, but it will sure help someone later :)Grovel
Either i have two value such like active and inactive then how can i search seprately using this filter. we do not want to make custom filter.French
S
82

You can use a controller function to filter.

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>
Segregate answered 8/4, 2013 at 0:53 Comment(2)
Besides separation of concerns, is there any reason (performance wise) that this would be avoided?Riegel
javascript version if others are looking for it: $scope.filteredMovies = $filter('filter')($scope.movies, $scope.filterByGenres);Dimmer
G
24

Creating a custom filter might be overkill here, you can just pass in a custom comparator, if you have the multiples values like:

$scope.selectedGenres = "Action, Drama"; 

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

then in the filter:

filter:{name:selectedGenres}:containsComparator
Goodell answered 3/9, 2013 at 14:9 Comment(3)
Helped me a lot. ThanksInopportune
@Goodell What would the ng-model be in the view file to link up to the described filter? (sorry, still learning here) -- I like the simplicity of your answer and trying to get it work but not sure how to label my <input>'s ng-model. :)Vulcanism
you need to read this - docs.angularjs.org/api/ng/filter/filterGoodell
C
18

Here is the implementation of custom filter, which will filter the data using array of values.It will support multiple key object with both array and single value of keys. As mentioned inangularJS API AngularJS filter Doc supports multiple key filter with single value, but below custom filter will support same feature as angularJS and also supports array of values and combination of both array and single value of keys.Please find the code snippet below,

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

Usage:

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

Here is a fiddle example with implementation of above "filterMutiple" custom filter. :::Fiddle Example:::

Chap answered 16/1, 2014 at 18:10 Comment(5)
You seem to have the correct intention but your example doesn't seem to work as expected. The output in the table seems rather inconsistent.Clari
This does not work with JSON's with nested objects. Would've been ideal if it did. also it fires a length property undefined error in the console.Monika
It is working for me but it returns all the results if it doesn't find the searched attribute in the list, how can I do to invert this behavior and return no result if the value doesn't exist in the list?Children
@ZakariaBelghiti if you want to return no results, then change the code: if (fData.length > 0){ this.filteredData = fData; } to just: this.filteredData = fData;Neoma
@Gerfried I think that site actually scrapes Stackoverflow, so they stole his answer, not the other way aroundSerration
E
15

If you want to filter on Array of Objects then you can give

filter:({genres: 'Action', key :value }.

Individual property will be filtered by particular filter given for that property.

But if you wanted to something like filter by individual Property and filter globally for all properties then you can do something like this.

<tr ng-repeat="supp in $data | filter : filterObject |  filter : search">
Where "filterObject" is an object for searching an individual property and "Search" will search in every property globally.

~Atul

Enchain answered 18/9, 2014 at 18:14 Comment(1)
This worked well for me and was a better solution than others suggesting offloading the filtering to a function as AngularJS does partial text matches when filtering on strings.Ecstatics
D
9

I've spent some time on it and thanks to @chrismarx, I saw that angular's default filterFilter allows you to pass your own comparator. Here's the edited comparator for multiple values:

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

And if we want to make a custom filter for 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);
      };
    });

And then we can use it anywhere we want:

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

Finally here's a plunkr.

Note: Expected array should only contain simple objects like String, Number etc.

Disulfide answered 23/6, 2015 at 15:6 Comment(1)
Thanks this helped me. To check for a single value filterWithOr:{name:'Jack'} If anyone need the same as me...Shurwood
H
7

you can use searchField filter of angular.filter

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 -->
Hereunto answered 23/7, 2014 at 6:54 Comment(3)
You can also use "where" of angular.filter <tr ng-repeat="obj in collection | where:{id: 1}"> {{ obj.name }} </tr>Stacey
the search stops here.Kawasaki
I tried this and setup both the module in my angular app and the script in my index HTML but I'm still able to search for ALL fields, not just the one I specified :(Vulcanism
K
7

You can also use ngIf if the situation permits:

<div ng-repeat="p in [
 { name: 'Justin' }, 
 { name: 'Jimi' }, 
 { name: 'Bob' }
 ]" ng-if="['Jimi', 'Bob'].indexOf(e.name) > -1">
 {{ p.name }} is cool
</div>
Kare answered 28/9, 2015 at 19:56 Comment(1)
Nice and simple solution!Gaol
R
7

The quickest solution that I've found is to use the filterBy filter from angular-filter, for example:

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

The upside is that angular-filter is a fairly popular library (~2.6k stars on GitHub) which is still actively developed and maintained, so it should be fine to add it to your project as a dependency.

Redeem answered 9/9, 2016 at 23:18 Comment(1)
By far the best answer. Thanks.Dinnerware
S
4

I believe this is what you're looking for:

<div>{{ (collection | fitler1:args) + (collection | filter2:args) }}</div>
Southsoutheast answered 11/7, 2014 at 14:17 Comment(2)
It just concatenate the arrays and don't return a union.Motorboating
Unless I misunderstood the implementation I fail to see how that is a problem.Southsoutheast
D
2

Lets assume you have two array, one for movie and one for genre

Just use the filter as: filter:{genres: genres.type}

Here genres being the array and type has value for genre

Distress answered 11/3, 2015 at 19:48 Comment(0)
S
2

Please try this

var m = angular.module('yourModuleName');
m.filter('advancefilter', ['$filter', function($filter){
    return function(data, text){
        var textArr = text.split(' ');
        angular.forEach(textArr, function(test){
            if(test){
                  data = $filter('filter')(data, test);
            }
        });
        return data;
    }
}]);
Sublimity answered 24/6, 2015 at 10:51 Comment(0)
G
1

I wrote this for strings AND functionality (I know it's not the question but I searched for it and got here), maybe it can be expanded.

String.prototype.contains = function(str) {
  return this.indexOf(str) != -1;
};

String.prototype.containsAll = function(strArray) {
  for (var i = 0; i < strArray.length; i++) {
    if (!this.contains(strArray[i])) {
      return false;
    }
  }
  return true;
}

app.filter('filterMultiple', function() {
  return function(items, filterDict) {
    return items.filter(function(item) {
      for (filterKey in filterDict) {
        if (filterDict[filterKey] instanceof Array) {
          if (!item[filterKey].containsAll(filterDict[filterKey])) {
            return false;
          }
        } else {
          if (!item[filterKey].contains(filterDict[filterKey])) {
            return false;
          }
        }
      }
      return true;
    });  
  };
});

Usage:

<li ng-repeat="x in array | filterMultiple:{key1: value1, key2:[value21, value22]}">{{x.name}}</li>
Gimel answered 30/10, 2014 at 1:2 Comment(0)
G
1

Angular Or Filter Module

$filter('orFilter')([{..}, {..} ...], {arg1, arg2, ...}, false)

here is the link: https://github.com/webyonet/angular-or-filter

Goldie answered 10/12, 2014 at 14:9 Comment(0)
M
1

I had similar situation. Writing custom filter worked for me. Hope this helps!

JS:

App.filter('searchMovies', function() {
    return function (items, letter) {
        var resulsts = [];
        var itemMatch = new RegExp(letter, 'i');
        for (var i = 0; i < items.length; i++) {
            var item = items[i];
            if ( itemMatch.test(item.name) || itemMatch.test(item.genre)) {
                results.push(item);
            }
        }
        return results;
    };
});

HTML:

<div ng-controller="MoviesCtrl">
    <ul>
        <li ng-repeat="movie in movies | searchMovies:filterByGenres">
            {{ movie.name }} {{ movie.genre }}
        </li>
    </ul>
</div>
Mcfarland answered 30/6, 2015 at 18:57 Comment(0)
B
1

Here is my example how create filter and directive for table jsfiddle

directive get list (datas) and create table with filters

<div ng-app="autoDrops" ng-controller="HomeController">
<div class="row">
    <div class="col-md-12">
        <h1>{{title}}</h1>
        <ng-Multiselect array-List="datas"></ng-Multiselect>
    </div>
</div>
</div>

my pleasure if i help you

Bowshot answered 8/8, 2016 at 10:17 Comment(0)
C
1

Too late to join the party but may be it can help someone:

We can do it in two step, first filter by first property and then concatenate by second filter:

$scope.filterd = $filter('filter')($scope.empList, { dept: "account" });
$scope.filterd = $scope.filterd.concat($filter('filter')($scope.empList, { dept: "sales" }));  

See the working fiddle with multiple property filter

Christachristabel answered 24/5, 2017 at 20:19 Comment(0)
O
0

OPTION 1: Using Angular providered filter comparator parameter

// declaring a comparator method
$scope.filterBy = function(actual, expected) {
    return _.contains(expected, actual); // uses underscore library contains method
};

var employees = [{name: 'a'}, {name: 'b'}, {name: 'c'}, {name: 'd'}];

// filter employees with name matching with either 'a' or 'c'
var filteredEmployees = $filter('filter')(employees, {name: ['a','c']}, $scope.filterBy);

OPTION 2: Using Angular providered filter negation

var employees = [{name: 'a'}, {name: 'b'}, {name: 'c'}, {name: 'd'}];

// filter employees with name matching with either 'a' or 'c'
var filteredEmployees = $filter('filter')($filter('filter')(employees, {name: '!d'}), {name: '!b'});
Offing answered 28/12, 2018 at 18:38 Comment(0)
E
-3

My solution

ng-repeat="movie in movies | filter: {'Action'} + filter: {'Comedy}"
Euryale answered 18/6, 2015 at 17:45 Comment(1)
checked on VS 2015, it doesn't get the syntax and it doesn't work alsoEncourage
B
-14

the best answer is :

filter:({genres: 'Action', genres: 'Comedy'}
Belter answered 21/3, 2014 at 9:9 Comment(2)
Does this not just filter on 'Comedy'?Oxyhydrogen
I tried this and it only filters on 'Comedy', not on 'Action'.Eaglestone

© 2022 - 2024 — McMap. All rights reserved.