0
votes

I am trying to sort some elements in an ng-repeat directive.

I have created a custom filter that appears to work although I get the error:

"Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!"

From what i've read on other answers this is often due to returning a different value for each filter. I have checked and I am returning the same array object...so I'm not sure what could be causing it.

.filter('sortOffers', function() {
        return function(vals, predicate, reverse) {
            var vals_old = vals;

            vals.sort(function(a, b) {
                var sorter = reverse ? 1 : -1;

                if(predicate === 'seeking') {
                    if(a.data.valueSought > b.data.valueSought) {
                        return sorter;
                    } else if(a.data.valueSought < b.data.valueSought) {
                        return -sorter;
                    } else {
                        return 0;
                    }
                }

                if(predicate === 'offering') {
                    if(a.data.valueOffered > b.data.valueOffered) {
                        return sorter;
                    } else if(a.data.valueOffered < b.data.valueOffered) {
                        return -sorter;
                    } else {
                        return 0;
                    }
                }
            });

            return vals;
        }
    });

html:

        <tr ng-repeat="offer in offers | sortOffers:sortOffersPredicate:sortOffersReverse" ng-hide="hideDueDateSellers && offer.data.offerType === 'seeking_flexibility' || hideFlexibilitySellers && offer.data.offerType === 'seeking_duedate'">
            <td>{{ offer.data.valueOffered || '0' }} {{ offer.offeringString }}</td>
            <td>{{ offer.data.valueSought || '0' }} {{ offer.seekingString }}</td>
            <td ng-if="offer.data._owner !== currentUser._id"><button class="ui button positive" ng-click="acceptOffer(offer)">Accept Offer</button></td>
            <td ng-if="offer.data._owner === currentUser._id"><button class="ui button negative" ng-click="cancelOffer(offer)">Cancel Your Offer</button></td>
        </tr>
2
You are confused about the purpose of a filter. A filter cannot modify the data in any way at all. Any modification of the data causes a digest, which causes the filter to be evaluated, the data to be modified (again), the digest to run (again), without end. A filter should only be used to limit the results returned, not modify the data. the data should be sorted in the controller, before any filter is applied. - Claies
so changing the order of the array is what is causing the issue? - Melbourne2991
if that's the case then why is there a native order by filter? docs.angularjs.org/api/ng/filter/orderBy - Melbourne2991
I'm trying to study the code for the native orderBy filter to see how they solve this issue... I haven't had reason to dig into it before now. - Claies

2 Answers

3
votes

So the issue here is based on the fact that the array is being modified within the filter. However, there is a way around this, as demonstrated in the original source for the native orderBy filter.

The Array itself is being modified by the .sort() function, triggering the $digest cycle. Using .slice() will create a new array which can be sorted into, and the new array can be returned without triggering the $digest.

return vals.slice().sort(function(a, b) {

A bit more context here...

There is nothing wrong with returning a different array with the same data structure as the original. In fact, you have to return a different array from the original if you are eliminating items. The confusion is that you can't return an array (the original or a copy) where the values have changed. by using .slice(), the returned array is only ever evaluated once to be the result of the .sort(), and the original array never changed.

However, a filter which took in an array and used it to evaluate data and return a different array with a totally different structure would not work.

1
votes

Copy the array before you do the sort. Modifying the array in place is what is firing the $watch:

        return vals.slice().sort(function(a, b) {
            var sorter = reverse ? 1 : -1;

            if(predicate === 'seeking') {
                if(a.data.valueSought > b.data.valueSought) {
                    return sorter;
                } else if(a.data.valueSought < b.data.valueSought) {
                    return -sorter;
                } else {
                    return 0;
                }
            }

            if(predicate === 'offering') {
                if(a.data.valueOffered > b.data.valueOffered) {
                    return sorter;
                } else if(a.data.valueOffered < b.data.valueOffered) {
                    return -sorter;
                } else {
                    return 0;
                }
            }
        });

I believe ng-repeat $watches the right hand side (rhs) expression for changes using $watchCollection. $watchCollection doesn't compare the arrays by reference, but instead looks for differences in length and also will compare the arrays item by item.