Animations: the Angular Way

Avatar of Benjamin Simmons
Benjamin Simmons on (Updated on )

📣 Freelancers, Developers, and Part-Time Agency Owners: Kickstart Your Own Digital Agency with UACADEMY Launch by UGURUS 📣

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 .circleclass. If the boolean is true, then the element will have the .boxclass. If it is false, it will have the .circleclass. 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 ngAnimateas 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 ngRepeatdirective, 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:

ngRepeat Event Classes

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-hideand 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 that ngAnimate 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 your enter 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 that ngRepeat 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.