Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I'll make it pretty simple. I have:

<div ng-repeat="group in topLevelGroups">

    ....

    <div ng-repeat="item in group.items | filter:itemFilter">

        ....

    </div>

</div>

That's working all well and fine, but I want to display somewhere the total number of filter results across all "group"s.

To accomplish this, I moved the filter into the controller, like so:

<div ng-repeat="item in filterItems(group)">

// in controller:

$scope.filterItems = function(group) {
    var ret = $filter('filter')(g.items, $scope.query);
    g.filteredItemCount = ret.length
    return ret
};

$scope.getTotalFiltered = function () {
    return $scope.data.reduce(function (prev,cur) {
        return prev + cur.filteredItemCount
    }, 0)
}

So, essentially, piggyback a "filteredPostLength" property on to the group when the filter occurs, and use this to sum that property across all the groups to achieve total filtered results.

Here is a jsFiddle

This is working just fine, but it feels rather hackish and I was wondering if there was a cleaner, more Angular way to accomplish this goal.

Any input is much appreciated, thanks!

share|improve this question
add comment

2 Answers

up vote 6 down vote accepted

Your code will work but it is not optimal.

I forked your jsFiddle code and added logging to demonstrate it:

  • Open http://jsfiddle.net/jvandemo/tDhgv/1/
  • Open the console in your browser
  • Type something in the input fields and you will notice that the filterItems runs 9 times every time the $scope.query changes

So why is this?

Well, there are 2 reasons:

  1. Your function call is inside an ng-repeat so it gets called multiple times (as expected, no surprises here)
  2. There is an issue with your filterItems method.

    You have assigned the filterItems method to the inner ng-repeat. Therefore Angular will automatically watch the result of the function for you and 'refresh' when it changes.

    However, inside your filterItems code you make an immediate change to the data Angular is watching for you by performing g.filteredItemCount = ret.length causing Angular to perform another 'refresh' since it detects a new change.

    If you were to comment out that single ine of code, you would immediately see a drop in number of times the method would run.

So what's a better way?

In your case I would:

  • Define a watcher for $scope.query
  • Filter the data in your watcher and store it separately (to leave the original data untouched)
  • Loop through the filtered data with the ng-repeat tags

Your code will be cleaner, faster and more efficient which is important if your data set would be much bigger.

I created a jsFiddle for your convenience: http://jsfiddle.net/jvandemo/VeLEJ/1/

If you open the console, you will see that the code only runs once when $scope.query changes instead of 9 times before.

Visually the result is the same, but behind the scenes there is quite a difference...

Hope that helps!


UPDATE:

An approach for better performance is to restructure the data (once after loading the data).

I have created a jsFiddle with sample code to demonstrate it: http://jsfiddle.net/jvandemo/fTE5R/1/

The data is restructured to avoid forEach cycles and to avoid as many loops as possible and to allow the query object to be applied directly to the items in the directive.

This looks a lot like the original code but without the need for calling filtering functions inside the scope.

If your application can deliver the data in the format of $scope.items the whole process would even be faster and the optimization step could be skipped.

Hope that helps!

share|improve this answer
1  
Thanks so much for explaining why my method was so much more inefficient! Sometimes it's tough to know exactly how Angular works at times. –  user48998 Jun 23 '13 at 22:52
 
Actually, now that I've put this into practice, my performance has become much worse. I think it's because of the fact that I'm copying the entirety of the data set every time a key is pressed (my data set is large and each "item" has a lot of properties). Any suggestions on how to handle a huge data set? –  user48998 Jun 24 '13 at 4:13
 
Can you give an estimate of the size of your data so I can simulate it? Thx! –  jvandemo Jun 24 '13 at 11:12
 
Here's a new jsFiddle that builds a new array with filtered data instead of making a copy of the original one so you can compare the performance on your dataset: jsfiddle.net/jvandemo/dsBAH/2 –  jvandemo Jun 24 '13 at 11:39
 
I updated the answer with a new jsFiddle for better performance. –  jvandemo Jun 24 '13 at 14:00
show 3 more comments

The way you did it is absolutely fine, but there's a way to do it without going to the controller:

Group {{group.id}} (results: {{filtered.length}})
<div ng-repeat="item in filtered = (group.items | filter:query)">
    {{item.name}} -- {{item.age}} 
</div>

You have access to the filtered variable 'outside' the inner ng-repeat.

Updated jsfiddle: http://jsfiddle.net/thric3blinded/ehQVe/1/

share|improve this answer
 
Thanks for the fiddle! I do think this looks a lot cleaner, but how can I keep the functionality of totaling the length of the filtered results from all groups? I could replace the "|" notation with my function and still piggyback that "filteredLength" variable? –  user48998 Jun 22 '13 at 6:28
 
I think for your requirements, @jvandemo's solution makes a lot more sense. Since ng-repeat automatically makes a copy of the data source you're giving it - you can't access filtered outside the top level ng-repeat (no closure). So managing the filter in the controller gives you the most flexibility to copy/store/change and manipulate the data. –  Jeff Dalley Jun 23 '13 at 1:11
 
That didn't work for me (AJS 1.2.12). As the filter was returning new array instance every time, it was called every time and resulted in an infinite digest loop. –  Artur Bodera Feb 14 at 13:11
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.