Out-of-the-box, AngularJS allows you to loop over collections; but, it doesn't allow you to perform a simple for-loop. Granted, I've never actually needed a for-loop in production; but, I can definitely see some valid use-cases. And, more than that, I think creating a for-loop would be a fun exercise in creating AngularJS directives. In order to keep the syntax very concise, I've opted for "range" syntax (ex, 1..5) instead of a full-on for-loop.
To create a Range-loop directive in AngularJS, we could build it completely from scratch. That means building all the looping logic, including DOM (Document Object Model) manipulation and Scope generation. Or, we could leverage the already existing, and super powerful, ngRepeat directive.
The ngRepeat directive works on collections. So, in order to leverage it, we have to be able to convert our range into a collection. That means taking this:
... and converting it into something like this:
[ 1, 2, 3, 4, 5 ]
Sure, you could do this with some sort of filter in the ngRepeat directive. But, the syntax for that is messy and obfuscates the intent of the code. Instead, I'd like to create a bnRange directive that uses the range syntax. Then, behind the scenes, the bnRange directive will actually compile itself down into an ngRepeat directive that uses a fleshed-out collection.
The exciting part about this is that we're creating a directive that, in some sense, recompiles itself. Not only does this mean that we get to inject directives; but, it also means that we have to be careful about which aspects of the DOM get compiled when. If we're not careful, we can end up compiling the DOM twice.
The ngRepeat directive executes at priority 1000. As such, the bnRange directive will have to execute at priority 1001 (or higher) so that it compiles before ngRepeat. And, since we're augmenting the same Element node (as opposed to child nodes), we have to explicitly $compile() the ngRepeat directive once it's been injected. And, of course, this means we have to use the "terminal" configuration, otherwise we end up compiling the sub-tree twice.
Ok, let's take a look. I've tried to make the bnRange directive flexible enough to use ascending and descending ranges. And, what's more, since it compiles down to ngRepeat, you can use all the same filters that you would normally (although you really shouldn't use filters in a production app, it's bad for performance).
Wait, AngularJS directives and an excuse to use Regular Expressions? Simmer down - I know it's a little too exciting for a Saturday morning. But, when we run the above code, we get the following page output:
-5 -4 -3 -2 -1 0 1 2 3 4 5
-5 -4 -3 -2 -1 0 1 2 3 4
5 4 3 2 1 0 -1 -2 -3 -4 -5
5 4 3 2 1 0 -1 -2 -3 -4
-5 -4 -3 -2 -1 0 1
5 4 3 2 1 0 -1
And, if we look at the live source code of the document, you can see that all of the bnRange directives have been compiled down into ngRepeat directives:
More than anything practical, I thought this was worthwhile because it forced me to think about the compilation and linking process used by AngularJS; it's not always the most obvious workflow, so the more you practice, the easier it becomes!
Want to use code from this post? Check out the license.