As of AngularJS 1.2, directives that conditionally include content have also supported "enter" and "leave" style animations (with the help of the ngAnimate module). For most of these directives - like ngIf, ngInclude, and ngSwitch - animating the "enter" state makes a lot of sense. But, with ngRepeat, which is a much more stateful directive, animating the initial rendering of the data can lead to a kind of junky user experience. As such, it'd be nice to prevent the initial ngRepeat animation while still allowing animation on subsequent collection mutations.
When an item is added to or removed from a list, it makes sense to have ngRepeat animate that transition. The movement pulls the user's eye to the change in data and allows the user to build a more natural, more organic mental model of what's going on. But, animating every item in the collection simultaneously on data-load makes little sense. It doesn't help the mental model and, in all likelihood, the animation was probably designed for a single-item change.
In order to prevent the animation of the initial ngRepeat data-load, we can leverage the fact that AngularJS prevents nested animations from taking place (by default). If we animate-in the parent container only once the ngRepeat data is ready, it will implicitly block the ngRepeat animation. Once everything is rendered, however, additional changes to the ngRepeat collection will naturally lead to "enter," "leave," and "move" style animations.
NOTE: Nested animations are allowed if the parent container has the ngAnimateChildren directive.
Now, just because we are "animating" the parent container, it doesn't actually mean that we're "animating" it. What I mean is, the transition can be instant. If, for example, we use ngIf to show the parent container, AngularJS will consider it "animating" even if we don't have a transition on it. As such, the ngIf can instantly show the nested ngRepeat while still blocking the initial ngRepeat animation.
AngularJS is pretty player that way!
To demonstrate this, I am going to use the ngIf directive to hide the ngRepeat directive until the ngRepeat data has loaded (via a $timeout-based network latency simulation). What you'll notice (if you watch the video or try the demo) is that the initial list shows up instantly while additional items are animated in:
In the comments, I mention that the animation would not be blocked if you were to use the the ngShow or ngHide directives instead of ngIf. This is because ngShow and ngHide use CSS class-based transitions. Meaning, they work by adding and removing the ".ng-hide" CSS class, as opposed to creating and destroying actual DOM (Document Object Model) elements. Such class-based transitions do not prevent nested animations (from the documentation):
Class-based transitions refer to transition animations that are triggered when a CSS class is added to or removed from the element (via $animate.addClass, $animate.removeClass, $animate.setClass, or by directives such as ngClass, ngModel and form). They are different when compared to structural animations since they do not cancel existing animations nor do they block successive transitions from rendering on the same element. This distinction allows for multiple class-based transitions to be performed on the same element
When AngularJS 1.2 added animations, I was a bit concerned that the multi-transclusion limitation was overly problematic. But, the more I experiment with the ngAnimate module (which, admittedly, is very new to me), the more excited I get. The AngularJS team seems to have really thought about transitions very deeply.
Want to use code from this post? Check out the license.
Thanks for this in-deep primer!
My pleasure! Glad you enjoyed it.
Thanks! That saved my day :)