452

I have a data array which contains many objects (JSON format). The following can be assumed as the contents of this array:

var data = [
  {
    "name": "Jim",
    "age" : 25
  },
  {
    "name": "Jerry",
    "age": 27
  }
];

Now, I display these details as:

<div ng-repeat="person in data | filter: query">
</div

Here, query is modeled to an input field in which the user can restrict the data displayed.

Now, I have another location in which I display the current count of people / person being display, i.e Showing {{data.length}} Persons

What I want to do is that when the user searches for a person and the data displayed is filtered based on the query, the Showing...persons also change the value of people being shown currently. But it is not happening. It always displays the total persons in data rather than the filtered one - how do I get the count of filtered data?

isherwood
  • 58,414
  • 16
  • 114
  • 157
user109187
  • 5,265
  • 7
  • 22
  • 25

8 Answers8

757

For Angular 1.3+ (credits to @Tom)

Use an alias expression (Docs: Angular 1.3.0: ngRepeat, scroll down to the Arguments section):

<div ng-repeat="person in data | filter:query as filtered">
</div>

For Angular prior to 1.3

Assign the results to a new variable (e.g. filtered) and access it:

<div ng-repeat="person in filtered = (data | filter: query)">
</div>

Display the number of results:

Showing {{filtered.length}} Persons

Fiddle a similar example. Credits go to Pawel Kozlowski

Wumms
  • 8,176
  • 2
  • 19
  • 18
  • 46
    This is the simple answer that doesn't repeat filter execution. – agmin Jan 22 '14 at 18:24
  • 1
    What if I now need to access the value of `filtered.length` inside of my controller? Is there any way to do that? – Ben Mar 18 '14 at 11:29
  • 3
    @Ben: Yes. You can access it inside the controller using `$scope.filtered.length`. – Wumms Mar 18 '14 at 11:53
  • 2
    That doesn't work for me. Logs `undefined`, likely because at the time of logging it, the variable isn't defined yet. So how would I ensure the code in the controller is run *after* the variable was defined and the filter was applied in the view? – Ben Mar 18 '14 at 12:11
  • 6
    @Ben: I guess this is going off-topic. I'd consider creating a custom filter e.g. [jsfiddle](http://jsfiddle.net/TahmidTanzim/N9Vqk/), however, you could also watch it inside the controller: `$scope.$watch('filtered', function() { console.log('filtered:', $scope.filtered); });` – Wumms Mar 18 '14 at 14:32
  • I had problems getting `$scope.$watch` to work so I used `ng-keyup` on the search input instead – Peter Jul 14 '14 at 07:54
  • This works for named controllers too, using this syntax: `ng-repeat="person in ctrl.filtered = (data | filter: query)"`. – nilskp Aug 29 '14 at 18:20
  • 2
    Perfect +1, also I should mention for others it may be less obvious but when placing () around your data filters, and you are doing pagination, don't include the pagination inside the (). – Don Thomas Boyle Nov 07 '14 at 16:19
  • this is what a perfect answer looks like. Clean, clear and concise. Saved me a lot of effort. Well done mate!! – Vikram Gulia Jul 31 '15 at 20:02
  • 3
    The 1.3+ version didn't work if I wanted to add limitTo : `person in data | filter:query as filtered | limitTo:5`. So I had to use the prior to 1.3 version: `person in filtered = (data | filter: query) | limitTo: 5` – benjovanic Jul 21 '16 at 08:24
  • @benjovanic thanks. I was trying the same thing just now (with a limitTo 5 at the end, too!) Your comment was helpful. – Adam Plocher Sep 25 '16 at 23:40
  • 1
    How would I make this work if I want filtered.length available on the parent of the ng-repeat div? – jacksparrow007 May 25 '17 at 08:32
337

For completeness, in addition to previous answers (perform calculation of visible people inside controller) you can also perform that calculations in your HTML template as in the example below.

