Filtering by Multiple Specific Model Properties in AngularJS (in OR relationship)

Angularjs

Angularjs Problem Overview


Take a look at the example here: http://docs.angularjs.org/api/ng.filter:filter

You can search by any of the phone properties by using <input ng-model="search"> and you can search by just the name by using <input ng-model="search.name">, and the results are appropriately filtered by name (typing in a phone number does not return any results, as expected).

Let's say I have a model with a "name" property, a "phone" property, and a "secret" property, how would I go about filtering by both the "name" and "phone" properties and not the "secret" property? So in essence, the user could type a name or phone number and the ng-repeat would filter correctly, but even if the user typed in a value that equaled part of a "secret" value, it wouldn't return anything.

Thanks.

Angularjs Solutions


Solution 1 - Angularjs

Here is the plunker

New plunker with cleaner code & where both the query and search list items are case insensitive

Main idea is create a filter function to achieve this purpose.

From official doc

> function: A predicate function can be used to write arbitrary filters. > The function is called for each element of array. The final result is > an array of those elements that the predicate returned true for.

<input ng-model="query">

<tr ng-repeat="smartphone in smartphones | filter: search "> 

$scope.search = function(item) {
    if (!$scope.query || (item.brand.toLowerCase().indexOf($scope.query) != -1) || (item.model.toLowerCase().indexOf($scope.query.toLowerCase()) != -1) ){
        return true;
    }
    return false;
};

Update

Some people might have a concern on performance in real world, which is correct.

In real world, we probably would do this kinda filter from controller.

Here is the detail post showing how to do it.

in short, we add ng-change to input for monitoring new search change

and then trigger filter function.

Solution 2 - Angularjs

You can pass an Object as the parameter to your filter expression, as described in the API Reference. This object can selectively apply the properties you're interested in, like so:

<input ng-model="search.name">
<input ng-model="search.phone">
<input ng-model="search.secret">
<tr ng-repeat="user in users | filter:{name: search.name, phone: search.phone}">

Here's a Plunker

Heads up...this example works great with AngularJS 1.1.5, but not always as well in 1.0.7. In this example 1.0.7 will initialize with everything filtered out, then work when you start using the inputs. It behaves like the inputs have non-matching values in them, even though they start out blank. If you want to stay on stable releases, go ahead and try this out for your situation, but some scenarios may want to use @maxisam's solution until 1.2.0 is released.

Solution 3 - Angularjs

I inspired myself from @maxisam's answer and created my own sort function and I'd though I'd share it (cuz I'm bored).

Situation I want to filter through an array of cars. The selected properties to filter are name, year, price and km. The property price and km are numbers (hence the use of .toString). I also want to control for uppercase letters (hence .toLowerCase). Also I want to be able to split up my filter query into different words (e.g. given the filter 2006 Acura, it finds matches 2006 with the year and Acura with the name).

Function I pass to filter

		var attrs = [car.name.toLowerCase(), car.year, car.price.toString(), car.km.toString()],
			filters = $scope.tableOpts.filter.toLowerCase().split(' '),
			isStringInArray = function (string, array){
				for (var j=0;j<array.length;j++){
					if (array[j].indexOf(string)!==-1){return true;}
				}
				return false;
			};

		for (var i=0;i<filters.length;i++){
			if (!isStringInArray(filters[i], attrs)){return false;}
		}
		return true;
	};

Solution 4 - Angularjs

If you're open to use third party libraries,'Angular Filters' with a nice collection of filters may be useful:

https://github.com/a8m/angular-filter#filterby

collection | filterBy: [prop, nested.prop, etc..]: search

Solution 5 - Angularjs

Hope this answer will help,Multiple Value Filter

And working example in this fiddle

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

Solution 6 - Angularjs

There are a bunch of good solutions here but I'd suggest going the other route with this. If searching is the most important thing and the 'secret' property is not used on the view at all; my approach for this would be to use the built-in angular filter with all its benefits and simply map the model to a new array of objects.

Example from the question:

$scope.people = members.map(function(member) { 
                              return { 
                                name: member.name, 
                                phone: member.phone
                              }});

Now, in html simply use the regular filter to search both these properties.

<div ng-repeat="member in people | filter: searchString">

Solution 7 - Angularjs

Pretty straight forward approach using filterBy in angularJs

