AngularJS ng-repeat with no html element
As Andy Joslin said they were working on comment based ng-repeats but apparently there were too many browser issues. Fortunately AngularJS 1.2 adds built-in support for repeating without adding child elements with the new directives ng-repeat-start
and ng-repeat-end
.
Here's a little example for adding Bootstrap pagination:
<ul class="pagination">
<li>
<a href="#">«</a>
</li>
<li ng-repeat-start="page in [1,2,3,4,5,6]"><a href="#">{{page}}</a></li>
<li ng-repeat-end class="divider"></li>
<li>
<a href="#">»</a>
</li>
</ul>
A full working example can be found here.
John Lindquist also has a video tutorial of this over at his excellent egghead.io page.
KnockoutJS containerless binding syntax
Please bear with me a second: KnockoutJS offers an ultra-convenient option of using a containerless binding syntax for its foreach
binding as discussed in Note 4 of the foreach
binding documentation.
http://knockoutjs.com/documentation/foreach-binding.html
As the Knockout documentation example illustrates, you can write your binding in KnockoutJS like this:
<ul>
<li class="header">Header item</li>
<!-- ko foreach: myItems -->
<li>Item <span data-bind="text: $data"></span></li>
<!-- /ko -->
</ul>
I think it is rather unfortunate AngularJS does not offer this type of syntax.
Angular's ng-repeat-start
and ng-repeat-end
In the AngularJS way to solve ng-repeat
problems, the samples I come across are of the type jmagnusson posted in his (helpful) answer.
<li ng-repeat-start="page in [1,2,3,4,5]"><a href="#">{{page}}</a></li>
<li ng-repeat-end></li>
My original thought upon seeing this syntax is: really? Why is Angular forcing all this extra markup that I want nothing to do with and that is so much easier in Knockout? But then hitautodestruct's comment in jmagnusson's answer started making me wonder: what is being generated with ng-repeat-start and ng-repeat-end on separate tags?
A cleaner way to use ng-repeat-start
and ng-repeat-end
Upon investigation of hitautodestruct's assertion, adding ng-repeat-end
on to a separate tag is exactly what I would not want to do in most cases, because it generates utterly usesless elements: in this case, <li>
items with nothing in them. Bootstrap 3's paginated list styles the list items so that it looks like you did not generate any superfluous items, but when you inspect the generated html, they are there.
Fortunately, you do not need to do much to have a cleaner solution and a shorter amount of html: just put the ng-repeat-end
declaration on the same html tag that has the ng-repeat-start
.
<ul class="pagination">
<li>
<a href="#">«</a>
</li>
<li ng-repeat-start="page in [1,2,3,4,5]" ng-repeat-end><a href="#"></a></li>
<li>
<a href="#">»</a>
</li>
</ul>
This gives 3 advantages:
- less html tags to write
- useless, empty tags are not generated by Angular
- when the array to repeat is empty, the tag with
ng-repeat
won't get generated, giving you the same advantage Knockout's containerless binding gives you in this regard
But there is still a cleaner way
After further reviewing the comments in github on this issue for Angular, https://github.com/angular/angular.js/issues/1891,
you do not need to use ng-repeat-start
and ng-repeat-end
to achieve the same advantages.
Instead, forking again jmagnusson's example, we can just go:
<ul class="pagination">
<li>
<a href="#">«</a>
</li>
<li ng-repeat="page in [1,2,3,4,5,6]"><a href="#">{{page}}</a></li>
<li>
<a href="#">»</a>
</li>
</ul>
So when to use ng-repeat-start
and ng-repeat-end
? As per the angular documentation, to
...repeat a series of elements instead of just one parent element...
Enough talk, show some examples!
Fair enough; this jsbin walks through five examples of what happens when you do and when you don't use ng-repeat-end
on the same tag.
http://jsbin.com/eXaPibI/1/
ngRepeat may not be enough, however you can combine that with a custom directive. You could delegate the the task of adding divider items to code if you don't mind a little bit of jQuery.
<li ng-repeat="item in coll" so-add-divide="your exp here"></li>
Such a simple directive doesn't really need an attribute value but might give you lots of possiblities like conditionally adding a divider according to index, length, etc or something completely different.
I recently had the same problem in that I had to repeat an arbitrary collection of spans and images - having an element around them was not an option - there's a simple solution however, create a "null" directive:
app.directive("diNull", function() {
return {
restrict: "E",
replace: true,
template: ""
};
});
You can then use a repeat on that Element, where element.url points to the template for that element:
<di-null ng-repeat="element in elements" ng-include="element.url" ></di-null>
This will repeat any number of different templates with no container around them
Note: hmm I could've sworn blind this removed the di-null element when rendering, but checking it again it doesn't...still solved my layout issues though...curioser and curioser...