Assuming your list of people is in data variable and you filter people using query model, the following code will work for you:

<p>Number of visible people: {{(data|filter:query).length}}</p>
<p>Total number of people: {{data.length}}</p>
  • {{data.length}} - prints total number of people
  • {{(data|filter:query).length}} - prints filtered number of people

Note that this solution works fine if you want to use filtered data only once in a page. However, if you use filtered data more than once e.g. to present items and to show length of filtered list, I would suggest using alias expression (described below) for AngularJS 1.3+ or the solution proposed by @Wumms for AngularJS version prior to 1.3.

New Feature in Angular 1.3

AngularJS creators also noticed that problem and in version 1.3 (beta 17) they added "alias" expression which will store the intermediate results of the repeater after the filters have been applied e.g.

<div ng-repeat="person in data | filter:query as results">
    <!-- template ... -->
</div>

<p>Number of visible people: {{results.length}}</p>

The alias expression will prevent multiple filter execution issue.

I hope that will help.

Tom
  • 26,212
  • 21
  • 100
  • 111
  • 4
    Will this mean the filter is executed twice? – Flash Jan 08 '14 at 23:33
  • 9
    Yes, it is executed twice. – nathancahill Jan 22 '14 at 23:34
  • 11
    make sure you read @Wumms answer before you decide to use this one (and you won't)... – epeleg Jun 25 '14 at 06:52
  • @epeleg Why would/should we decide to use Wumms' answer over this one? As far as I can tell the two answers' examples are functionally equivalent, except I can find this answer as a documented feature of ngRepeat but I can't find anything about variable assignments in an ngRepeat expression being something officially condoned. – Adam Goodwin Sep 05 '15 at 04:48
  • 1
    Nevermind, I see the answer didn't contain the part about the alias expression when you made your comment. – Adam Goodwin Sep 05 '15 at 04:55
  • 1
    Yep, but I am glad you asked... I was not familiar with the alias expression. It is a good Q what should happen to Q's where the correct answer changes over time... should the person that provided the best answer (for when the Q was asked) loose his reputation because the correct answer he gave in the past is no longer the best? maybe there should be a difference between the accepted answer and the currently most accurate answer... – epeleg Sep 06 '15 at 05:21
  • Great, and this is cool because it works for `collection-repeat` too! – Louis Nov 24 '15 at 19:50
  • 1
    Great anwer! Especially the one that you could use filter just like that, without any ng-repeat or similar. +1 – Lucas Reppe Welander Apr 18 '16 at 21:47
  • 2
    This answer supports multiple filter expressions unlike the current top answer. i.e) `{{(data|filter:query|filter:query2).length}}` – chakeda May 17 '16 at 14:54
50

The easiest way if you have

<div ng-repeat="person in data | filter: query"></div>

Filtered data length

<div>{{ (data | filter: query).length }}</div>
Donald Duck
  • 8,409
  • 22
  • 75
  • 99
ionescho
  • 1,360
  • 2
  • 17
  • 31
36

ngRepeat creates a copy of the array when it applies a filter, so you can't use the source array to reference only the filtered elements.

In your case, in may be better to apply the filter inside of your controller using the $filter service:

function MainCtrl( $scope, filterFilter ) {
  // ...

  $scope.filteredData = myNormalData;

  $scope.$watch( 'myInputModel', function ( val ) {
    $scope.filteredData = filterFilter( myNormalData, val );
  });

  // ...
}

And then you use the filteredData property in your view instead. Here is a working Plunker: http://plnkr.co/edit/7c1l24rPkuKPOS5o2qtx?p=preview