For working plnkr follow this link

http://plnkr.co/edit/US6xE4h0gD5CEYV0aMDf?p=preview

This has specially used a single property to search against multiple column but not all.

<!DOCTYPE html>
<html ng-app="myApp">

  <head>
    <script data-require="[email protected]" data-semver="1.4.1" src="https://code.angularjs.org/1.4.1/angular.js"></script>
    <script data-require="[email protected]" data-semver="0.5.2" src="https://cdnjs.cloudflare.com/ajax/libs/angular-filter/0.5.2/angular-filter.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body ng-controller="myCtrl as vm">
  <input type="text" placeholder="enter your search text" ng-model="vm.searchText" />
   <table>
     <tr ng-repeat="row in vm.tableData | filterBy: ['col1','col2']: vm.searchText">
       <td>{{row.col1}}</td>
       <td>{{row.col2}}</td>
       <td>{{row.col3}}</td>
     </tr>
   </table>
   <p>This will show filter from <b>two columns</b> only(col1 and col2) . Not from all. Whatever columns you add into filter array they
   will be searched. all columns will be used by default if you use <b>filter: vm.searchText</b></p>
  </body>
</html>

controller

// Code goes here
(function(){
  
  var myApp = angular.module('myApp',['angular.filter']);
  
  myApp.controller('myCtrl', function($scope){
    var vm= this;
    vm.x = 20;
    
    vm.tableData = [
      {
        col1: 'Ashok',
        col2: 'Tewatia',
        col3: '12'
      },{
        col1: 'Babita',
        col2: 'Malik',
        col3: '34'
      },{
        col1: 'Dinesh',
        col2: 'Tewatia',
        col3: '56'
      },{
        col1: 'Sabita',
        col2: 'Malik',
        col3: '78'
      }
      ];
  })
  
})();

Solution 8 - Angularjs

I solved this simply:

<div ng-repeat="Object in List | filter: (FilterObj.FilterProperty1 ? {'ObjectProperty1': FilterObj.FilterProperty1} : '') | filter:(FilterObj.FilterProperty2 ? {'ObjectProperty2': FilterObj.FilterProperty2} : '')">

Solution 9 - Angularjs

http://plnkr.co/edit/A2IG03FLYnEYMpZ5RxEm?p=preview

Here is a case sensitive search that also separates your search into words to search in each model as well. Only when it finds a space will it try to split the query into an array and then search each word.

It returns true if every word is at least in one of the models.

$scope.songSearch = function (row) {
    var query = angular.lowercase($scope.query);
    if (query.indexOf(" ") > 0) {
        query_array = query.split(" ");
        search_result = false;
        for (x in query_array) {
            query = query_array[x];
            if (angular.lowercase(row.model1).indexOf(query || '') !== -1 || angular.lowercase(row.model2).indexOf(query || '') !== -1 || angular.lowercase(row.model3).indexOf(query || '') !== -1){
                search_result = true;
            } else {
                search_result = false;
                break;
            }
        }
        return search_result;
    } else {
        return (angular.lowercase(row.model1).indexOf(query || '') !== -1 || angular.lowercase(row.model2).indexOf(query || '') !== -1 || angular.lowercase(row.model3).indexOf(query || '') !== -1);
    }
};

Solution 10 - Angularjs

Filter can be a JavaScript object with fields and you can have expression as:

ng-repeat= 'item in list | filter :{property:value}'

Solution 11 - Angularjs

I like to keep is simple when possible. I needed to group by International, filter on all the columns, display the count for each group and hide the group if no items existed.

