AngularJS is a major player in the JavaScript MVW framework world. ‘Thinking in Angular’ is something that can elude developers who come from jQuery or other DOM manipulation heavy libraries. There is an ‘Angular way’ to do things that is data-driven rather than using DOM traversal to drive view changes, and that can be hard to visualize when it comes to something like animations. Together, we will go through exactly how to animate with the tools provided by the Angular team.
ngAnimate
and the $animate
Service
The Angular core team gave us the ngAnimate
module so that we could give our apps a way to animate from a data driven ‘Angular way’, and so that we could hook into the events that Angular emits through some of its built-in directives.
Angular, unlike jQuery, focuses on binding our view to a JavaScript object through the use of controllers. This approach allows us to bind view values like input fields directly to a corresponding value in a JavaScript object and trigger view changes through data changes, or vice versa.
So then, just how do we hook animations into these events if they could happen from either the view or the corresponding object being changed?
First, we need to add ngAnimate
to our project.
Including Angular Animation in our Project
As of 1.2.0, animations are no longer part of the Angular core, but are instead in their own separate module: ngAnimate
. In order to use the $animate
service, we need to include the animation library after Angular in our HTML file, like this: js/lib/angular-animate.js
As an alternative, you can also use the CDN or bower
to install angular-animate
:
$ bower install --save angular-animate
However you choose to install it, make sure to include it in your source file, like so:
<script src="js/lib/angular.js"></script>
<script src="js/lib/angular-animate.js"></script>
Next, we will have to include the ngAnimate
module as a dependency to our app. This can be done when we instantiate our Angular app, like so:
angular.module('myApp', ['ngAnimate']);
Now that ngAnimate
is included in our project (and as long as we do not have an injector error or something like that in our console) we can begin to create animations with Angular!
CSS3 Transitions
The easiest way to include animations in any application is by using CSS3 transitions. This is because they are completely class-based, which means that the animation is defined in a class and, as long as we use that class in our HTML, the animation will work in the browser.
CSS transitions are animations that allow an HTML element to steadily change from one style to another. To define a transition, we need to specify the element we want to add an effect to, and the duration of said effect.
First, let’s take a look at a simple example of a CSS3 transition, and then we can see how to make use of this knowledge from a data-driven Angular app.
Let’s create a simple div
inside a container div
and apply two classes to it: one for basic styling and one for our transition.
<div class="container">
<div class="box rotate"></div>
</div>
Now we can add transitions for either the hover state or static state of the element:
.box {
margin: 50px auto;
background: #5FCF80;
width: 150px;
height: 150px;
}
.box:hover {
transform: rotate(360deg);
background: #9351A6;
border-radius: 50%;
}
.rotate {
transition: all 0.5s ease-in-out;
}
.rotate:hover {
transition: all 1s ease-in-out;
}
This applies two states to our div
: one normal state and one for when we hover over the div
. The transitions defined in the .rotate
and .rotate:hover
classes tell the browser how to transition between these two states when we trigger the hover
and mouseleave
events.
We end up with an effect like this:
Basic CSS3 Transition
Angular Data-Driven CSS3 Animation
Now let’s see how we could do something like that in an Angular app, and bind this same functionality to some data within our application.
Instead of doing this transition on :hover
, we can create a simple animation by binding transitions to one class, .rotate
, and create a class for both the “box” and “circle” states of the div
. This enables us to switch between classes using the ng-class
directive built into Angular.
.box {
margin: 20px auto;
background: #5FCF80;
width: 150px;
height: 150px;
}
.circle {
transform: rotate(360deg);
background: #9351A6;
border-radius: 50%;
margin: 20px auto;
width: 150px;
height: 150px;
}
.rotate {
transition: all 1s ease-in-out;
}
To do this, we will need to set up our Angular app and create a conditional statement in the ng-class
directive to switch the class based on the value of a boolean on the $scope
.
<div ng-app="myApp" ng-controller="MainCtrl">
<div class="container">
<input type="checkbox" ng-model="boxClass" />
<div class="box rotate" ng-class="{'box': boxClass, 'circle': !boxClass} "></div>
</div>
</div>
Now let’s set up our JavaScript:
angular.module('myApp', [])
.controller('MainCtrl', function($scope) {
$scope.boxClass = true;
});
Here, we bind the boolean value that is attached to $scope.boxClass
to whether or not the element should have the .box
or .circle
class. If the boolean is true, then the element will have the .box
class. If it is false, it will have the .circle
class. This allows us to trigger a CSS3 transition by changing the value of our data, with no DOM manipulation whatsoever.
This does not use the $animate
service, but I wanted to provide an example of an instance that you could use CSS3 alone and not have to rely on $animate
and ngAnimate
.
The result of this is an animation that is triggered strictly by a data change when we change the underlying boolean by clicking the checkbox.
Angular Data-Driven CSS3 Transition
Transitions with $animate
If we want to leverage CSS3 transitions and the $animate
service, then we need to know a couple things about how $animate
works behind the scenes.
The $animate
service supports several directives that are built into Angular. This is available without any other configuration, and allows us to create animations for our directives in plain CSS. To use animations in this way, you do not even need to include $animate
in your controller; just include ngAnimate
as a dependency of your Angular module.
Once you include ngAnimate
in your module, there is a change in how Angular handles certain built-in directives. Angular will begin to hook into and monitor these directives, and add special classes to the element on the firing of certain events. For example, when you add, move, or remove an item from an array which is being used by the ngRepeat
directive, Angular will now catch that event, and add a series of classes to that element in the ngRepeat
.
Here you can see the classes that ngAnimate
adds on the enter event of an ngRepeat
:
The attached CSS classes take the form of ng-{EVENT}
and ng-{EVENT}-active
for structural events like enter, move, or leave. But, for class-based animations, it takes the form of {CLASS}-add
, {CLASS}-add-active
, {CLASS}-remove
, and {CLASS}-remove-active
. The exceptions to these rules are ng-hide
and ng-show
. Both of these directives have add and remove events that are triggered, just like ng-class
, but they both share the .ng-hide
class, which is either added or removed when appropriate. You will also see ngAnimate
add a .ng-animate
class to some of these directives on animation.
Below is a table that illustrates some of the built-in directives, the events that fire, and classes that are temporarily added when you add ngAnimate
to your project:
Built-In Directives’ $animate
Events
Directive | Event(s) | Classes |
---|---|---|
ngRepeat | enter | ng-enter, ng-enter-active |
leave | ng-leave, ng-leave-active | |
move | ng-move, ng-move-active | |
ngView, ngInclude, ngSwitch, ngIf | enter | ng-enter, ng-enter-active |
leave | ng-leave, ng-leave-active | |
ngClass | add | ng-add, ng-add-active |
remove | ng-remove, ng-remove-active | |
ngShow, ngHide | add, remove | ng-hide |
Angular will automatically detect that CSS is attached to an animation when the animation is triggered, and add the .ng-{EVENT}-active
class until the animation has run its course. It will then remove that class, and any other added classes, from the DOM.
Below is an example of using CSS3 transitions to animate a ngRepeat
directive. In it, we attach a transition to the base class—.fade
in this case—and then piggyback off of the classes thatngAnimate
will add to the li
elements when they are added and removed from the array. Once again, this allows us to have data-driven animations — the Angular way.
ngRepeat
$animate
Powered CSS3 Transitions
As we can see, Angular’s ngAnimate
gives us the ability to easily tap into events and leverage the power of CSS3 transitions to do some really cool, natural animations on our directives. This is by far the easiest way we can do animations for our Angular apps, but now we will look at some more complex options.
CSS3 Animations
CSS3 animations are more complicated than transitions, but have much of the same implementation on the ngAnimate
side. However, in the CSS we will be using an @keyframes
rule to define our animation. This is done in much the same way that we did our basic transition earlier, except we use the animation
keyword in our CSS and give the animation a name like this:
@keyframes appear {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes disappear {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
Here we have created an appear
and disappear
animation that can be triggered through CSS in the place that our transition was before.
.fade.ng-enter {
animation: 2s appear;
}
.fade.ng-leave {
animation: 1s disappear;
}
The difference this time, as you can see above, is that we no longer have to use .ng-enter-active
or .ng-leave-active
, but rather we can attach the animation to .ng-leave
and .ng-active
and the animation will trigger at the appropriate times because of ngAnimate
. This is not a particularly better way to do it than our transition method above, but it illustrates how to use CSS3 animations, which can be MUCH more powerful than this simple effect.
The final result of this animation when applied to our previous grocery list ngRepeat
example will look something like this:
ngRepeat $animate
Powered CSS3 Animations
JavaScript Animations
Now for the elephant in the room: JavaScript animations with AngularJS.
Angular is entirely data-driven with its fancy two-way data binding—that is, until it isn’t. This is one of the most confusing things about coming from jQuery to Angular. We are told to re-learn how we think and throw out DOM manipulation in favor of bindings, but then, at some point, they throw it right back at us later. Well, welcome to that full-circle point.
JavaScript animation has one major advantage—JavaScript is everywhere, and it has a wider acceptance than some advanced CSS3 animations. Now, if you are just targeting modern browsers, then this probably won’t be an issue for you, but if you need to support browsers that do not support CSS transitions, then you can easily register a JavaScript animation with Angular and use it over and over in your directives. Basically, JavaScript has more support in older browsers, and therefore, so do JavaScript animations.
When you include ngAnimate
as a dependency of your Angular module, it adds the animation
method to the module API. What this means is that you can now use it to register your JavaScript animations and tap into Angular hooks in built-in directives like ngRepeat
. This method takes two arguments: className(string)
and animationFunction(function)
.
The className
parameter is simply the class that you are targeting, and the animation function can be an anonymous function that will receive both the element
and done
parameters when it is called. The element
parameter is just that, the element as a jqLite object, and the done
parameter is a function that you need to call when your animation is finished running so that angular can continue on it’s way and knows to trigger that the event has been completed.
The main thing to grasp here however, is what needs to be returned from the animation function. Angular is going to be looking for an object to be returned with keys that match the names of the events that you want to trigger animations on for that particular directive. If you are unsure what the directive supports, then just refer to my table above.
So for our ngRepeat
example, it would look something like this:
return {
enter: function(element, done) {
// Animation code goes here
// Use done() in your animation callback
},
move: function(element, done) {
// Animation code goes here
// Use done() in your animation callback
},
leave: function(element, done) {
// Animation code goes here
// Use done() in your animation callback
}
}
And if we tie this all together with the same old boring (sorry) ngRepeat
grocery list example and use jQuery for the actual animations:
var app = angular.module('myApp', ['ngAnimate'])
.animation('.fade', function() {
return {
enter: function(element, done) {
element.css('display', 'none');
$(element).fadeIn(1000, function() {
done();
});
},
leave: function(element, done) {
$(element).fadeOut(1000, function() {
done();
});
},
move: function(element, done) {
element.css('display', 'none');
$(element).slideDown(500, function() {
done();
});
}
}
})
Now, let me break down what is going on.
We can get rid of any CSS we previously had on the .fade
class, but we still need some kind of class to register the animation on. So, for continuity’s sake, I just used the good old .fade
class.
Basically, what happens here is that Angular will register your animation functions and call them on that specific element when that event takes place on that directive. For example, it will call yourenter
animation function when a new item enters an ngRepeat
.
These are all very basic jQuery animations and I will not go into them here, but it is worth noting thatngRepeat
will automatically add the new item to the DOM when it is added to the array, and that said item will be immediately visible. So, if you are trying to achieve a fade in effect with JavaScript, then you need to set the display to none immediately before you fade it in. This is something you could avoid with CSS animations and transitions.
Let’s tie it all together and see what we get:
ngRepeat $animate
Powered JavaScript Animations
Conclusion
The ngAnimate
module is a bit of a misleading name.
Granted, I could not come up with a better name if I tried, but it does not actually DO any animations. Rather, it provides you with access into Angular’s event loop so that you can do your own DOM manipulation or CSS3 animations at the proper, data-driven point. This is powerful in its own right because we are doing it ‘the Angular way’ instead of trying to force our own logic and timing onto a very particular framework.
Another benefit of doing your animations with ngAnimate
is that once you write your animations for that directive, they can be packed up nicely and moved on to other projects with relative ease. This, in my book, is always a good thing.