[angular.js] Counting elements in a filtered list - the ultimate solution?

31,686 views
Skip to first unread message

Pawel Kozlowski

unread,
Aug 11, 2012, 6:09:35 AM8/11/12
to ang...@googlegroups.com
Hi!

Recently I've seen quite a number of posts here with question on how
to count items in a filtered list that is used in ngRepeat.

There were number of posts but most them involved either:
- moving filtering logic to a controller
- calling filters twice (I might be guilty of proposing this one!)
Some people went as far as counting DOM elements with jQuery.

Anyway, today it downed on me that one could use something like:

ng-repeat='item in filtered = (items | filter:filterExpr) and then
simply {{filtered.length}}

Seems to be working perfectly fine:
http://jsfiddle.net/pkozlowski_opensource/Nk8qy/2/

What do you think? Can you spot any problem with using this syntax?

Cheers,
Pawel

Josh Kurz

unread,
Aug 11, 2012, 6:43:13 AM8/11/12
to ang...@googlegroups.com
I added a splice button to go with the push. 

Seems to work perfect. 

Jaymes Bearden

unread,
Aug 30, 2012, 1:09:42 AM8/30/12
to ang...@googlegroups.com
I've been looking over a few of these solutions over the past few days and it basically boils down to a memory vs time situation (isn't it always? :)

It would be nice if the devs could expose some information about the ng-repeat back to the controller somehow...

Something like: <div ng-repeat="item in items | filter:blah" ng-repeat-results="repeatResults"> ... </div>

I feel that running the filter set twice is ugly in the view and having a duplicated / filtered array of the results sucks for memory usage in large data sets.

Pawel Kozlowski

unread,
Aug 30, 2012, 2:47:11 AM8/30/12
to ang...@googlegroups.com
Hi Jaymes!

On Thu, Aug 30, 2012 at 7:09 AM, Jaymes Bearden <slas...@gmail.com> wrote:
> I've been looking over a few of these solutions over the past few days and
> it basically boils down to a memory vs time situation (isn't it always? :)

Yeh, it is vary often memory vs. cpu trade-off. I saw also some
DOM-intensive solutions for this counting problem and it was pretty
bad.

> It would be nice if the devs could expose some information about the
> ng-repeat back to the controller somehow...
>
> Something like: <div ng-repeat="item in items | filter:blah"
> ng-repeat-results="repeatResults"> ... </div>

but this is exactly what my little trick:

ng-repeat='item in filtered = (items | filter:filterExpr)'

does, no? It should expose a filtered array in the 'filtered' variable
(not in a controller but in scope thought, so it is imediatelly
available to a template).

> I feel that running the filter set twice is ugly in the view and having a
> duplicated / filtered array of the results sucks for memory usage in large
> data sets.

Exactly, this is why you can see this assignment to a temp variable so
you don't need to run filters twice (at least not for counting
objects). Do you see filters running multiple times (apart from the
standard $digest cycles)?

Cheers,
Pawel

col...@gmail.com

unread,
Aug 30, 2012, 4:39:28 AM8/30/12
to ang...@googlegroups.com
 (items | filter:filterExpr).length 

суббота, 11 августа 2012 г., 14:09:35 UTC+4 пользователь Pawel Kozlowski написал:

Pawel Kozlowski

unread,
Aug 30, 2012, 4:53:42 AM8/30/12
to ang...@googlegroups.com
hi!

On Thu, Aug 30, 2012 at 10:39 AM, <col...@gmail.com> wrote:
> (items | filter:filterExpr).length

This means re-executing filters just to count elements, and this is
what we are trying to avoid here...

Cheers,
Pawel

Nicola G.

unread,
Jan 3, 2013, 11:53:15 AM1/3/13
to ang...@googlegroups.com
Hi there, what about using a $watch on items? It doesnt work for me, but I think it may lead on a new way of doing it. Or am I just wrong?

Martín Paulucci

unread,
Jan 23, 2013, 9:17:23 AM1/23/13
to ang...@googlegroups.com
Pawel, I think your solution is awesome! By far the best I've seen for this problem.

James Van Dyke

unread,
Feb 22, 2013, 12:26:24 PM2/22/13
to ang...@googlegroups.com
@Pawel, when I use this, and then try to read from the resulting variable in the controller, I reach the maximum digest iteration limit even though I'm only accessing the variable.

Any clue what's causing this?

Pawel Kozlowski

unread,
Feb 22, 2013, 12:30:55 PM2/22/13
to ang...@googlegroups.com
HI!

On Fri, Feb 22, 2013 at 6:26 PM, James Van Dyke <jam...@gmail.com> wrote:
> Any clue what's causing this?

