Most of the time, each ngRepeat directive in AngularJS renders a single, homogeneous set of data. But sometimes, the user interface (UI) of your application requires a list to contain several commingled sets of data. In such cases, not only do the individual subsets of data contain potentially-overlapping unique identifiers, they are also likely to require different rendering templates. The ngRepeat directive can handle this, as long as you take a little extra care in preparing your View Model (VM).
When you need to render several commingled sets of data using a single ngRepeat directive in AngularJS, you have to worry about two things:
- Making sure each item can be tracked by a unique identifier.
- Making sure each item has a "type" indicator for differential rendering.
Technically, you can probably get away without the first consideration (a set-wide unique identifier); but, I would recommend this approach - it's good to be able to use the "track by" expression as it will cut down on unnecessary DOM (Document Object Model) node destruction and re-creation, which is a nice performance boost.
The second consideration - a type indicator - can be used inside the ngRepeat template to define individual sub-templates for each data type. While AngularJS 1.2 doesn't like using two different types of transclusion on the same element, there are some exceptions. Luckily, the ngRepeat-ngSwitch combination is one of those exceptions and we can use ngSwitch to handle the template rendering.
To see this in action, I've created a demo that lists out the "people" on your team. This list is composed of both real users and pending invitations (to the team). You will see that when I create the data set used in the ngRepeat directive, I am taking care to create both a unique identifier - uid - as well as a type differentiator - isInvitation.
My biggest concern with this approach is that I believe that the sub-templates keep getting re-compiled every time the ngRepeat element is cloned. While I am not exactly sure how AngularJS is working under the hood, I do know that the ngSwitchWhen directive uses "transclude:element"; this means that the HTML is pulled out of the DOM and the directive is provided with a linking function that can clone the extracted DOM element(s).
According to the AngularJS documentation:
transclude: Extract the contents of the element where the directive appears and make it available to the directive. The contents are compiled and provided to the directive as a transclusion function.
From the above documentation, it sounds like the ngSwitchWhen directives are going to be recompiled every time the ngRepeat element is cloned, injected into the DOM, and then linked to the child scope. This is less than ideal. What we'd really like is for the ngSwitchWhen to be compiled once, along with the ngRepeat directive, and then simply cloned and linked with each ngRepeat element. Unfortunately, that kind of a solution will take a little bit more "noodling" on my part.
Now, you could forego the compiling and the transclusion by changing the ngSwitchWhen directives to simply be ngHide/ngShow. But, in that case, you're trading in one performance problem (repeat compiling) for another performance problem (a higher number of data bindings that add overhead to each each digest). Plus, I don't love the idea of having hidden data bindings that I know are causing suppressed exceptions.
While there might be some aspects of this approach that are sub-optimal, I have to say that it has been quite successful for me. Personally, I'm not crazy about the idea of having a user interface that commingles different sets of data in a single list. But, if that's what the product team wants, well then, that's what they get.
Want to use code from this post? Check out the license.