7

Suppose I have an array of 5000 objects (with boolean values) which I have to ng-repeat in the template:

$scope.arr = [
    {
        "value": true
    },
    {
        "value": false
    },
    {
        "value": false
    }
    //and so on
]

Now, I want to filter this ng-repeated array on the basis of a dynamic variable, say 'show_filter', which I am setting elsewhere.

If 'show_filter' is set to 'all' I want to show all the objects. If it is set to false (the boolean value) then I want to show objects with 'value' key set to false. Same goes for when 'show_filter' is set to true.

So, there are two approaches:

1. Build a custom filter:

I would write a custom filter for the filtering task like this:

filter:

app.filter('filterArr', function() {
    return function(arr, show_filter) {
        var filtered_arr = [];
        if(show_filter != 'All') { //if show_filter is a boolean value
            for(var i = 0; i < arr.length; i++) {
                if(arr[i].value == show_filter) { 
                    filtered_arr.push(arr[i]);
                }
            }
            return filtered_arr;
        }
        else {
            return arr; //return the entire array if show_filter is set to 'All'
        }
    }
})

template:

obj in arr | filterArr : show_filter

2. Write a filter function in the controller:

filter:

$scope.filterObjects = function(arr) {
    var filtered_arr = [];
    if($scope.show_filter != 'All') { //if $scope.show_filter is a boolean value
        for(var i = 0; i < arr.length; i++) {
            if(arr[i].value == $scope.show_filter) { 
                filtered_arr.push(arr[i]);
            }
        }
        return filtered_arr;
    }
    else {
        return arr; //return the entire array if show_filter is set to 'All'
    }
}

template:

obj in filterObjects(arr)

Which of the above two methods will be faster? I have seen the custom filter code execute everytime for each digest loop and not only for changes made to $scope.show_filter, which leds me to believe its quite inefficient. Although I am not sure which is faster between the two ways.

Tarun Dugar
  • 8,921
  • 8
  • 42
  • 79
  • You have two identical functions and you ask which is fastest? If you mean which is called the fewest number of times, then perhaps you have answered your own question already? – davidkonrad Nov 21 '15 at 08:38
  • I don't know how many times the second function is called. – Tarun Dugar Nov 21 '15 at 08:39
  • The fastest alternative is to expose a filtered array to `ngRepeat`. The second function has to be called be called in every digest cycle. – a better oliver Nov 21 '15 at 11:34
  • array is filtered based on an external variable which can be set any time via an event. So, I don't think I can have a pre-filtered array. I think the best way would be to use apply a watch on the external variable and filter the array in the callback for the watch. What do you think? – Tarun Dugar Nov 21 '15 at 11:35
  • That's what I meant. – a better oliver Nov 21 '15 at 11:39

1 Answers1

4

Both functions will be called in every digest cycle. That's somewhat obvious for the second function. The return value of filterObjects(arr) could be different on every call.

It's not so obvious why a filter would be called in every digest cycle. The documentation states the following:

The filter function should be a pure function, which means that it should be stateless and idempotent. Angular relies on these properties and executes the filter only when the inputs to the function change.

So if neither arrnor show_filter change then the filter shouldn't be called, right? But here's the catch: Detecting a change in arr is costly.

Angular has to make a copy of the array to compare it with the current content. Even if nothing has changed, every single item has to be compared. And if the items are objects every single property of them has to be compared. Directly calling a filter instead is much cheaper. And that's what Angular does when a filter is applied to an array (or object).

To speed up the application you have two choices. The first one is to filter the array only when it's necessary and to expose the filtered array to ng-repeat. E.g. if you can enter a value by which the array will be filtered, then filter the array whenever that value changes.

The second alternative can be used if both the array and the filter don't change (so not in your case). Then you can use one-time binding:

<li ng-repeat="item in ::array | filter">

That's useful when you have a fixed set of items and want to sort them by name e.g. The filter will be called only once in that case.

a better oliver
  • 26,330
  • 2
  • 58
  • 66
  • 'Directly calling a filter instead is much cheaper'. Does this mean the first approach or second? Also, one correction, a filter cannot be applied to an object. Nonetheless, thanks for clearing many things up! – Tarun Dugar Nov 21 '15 at 13:07
  • This applies to the custom filter (the first approach). A filter can be applied to everything. Think of the `date`filter (a date is an object), or the `json` filter, that only makes sense for objects. An array is an object too, btw. – a better oliver Nov 21 '15 at 14:59