3

I want to use AngularJS to display a shuffled list - but only the first couple of elements. At the moment, I perform both the shuffling and limiting in the HTML template, demonstrated in this fiddle:

<li ng-repeat="value in array | shuffle | limitTo:1">
    {{value}}
</li>

This works fine, but causes Angular to exceed 10 $digest iterations when there are more items in the list than are shown, as in the example. What is happening, as far as I can tell, is that something is watching the filtered and limited value of the list, which is highly likely to change when there all the elements will not always be displayed.

How should I achieve this effect without breaking Angular? Of course, it still works like it should, but it's inefficient and probably an indication that what I'm doing is incorrect and should be achieved some other way.

The obvious solution is to shuffle the list in the controller before it is ever displayed - but I do want the selection of elements displayed to change each time the user updates the view.

EDIT: here's a better example of what I'm trying to achieve - the app now lets you switch between two lists, which get shuffled and limited each time.

Daniel Buckmaster
  • 7,108
  • 6
  • 39
  • 57

3 Answers3

2

As mentoined before - angular waits for an expression gets stabilized. So, the best way, is to shuffle an array before passing it to view. Anyway, there are some tricks, that would help you to keep it clean.

http://jsfiddle.net/zc3YH/3/

In this fiddle we cache shuffle result, while a length keeps the same. So, you get array reshuffled not when an array changes, but when it length changed. You can implement more complex caching behavior. So, the main idea is to shuffle the same array only once, and reshuffle it only on array update.

Current filter implementation is really bad, cause it caches only single array, so if you would use this filter twice - it would be broken. So, caching should be done using something like hashKey for an array to differ different arrays.

shufflemodule.filter('shuffle', function() {
    var shuffledArr = [],
        shuffledLength = 0;
    return function(arr) {
        var o = arr.slice(0, arr.length);
        if (shuffledLength == arr.length) return shuffledArr;
        for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
        shuffledArr = o;
        shuffledLength = o.length;
        return o;
    };
});

Anyway, it is not good practice to modify data in scope within a filter. If you need this shuffled array be shared - shuffle it in your controller/service/.... So, var o = arr.slice(0, length) copies that array, thus we keep originar array unmodified.

Pythonic
  • 486
  • 5
  • 9
1

This fiddle demonstrates the 'filter in the controller' approach. Instead of actually creating a filter, shuffling is defined as a regular function and applied to $scope.array.

Daniel Buckmaster
  • 7,108
  • 6
  • 39
  • 57
0

I think tgat problem is that your filter modify source array. Even if you return new array it would be asane problem. Its how dirty checking works: it keeps evaluating expression until it gets "stabilized" (or 10 iterations). More isdescribed in this post: https://groups.google.com/forum/m/#!msg/angular/IEIQok-YkpU/InEXv61MrkMJ

Solution would be pre-shuffle values in controller so expression would return same vallue each dirty-check phase... Sorry, but that is the best way for now (unless you whant to play dirty games with $$hash to let ngRepeat beleave that yourexpression evaluates into same values :))

Valentyn Shybanov
  • 19,331
  • 7
  • 66
  • 59