Angular.js: Add repeat-range directive

Created on 1 Feb 2015  路  14Comments  路  Source: angular/angular.js

Programmers want this directive!
Google has many examples with ng-range, ng-repeat-range, but a lot of them working through compiling to e.g ng-repeat="n in [1,2,3,4,5,6]". This may create a large arrays in memory.

I want to see the ng-range directive as:
<div ng-range="5"> {{ n }} </div> - renders my element 5 times (n is a default alias)
<div ng-range="5 as x"> {{ x }} </div> - x is an new alias
<div ng-range="1 to 10"> {{ n }} </div>
<div ng-range="1 to 10 as x"> {{ x }} </div>
<div ng-range="10 step 2"> {{ n }} </div>
<div ng-range="10 step 2 as x"> {{ x }} </div>
<div ng-range="1 to 10 step 2"> {{ n }} </div>
<div ng-range="1 to 10 step 2 as x"> {{ x }} </div>

I wrote the RegExp for parse this string.
/^(.+?)(?:\s*to\s*(.+?))?(?:\s*step\s*(.+?))?(?:\s*as\s*(\w+))?$/.

Thanks!

ngRepeat feature

Most helpful comment

https://github.com/angular/angular.js/milestone/38 : "Ice Box" - Milestone for valid issues and bugs that we have no aspirations on fixing any time soon because they are not affecting primary use-cases and are not aligned with our goals.

All 14 comments

@Serabass ng-repeat="n in [1,2,3,4,5,6]". This may create a large arrays in memory
Hum. Array of integers are really very small in memory, so if really you make large integer arrays for ng-repeat, that means that the created DOM elements which take much much more room than a simple integer will explose your memory.

@frfancha all the same, this directive in official bundle does not prevent

@Serabass I think it should be possible to write a filter that does all this. It would be a filter that takes begin, end and step and would generate an array with the right elements. Variations of what you are proposing were discussed before at #8210 and #9310. In both cases, the call was that this was fine, but should not be part of the core.

I think that your proposal falls into the same category, it is very helpful, but it falls into something that should be part of a third party module.

@lgalfaso what about adding something on the expression level - that is - make $parse understand [x..y]and turning it into an array (I'm assuming that both x and y are constants here). But yeh, I agree with you that this one can be easily covered outside of the core...

@pkozlowski-opensource #8210 added just that with the exception that x and y can be expressions too, and that there was also a ... option that would create the array with the range [x,y). The issue is that it cannot be made flexible enough. Eg, we cannot add a step, as we would be limited by the begin and end properties. A filter does not have that limitation and can be made more flexible.

That said, I still think it would be best if this is not part of the core and a third party module.

I found the function from Google and modify it for understanding the step and alias with examples from first topic:
`
angular.module('myApp')
.directive('ngRepeatRange', ['$compile', function ($compile) {
return {
replace: true,
link: function (scope, element, attrs) {

            // returns an array with the range of numbers
            // you can use _.range instead if you use underscore
            function range(from, to, step) {
                var result = [];

                for (var i = from; i <= to; i += step) {
                    result.push(i);
                }

                return result;
            }

            var rgx = /^(.+?)(?:\s*to\s*(.+?))?(?:\s*step\s*(.+?))?(?:\s*as\s*(\w+))?$/;

            var data = rgx.exec(attrs.ngRepeatRange.trim());

            // prepare range options
            var from;
            var to;
            var step = scope.$eval(data[3]) || 1;
            var alias = data[4] || 'n';

            if (data[2]) {
                from = scope.$eval(data[1]);
                to = scope.$eval(data[2]);
            } else {
                from = 0;
                to = scope.$eval(data[1]);
            }

            // get range of numbers, convert to the string and add ng-repeat
            var rangeString = range(from, to, step).join(',');

            angular.element(element).attr('ng-repeat', alias + ' in [' + rangeString + ']');
            angular.element(element).removeAttr('ng-repeat-range');

            $compile(element)(scope);
        }
    };
}]);

`

It would be a lot easier if done with a filter

Not saying this should be built-in, but here's the problem I'm currently having that easy access to such a feature would solve.

It's not just a single in-memory array that is the cost. Because the examples the OP was referring to compile to ng-repeat, the array actually appears many times in the DOM, with resource consumption growing exponentially. More repeats make the attributes larger, and also generates more of those larger attributes. Just a few nestings of this demonstrates vastly diminished scalability/performance.

I agree that a filter avoids this issue, however it adds the complexity of managing index uniqueness. I'm currently using: http://stackoverflow.com/questions/11873570/angularjs-for-loop-with-numbers-ranges#14932395

@syndicatedshannon using a filter solves many odd cases, and this is why it works best. Eg, say that there is a filter named range that given and end (and optional begin) returns an array that is [begin..end], then doing <div ng-repeat="foo in 5 | range">{{foo}}</div> would solve the following issues:

  • Are the begin, end, step, etc parameters expressions or constants? Do you do not care, $parse and ngRepeat will do the right thing
  • Do you want to initialize the array and then just use it. Again, just ng-init="foo = 10 | range:5"
  • Do you want to iterate over chars/token/something else. If the range filter does not support it, create your own filter (that is a lot easier than to write a directive)
  • No memory leaks. It took a lot of time to be sure that ngRepeat does not leak (and in fact it looks like the implementation at https://github.com/angular/angular.js/issues/10925#issuecomment-72483098 does not handle some cases so it can leak)

Yeah, I'm not arguing with you. I also see "trackBy: $index" isn't required if the filter returns unique values. A filter is working fine for me.

Of course, semantically it still looks really awkward to me. I wouldn't easily have thought of it on my own.

edit: explaining that last comment... I think that since, as you mentioned, a filter solves many unusual cases, it's perfectly represented by the "alter/embellish" syntax the pipe notation offers. However, representing a highly normal/structured grid by embellishing an empty set just doesn't feel natural.

any update on this?

It's in the Ice Box, so probably never going to be added to core.

@gkalpak ice box?

https://github.com/angular/angular.js/milestone/38 : "Ice Box" - Milestone for valid issues and bugs that we have no aspirations on fixing any time soon because they are not affecting primary use-cases and are not aligned with our goals.

Was this page helpful?
0 / 5 - 0 ratings