Not without seeing your code. Put it in http://plnkr.co/ and send here.

Cheers,
Pawel


--
Looking for bootstrap-based widget library for AngularJS?
http://angular-ui.github.com/bootstrap/

James Van Dyke

unread,
Feb 22, 2013, 12:42:40 PM2/22/13
to ang...@googlegroups.com
Oops, had it open and forgot to paste the link:  http://jsfiddle.net/jvandyke/EAbks/

Type in the filter box with your console open and you'll see the error.

However, I figured it out.  Because the setting of "filtered" is not idempotent (i.e., a new object is created each time), and the default equality check for $watch is by reference, $digest keeps waiting for the model to stabilize, but it doesn't.

The solution is to pass the third variable to $watch as true so that the equality check will use angular.equals instead of a reference check.

Code for solution here: http://jsfiddle.net/jvandyke/EAbks/2/

That make sense?

Thanks for the original post.  I'm surprise this hasn't been resolved in a better way since 7 months ago.

- James

Pawel Kozlowski

unread,
Feb 22, 2013, 12:45:50 PM2/22/13
to ang...@googlegroups.com
Hi!

Glad you've figured it out, love code always helps.

On Fri, Feb 22, 2013 at 6:42 PM, James Van Dyke <jam...@gmail.com> wrote:
> Thanks for the original post. I'm surprise this hasn't been resolved in a
> better way since 7 months ago.

Well, I guess there is not much more to resolve here I would say. I
mean, if you want to read filtered items in a template only this is a
way to go IMO.

If, on the other hand, you want to read values in a controller I would
move the whole filtering to a controller probably.

leif hanack

unread,
Apr 3, 2013, 5:16:12 PM4/3/13
to ang...@googlegroups.com
Hi, thanks for this suggestion.

Did anyone see what I'm doing wrong: http://jsfiddle.net/Buqcj/1/

The item count is not updated with every click on a checkbox. 

Thanks, Leif

Sander Elias

unread,
Apr 4, 2013, 9:28:35 AM4/4/13
to ang...@googlegroups.com
Hi Leif,

You where watching the wrong way, it needs to be a string, which angular can resolve, or a function that gets evaluated.

Regards
Sander Elias

zorro

unread,
Apr 9, 2013, 3:35:46 PM4/9/13
to ang...@googlegroups.com
Watching 'filtered.length' is maybe even better (or just safer in terms performance). Thanks for your fiddle. Nice!

fess

unread,
Jul 1, 2013, 11:37:20 AM7/1/13
to ang...@googlegroups.com
Can someone explain to  me what's wrong with Leif's fiddle?

I moved the "{{ filteredItems.length }} items"  down below the ng-repeat and everything started working.  This confuses me a lot. I thought I  understand  the whole $digest cycle and I'd really like to understand what's happening there. 

Sergio Castillo Yrizales

unread,
Jul 1, 2013, 12:11:41 PM7/1/13
to ang...@googlegroups.com
I like Pawel solution. It seems elegant an explicit. I believe that could be the ultimate way to doing it.

Dan Hadley

unread,
Aug 1, 2013, 9:41:10 AM8/1/13
to ang...@googlegroups.com
I think this is a bug with the {{ }} bindings. If you move the filter logic into the controller and use a function like:

$scope.getFiltered( ) 

and then bind the <p> to:

{{ getFiltered( ).length }}

everything works regardless of where it is placed. So it seems like something in the process of compiling {{ }} with a variable is dependent on where it is placed, even in the scope?

Andrew C. Greenberg

unread,
Aug 2, 2013, 11:15:20 AM8/2/13
to ang...@googlegroups.com
I'm very fond of this approach, and used it while spiking a view.  The problem is that the code can be ungainly, and looks like, well code rather than declaration.  So I refactored to use a $scope.$watch in the controller, setting the interim values.  The view looks far more readable and declarative, but doesn't reveal intent as well as the expression (should be reparable with a solid name).  This also feels a bit hacky, though, since it interposes otherwise unnecessary code to support data binding in a controller.

Both work fine.  Does anyone have a sense which is closer to the angular way?  I'm wondering if expensive pieces of an angularjs expression should be (could be) cached, so that this tradeoff would be unnecessary?

Félix Le Blanc

unread,
Mar 19, 2015, 9:58:11 PM3/19/15
to ang...@googlegroups.com
You can do this with array containing dictionary

<div ng-repeat="item in data | orderBy : item.mykey " >
{{ (data | filter: item.myKey).length  }}
</div>
Reply all
Reply to author
Forward
0 new messages