108

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.

Talha Awan
  • 4,573
  • 4
  • 25
  • 40
winduptoy
  • 5,366
  • 11
  • 49
  • 67
  • Huh, I'm really confused as to why attaching a "name" object to the input field's `ng-model` (specifying `search.name` in the INPUT field's `ng-model`) would result in the objects to be repeated being filtered by their `name` property? I.e. intuitively to me, you should be able to specifically filter by just `name` by specifying in your `ng-repeat` filter: `filter: friend.name`, instead of ` writing `... – LazerSharks Jul 26 '13 at 21:33

13 Answers13

172

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.

Mistalis
  • 17,793
  • 13
  • 73
  • 97
maxisam
  • 21,975
  • 9
  • 75
  • 84
  • 1
    Hi @maxisam, could you explain what the double vertical lines mean? I'm not really sure how to read this line: `item.brand.indexOf($scope.query)!=-1 || item.model.indexOf($scope.query)!=-1)` – LazerSharks Jul 26 '13 at 21:10
  • 15
    || means OR. You might want to read Javascript, the good parts. – maxisam Jul 27 '13 at 17:24
  • 1
    Speaking of ||, just tried this and it works: . The first filter takes precedence. – Jazzy Nov 16 '13 at 19:22
  • @maxisam - Great post! Very helpful. One question. When I search for 'Apple', I get the results, but not when I type 'apple'. Is there a way to make it non case sensitive? Thank you! – Kate Feb 21 '14 at 18:57
  • 2
    @kate angular.lowercase(item.brand).indexOf ... basically, just lower case everything – maxisam Feb 21 '14 at 19:12
  • Thanks! This works great. To take that question a step further: What if you want to filter multiple specific properties in a single input field.. but then ALSO have multiple input fields like this and allow ALL the inputs to be used at once. Is that a clear question? – Adam Pflantzer Jul 18 '14 at 13:38
  • you need to use filter in the controller in your JS code instead of this way. – maxisam Aug 12 '14 at 17:58
  • 1
    @maxisam. The query string needs to be lowercase as well for the case insensitivity to work correctly. Here is is a **Fork of you plunker** with working case insensitivity. http://plnkr.co/edit/auz02bvWm1Vw4eItSa5J?p=preview Thanks for the filter. – Leopold Kristjansson Jul 23 '15 at 13:50
  • I used this to filter based on date as well, just need to convert the date object to a string before using indexOf to search `var fdate = $filter('date')(item.date, 'shortDate')` for instance – Justin Apr 10 '16 at 19:02
  • 1
    Whew...I searched what seemed like an eternity for that answer. THANK YOU! My specifc use-case was for linking the filtering mechanism to several buttons on a page, each with a different filter criteria. This worked perfectly. – Jim Parker Sep 10 '16 at 19:37
  • That's a great solution thanks, but the function `search` is executed many many times in the case of big lists and its slows the UI, any idea to prevent that ? – Louis Jan 09 '17 at 11:31
  • how would you do it like in this example where even if you type part of a word then click space and start typing a word in another column it still filters accordingly; https://datatables.net/ – Flash Feb 24 '17 at 15:00
  • 1
    I think you have a mistake where you make the 'brand' and 'model' properties lower case, but you are not making the search query itself lower case. So you won't get any matches if at least one character of the search query is in upper case – tonysepia May 01 '17 at 19:23
  • 1
    Also, !$scope.query is not needed – tonysepia May 01 '17 at 19:27
  • https://stackoverflow.com/questions/51083292/how-to-do-ng-repeat-with-filter-using-angularjs – its me Jun 28 '18 at 14:04
80

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.

Anson
  • 6,575
  • 2
  • 39
  • 33
5

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;
    };
NicolasMoise
  • 7,261
  • 10
  • 44
  • 65
5

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
Khashayar
  • 1,321
  • 10
  • 9
3

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]}
Community
  • 1
  • 1
3

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="angular.js@1.4.1" data-semver="1.4.1" src="https://code.angularjs.org/1.4.1/angular.js"></script>
    <script data-require="angular-filter@0.5.2" 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'
      }
      ];
  })

})();
CredibleAshok
  • 73
  • 1
  • 9
2

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">
Siddharth Singh
  • 3,123
  • 2
  • 14
  • 14
  • 1
    I like this solution for dealing with small objects, but if you have a big set of objects with lots of nested stuff, mapping everything can be confusing / easy to make an error. +1 though for a great solution for basic needs...wish this wasn't so complicated to filter for only a few properties instead of all! :/ ...maybe in the future Angular could add some built in functionality for filters of this sort... – twknab Feb 27 '17 at 08:16
2

I solved this simply:

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

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

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

ng-repeat= 'item in list | filter :{property:value}'
Pritam Banerjee
  • 17,953
  • 10
  • 93
  • 108
0

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

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

Raj Nandan Sharma
  • 3,694
  • 3
  • 32
  • 42
-1

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/

Rick
  • 325
  • 1
  • 7
  • How would you implement this type of search? In this example in their table you can type part of a word, hit the space key, and type part of another word in a column, and it still filters accordingly.https://datatables.net/ – Flash Feb 23 '17 at 19:51
  • I don't know why my post was downgraded unless I didn't address the question properly. The code works when you apply it correctly. – Rick Feb 24 '17 at 01:50
  • thanks for your reply rick I don't know who voted down but I can go ahead and vote it back up. However in that example how is that similar to the one in the link I provided where they have a search input field and as they type it filters out. Hers was my origninal post someoune had voted it down as well: http://stackoverflow.com/questions/42421941/angular-custom-search – Flash Feb 24 '17 at 14:42
  • 1
    I've updated the plnkr to show how a textbox filter might work. Notice how it filters on any partial text as expected. I believe this is closer to your linked example. If you need multiple filters (for seperate data pieces) then that's a different question. – Rick Feb 25 '17 at 04:07
  • @Rick I really appreciate you putting time into this example, as I'm currently trying to figure out myself to only filter 2 properties rather than all properties within an object being repeated via `ng-repeat`. Your example however seems a bit complicated: is there any way you can boil it down? I know the question is essentially looking for a single search field that can filter for selected properties only. In your example, it looks like we're filtering for either face value names or numbers, but not both. If I'm incorrect in understanding your example, please let me know! – twknab Feb 27 '17 at 08:33
  • I think you're looking for specifying the columns to filter. In that case, you need to apply another filter like this: ng-repeat="c in deck.Cards | filter: {Type: cardTypes[cardSelected]} | filter: {Name: cardSearch}" and now the textbox will filter the set by name and the dropdown will filter the set by type – Rick Feb 27 '17 at 14:10
  • And that can be then be reduced to: filter: {Type: cardTypes[cardSelected], Name: cardSearch} which will process both checks in the same pass. I've updated the plnkr to the most efficient – Rick Feb 27 '17 at 14:16
  • Thanks Rick yeah the thing is with md-data-table it already has it's own filter it doesn't seem to work when I add a search input field and set the ng model to filter – Flash Feb 27 '17 at 16:09