Plus I did not want to add a custom filter just for something simple like this.

        <tbody>
            <tr ng-show="fusa.length > 0"><td colspan="8"><h3>USA ({{fusa.length}})</h3></td></tr>
            <tr ng-repeat="t in fusa = (usa = (vm.assignmentLookups | filter: {isInternational: false}) | filter: vm.searchResultText)">
                <td>{{$index + 1}}</td>
                <td ng-bind-html="vm.highlight(t.title, vm.searchResultText)"></td>
                <td ng-bind-html="vm.highlight(t.genericName, vm.searchResultText)"></td>
                <td ng-bind-html="vm.highlight(t.mechanismsOfAction, vm.searchResultText)"></td>
                <td ng-bind-html="vm.highlight(t.diseaseStateIndication, vm.searchResultText)"></td>
                <td ng-bind-html="vm.highlight(t.assignedTo, vm.searchResultText)"></td>
                <td ng-bind-html="t.lastPublished | date:'medium'"></td>
            </tr>
        </tbody>
        <tbody>
            <tr ng-show="fint.length > 0"><td colspan="8"><h3>International ({{fint.length}})</h3></td></tr>
            <tr ng-repeat="t in fint = (int = (vm.assignmentLookups | filter: {isInternational: true}) | filter: vm.searchResultText)">
                <td>{{$index + 1}}</td>
                <td ng-bind-html="vm.highlight(t.title, vm.searchResultText)"></td>
                <td ng-bind-html="vm.highlight(t.genericName, vm.searchResultText)"></td>
                <td ng-bind-html="vm.highlight(t.mechanismsOfAction, vm.searchResultText)"></td>
                <td ng-bind-html="vm.highlight(t.diseaseStateIndication, vm.searchResultText)"></td>
                <td ng-bind-html="vm.highlight(t.assignedTo, vm.searchResultText)"></td>
                <td ng-bind-html="t.lastPublished | date:'medium'"></td>
            </tr>
        </tbody>

Solution 12 - Angularjs

I might be very late but this is what I ended up doing. I made a very general filter.

angular.module('app.filters').filter('fieldFilter', function() {
		return function(input, clause, fields) {
			var out = [];
			if (clause && clause.query && clause.query.length > 0) {
				clause.query = String(clause.query).toLowerCase();
				angular.forEach(input, function(cp) {
					for (var i = 0; i < fields.length; i++) {
						var haystack = String(cp[fields[i]]).toLowerCase();
						if (haystack.indexOf(clause.query) > -1) {
							out.push(cp);
							break;
						}
					}
				})
			} else {
				angular.forEach(input, function(cp) {
					out.push(cp);
				})
			}
			return out;
		}

	})

Then use it like this

<tr ng-repeat-start="dvs in devices |  fieldFilter:search:['name','device_id']"></tr>

Your search box be like

<input ng-model="search.query" class="form-control material-text-input" type="text">

where name and device_id are properties in dvs

Solution 13 - Angularjs

Here's simple solution for those who want a quick filter against an object:

<select>
  <option ng-repeat="card in deck.Cards | filter: {Type: 'Face'}">{{card.Name}}</option>
</select>

The array filter lets you mimic the object you are trying to filter. In the above case, the following classes would work just fine:

var card = function(name, type) {
  var _name = name;
  var _type = type;

  return {
    Name: _name,
    Type: _type
  };
};

And where the deck might look like:

var deck = function() {
  var _cards = [new card('Jack', 'Face'),
                new card('7', 'Numeral')];

  return {
    Cards: _cards
  };
};

And if you want to filter multiple properties of the object just separate field names by a comma:

<select>
  <option ng-repeat="card in deck.Cards | filter: {Type: 'Face', Name: 'Jack'}">{{card.Name}}</option>
</select>

EDIT: Here's a working plnkr that provides an example of single and multiple property filters:

http://embed.plnkr.co/i0wCx6l1WPYljk57SXzV/

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionwinduptoyView Question on Stackoverflow
Solution 1 - AngularjsmaxisamView Answer on Stackoverflow
Solution 2 - AngularjsAnsonView Answer on Stackoverflow
Solution 3 - AngularjsNicolasMoiseView Answer on Stackoverflow
Solution 4 - AngularjsKhashayarView Answer on Stackoverflow
Solution 5 - AngularjsNirmal Kumar VeluPillaiView Answer on Stackoverflow
Solution 6 - AngularjsSiddharth SinghView Answer on Stackoverflow
Solution 7 - AngularjsCredibleAshokView Answer on Stackoverflow
Solution 8 - AngularjsGeovani AnholeteView Answer on Stackoverflow
Solution 9 - AngularjsTom QuinlanView Answer on Stackoverflow
Solution 10 - AngularjsGholamreza FathpourView Answer on Stackoverflow
Solution 11 - AngularjsLouis RebollosoView Answer on Stackoverflow
Solution 12 - AngularjsRaj Nandan SharmaView Answer on Stackoverflow
Solution 13 - AngularjsRickView Answer on Stackoverflow