Josh David Miller
  • 120,525
  • 16
  • 127
  • 95
  • 1
    What I understood is that I use `{{filteredData.length}}` instead of `data.length`. I also understood that `myInputModel` is the model mapped to the input query which filters the data. `val` would be the text that is typed in the input (basically query) but each time I type, it changes. But what is `filterFilter` here? I might add that I have my own customFilter created (wherein I can specify which key of the object to consider for filtering). – user109187 Mar 09 '13 at 22:09
  • That's right. Also just use `ng-repeat="person in filteredData"` as there is no sense filtering it twice. With the `$filter` service, you can ask for a specific filter by just suffixing it with "Filter", e.g.: `dateFilter`, `jsonFilter`, etc. If you are using your own custom filter, just use that one instead of the generic `filterFilter`. – Josh David Miller Mar 09 '13 at 22:16
  • What about applying multiple filters to one ng-repeat, say you have 2 dropdowns with values and one text input field? – alchemication Mar 13 '13 at 08:44
  • The filter is just a function, so you'd just pass the output of the first filter to the second filter. – Josh David Miller Mar 13 '13 at 17:39
29

Since AngularJS 1.3 you can use aliases:

item in items | filter:x as results

and somewhere:

<span>Total {{results.length}} result(s).</span>

From docs:

You can also provide an optional alias expression which will then store the intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message when a filter is active on the repeater, but the filtered result set is empty.

For example: item in items | filter:x as results will store the fragment of the repeated items as results, but only after the items have been processed through the filter.

WelcomeTo
  • 811
  • 8
  • 11
  • I am unable to apply `$scope.$watch` to this aliased result. Any tips for `$broadcast`ing the aliased `results` variable? – DeBraid Feb 19 '16 at 01:01
25

It is also useful to note that you can store multiple levels of results by grouping filters

all items: {{items.length}}
filtered items: {{filteredItems.length}}
limited and filtered items: {{limitedAndFilteredItems.length}}
<div ng-repeat="item in limitedAndFilteredItems = (filteredItems = (items | filter:search) | limitTo:25)">...</div>

here's a demo fiddle

JeremyWeir
  • 24,118
  • 10
  • 92
  • 107
13

Here is worked example See on Plunker

  <body ng-controller="MainCtrl">
    <input ng-model="search" type="text">
    <br>
    Showing {{data.length}} Persons; <br>
    Filtered {{counted}}
    <ul>
      <li ng-repeat="person in data | filter:search">
        {{person.name}}
      </li>
    </ul>
  </body>

<script> 
var app = angular.module('angularjs-starter', [])

app.controller('MainCtrl', function($scope, $filter) {
  $scope.data = [
    {
      "name": "Jim", "age" : 21
    }, {
      "name": "Jerry", "age": 26
    }, {
      "name": "Alex",  "age" : 25
    }, {
      "name": "Max", "age": 22
    }
  ];

  $scope.counted = $scope.data.length; 
  $scope.$watch("search", function(query){
    $scope.counted = $filter("filter")($scope.data, query).length;
  });
});

Mak
  • 19,913
  • 5
  • 26
  • 32
  • 4
    The problem with this approach is that you're running the filter twice: once in the ngRepeat and once in the controller. – Josh David Miller Mar 10 '13 at 00:30
  • Yea, you're right, can you post your working example based on your answer, i'd like to learn a bit more filters? Thanks. – Mak Mar 10 '13 at 15:19
  • 2
    Sure. Here's the working Plunker: http://plnkr.co/edit/7c1l24rPkuKPOS5o2qtx?p=preview. I just edited yours. – Josh David Miller Mar 11 '13 at 05:45
10

You can do it with 2 ways. In template and in Controller. In template you can set your filtered array to another variable, then use it like you want. Here is how to do it:

<ul>
  <li data-ng-repeat="user in usersList = (users | gender:filterGender)" data-ng-bind="user.name"></li>
</ul>
 ....
<span>{{ usersList.length | number }}</span>

If you need examples, see the AngularJs filtered count examples/demos

David Newcomb
  • 10,639
  • 3
  • 49
  • 62
Hazarapet Tunanyan
  • 2,809
  • 26
  • 30