Search for Programming, AngularJS, Rails, Testing ...

Remastered Animation in AngularJS 1.2

Learn how to use the bigger and better animation features in AngularJS 1.2

AngularJS 1.2rc1 1.2 is out and this brings in a brand-new API for animations using ngAnimate. A lot has changed, but animations are better than ever. With 1.2, animations come full circle with new features such as support for ngClass, class expressions, and callbacks. Animations are also entirely class-based which means so long as a CSS class is present in your HTML code, then animations can be directly hooked into a working application.

This article will explain how to perform animations in AngularJS 1.2 with the new API setup. For earlier versions of AngularJS, please use 1.1.5 if you wish to stick to the older API of ngAnimate.

Last Updated

This page was first published on August 15th 2013 and was last updated on November 20th 2013.

Table of Contents

What has changed from 1.1.5 to 1.2

This section explains the changes between 1.1.5 and 1.2. If you are new to animations in AngularJS, please skip this section and hop over to the next one which explains how to use animations directly. If you haven't downloaded AngularJS 1.2 yet then you can find all the files you need right here.

Animations are not apart of the core anymore

The AngularJS core (which is everything inside of the angular.js JavaScript file) doesn't provide direct support for animations anymore. Instead what you need to do is include the angular-animate.js file (which is apart of the downloadable zip file present on the angularjs.org website or within the code.angularjs.org listing page) into your webpage and reference the ngAnimate module inside of your application module. So just make these changes and animations are back to being active in your application. (This is basically the same setup as with including ngResource into your application.)

<html ng-app="MyApp">
  <body>
    ...
    <script type="text/javascript" src="angular-1.2/angular.js"></script>
    <script type="text/javascript" src="angular-1.2/angular-animate.js"></script>
    <script type="text/javascript">
      //replace this with whatever your module ends up being
      var myApp = angular.module('MyApp', ['ngAnimate']);
    </script>
  </body>
</html>

The ng-animate="..." directive is deprecated

AngularJS 1.1.5 (and 1.1.4) both paid attention to the ng-animate directive/attribute when performing animations on common ng- directives as well as custom directives. This has changed in AngularJS 1.2 and the ng-animate directive is no longer used. Instead, all animations are resolved from the CSS class(es) present on the element when an animation event is triggered and additional CSS classes are applied to the element to specify which animation is occurring. In other words, when a directive such as ngRepeat inserts an element into the DOM, it triggers an animation (the enter animation) and the $animate service appends the ng-enter and ng-enter-active CSS classes to the element. Your CSS code then is defined on that combination of CSS classes and contains the transition/keyframe animation code for it to work.

So lets say that our repeat element HTML looks like this:

<div ng-init="items=['a','b','c','d','e','x']">
  <input placeholder="filter" ng-model="f" />
  <div ng-repeat="item in items | filter:f" class="repeat-item">
    {{item}}
  </div>
</div>

Then, when defining the actual CSS animation code for that ngRepeat item, the CSS selector code looks like so:

/* you can also define the transition style
   on the base class as well (.repeat-item) */
.repeat-item.ng-enter,
.repeat-item.ng-leave {
  -webkit-transition:0.5s linear all;
  transition:0.5s linear all;
}

.repeat-item.ng-enter,
.repeat-item.ng-leave.ng-leave-active {
  opacity:0;
}
.repeat-item.ng-leave,
.repeat-item.ng-enter.ng-enter-active {
  opacity:1;
}

ngShow and ngHide remove and add the .ng-hide class name

Before, in earlier versions of AngularJS, the ngShow and the ngHide directives set the CSS display property of the element, being set as hidden or visible, to display:block and display:none. This approach had some limitations and complications with any conflicting CSS styling and selectors.

Now, with AngularJS 1.2, the .ng-hide CSS class is applied to the element when it is set to be hidden (when ngShow is false or when ngHide is true) and removes it from the element when the element is set to be visible (when ngShow is true or when ngHide is false). By default, the CSS code for .ng-hide looks like so:

.ng-hide {
  display:none!important;
}

You may be wondering why we used the !important flag? Well, turns out that having a simple CSS class selector such as .ng-hide on an element can be very easily overridden by other conflicting CSS classes. The important flag fixes this, but this also means that you need to use important in your own CSS code to override it yourself. Fortunately, the .ng-hide CSS class is defined inside of a style tag at the top of the head element of the HTML document. This allows an easy override when using the important flag yourself.

When showing and hiding elements using ngShow and ngHide, the CSS code for performing animations has also changed and it uses a CLASS-remove and CLASS-add selector syntax. Here's what it looks like for CSS transitions:

/* you can use natural CSS transitions just
   as you would without using ngAnimate */
.my-elm {
  -webkit-transition:0.5s linear all;
  transition:0.5s linear all;
  opacity:1;
}

.my-elm.ng-hide {
  opacity:0;
}

.my-elm.ng-hide-add, .my-elm.ng-hide-remove {
  /* this needs to be here to make it visible during the animation
     since the .ng-hide class is already on the element rendering
     it as hidden. */
  display:block!important;
}

The JavaScript-enabled animations API has also changed for show and hide animations (this will be explained soon).

Changes to ngInclude and ngView

The ngInclude and ngView directives work similar to ngRepeat now. Instead of the contents getting replaced with a new element within, the ngInclude/ngView elements are cloned (transcluded) and the new template data is injected inside and then the animation is fired. This means that all template code which is downloaded and injected will always be housed inside one container element so you don't have to wrap your templates with a containing <div> tag. This also changes your CSS code since you won't be setting CSS styles for child elements anymore. (Take a look at the ngView and ngInclude examples later on in the article to see what I mean.)

Changes to the JavaScript animations API

JavaScript animations have been changed around a bit. Instead of an animation being defined one at a time, multiple animation events can be attached to a single CSS class (since animations are class based). So if you have a CSS class like .fade present on the element, then the matching JavaScript animation code to register a fade animation will look like so:

//remember to put the period!
myApp.animation('.fade', function() {
  return {
    //call done when the animation is over
    enter : function(element, done) {
      runTheAnimation(element, done);
      return function(cancelled) {
        if(cancelled) {
          stopTheAnimation(); 
        }
        else {
          completeTheAnimation(); 
        }
      } 
    },

    //leave and move are the same as enter
    leave : function(element, done) { },
    move : function(element, done) { },

    //this is called BEFORE the class is added
    beforeAddClass : function(element, className, done) {
      //...
    },

    //this is called AFTER the class is added
    addClass : function(element, className, done) {
      if(className == 'hide') {
        runTheHideAnimation(element, done);
      }
      else {
        runTheAnimation(element, done);
      }

      return function onEnd(element, done) { };
    },

    //this is called BEFORE the class is removed
    beforeRemoveClass : function(element, className, done) {
      //...
    },

    //this is called AFTER the class is removed
    removeClass : function(element, className, done) {
      if(className == 'hide') {
        runTheShowAnimation(element, done);
      }
      else {
        runTheAnimation(element, done);
      }

      return function onEnd(element, done) { };
    },

    /* this is called to ask if the current animation can be
       disabled given the new animation className. This callback
       is entirely optional and is called on each animation callback */
    allowCancel : function(element, event, className) {
      
    } 
  };
});

The show and hide animation events are gone. To get this to work with 1.2, just make an if statement to check for the removal and addition of the hide class (look at the code above to see this in action).

$animator is now $animate

The former $animator service has been renamed to $animate. In addition to the name change, the implementation is also a little different. As expected with the ngShow/ngHide changes, the $animate.show() and $animate.hide() have been removed and so has the $animate.animate() method. To hide and show an element, just do $animate.addClass('ng-hide') and $animate.removeClass('ng-hide') and to perform a custom animation just run $animate.addClass('some-animation'). Also, you don't need to "construct" animate object anymore--just use $animate directly.

Callbacks are also supported. This is explained in a later section.

Why did we make these changes?

You may be wondering or frustrated at the fact that the ngAnimate attribute is gone and animations are purely CSS class-based or why we changed around the JavaScript API. Well there are a number of reasons why we did this:

  • We wanted to have support for ngClass animations, but this meant that both ngAnimate and ngClass had to be defined together on the same element referencing the same CSS class labels. This would be very difficult to test and disabling animations would have to happen on both the ngClass attribute as well as the ngAnimate attribute.
  • We wanted a better API for plugins and other animation tools to hook into animations in AngularJS without the developer being reliant on referencing each animation one by one via ngAnimate. This way, as long as there is at least one base CSS class, then all animation events can be defined in the JavaScript API.

Presentation Slides

A new deck of slides by the super talented @gsklee has been put together which goes over the changes from 1.1.5 to 1.2.

There is also a sweet collection of great demos for the animations. If you're familiar with the old 1.1.5 setup or new to ngAnimate then please take a look at the slides.

Click here to view the slides

How to make animations in AngularJS

There are three methods to perform animations in AngularJS: CSS Transitions, CSS Keyframe Animations and JavaScript Animations. Before we get started, please make sure that you've included the angular-animate.js file into your webpage and set the ngAnimate module as a dependency within your module. If you're a bit lost, then this is explained in the section prior to this one in this article.

For each animation methods explained below, lets imagine that we have our template HTML code like so:

<div ng-init="on=true">
  <button ng-click="on=!on">Toggle On/Off</button>
  <div class="my-special-animation" ng-if="on">
    This content will enter and leave
  </div>
</div>

CSS Transitions

CSS Transitions are the easiest and fastest way to attach animations to your application. CSS Transitions are supported in all browsers expect for IE9 and below, so you should be well on your way to using them guilt-free!

Since ngAnimate in 1.2 is class-based, this means that so long as a matching CSS class (the starting CSS class) is present within your HTML template code then AngularJS will pick up the animation (just remember to set ngAnimate as a module). However, Despite animations being class-based, your CSS code needs to follow AngularJS' CSS naming convention and you also must include a CSS class on the element so that you can target animations to the element (the .my-special-animation CSS class is used with the ngIf code demonstrated above).

The example below shows how to perform an animation animating the element from red to blue when inserted into the page by ngIf.

/* starting animations */
.my-special-animation.ng-enter {
  -webkit-transition:0.5s linear all;
  transition:0.5s linear all;

  background:red;
}

/* destination animations */
.my-special-animation.ng-enter.ng-enter-active {
  background:blue;
}

And yes ngAnimate pays attention to the transition-delay property!

The .ng-enter and .ng-enter-active CSS classes are generated and appended to the element by AngularJS when the ngIf tells it that it's adding a new item into the repeat list. Depending on the animation, other CSS classes are added. Here's a breakdown of which directives add which CSS classes.

Event Staring CSS Class Ending CSS class Directives that fire it
enter .ng-enter .ng-enter-active ngRepeat, ngInclude, ngIf, ngView
leave .ng-leave .ng-leave-active ngRepeat, ngInclude, ngIf, ngView
move .ng-move .ng-move-active ngRepeat

In addition, ngAnimate also supports CSS-based animations which follow a similar naming convention. Here's a breakdown of that:

Action Staring CSS Class Ending CSS class Directives that fire it
hide an element .ng-hide-add .ng-hide-add-active ngShow, ngHide
show an element .ng-hide-remove .ng-hide-remove-active ngShow, ngHide
adding a class to an element .CLASS-add .CLASS-add-active ngClass and class="{{expression}}"
removing a class from an element .CLASS-remove .CLASS-remove-active ngClass and class="{{expression}}"

CSS Keyframe Animations

CSS Keyframe animations are more extensive than Transitions and they're supported by the same browsers (other than IE9 and below). The CSS naming style is similar, but there is no need to use an -active class since keyframe animations are fully managed within a CSS @keyframes declaration block. Here's the same example as before (animating an enter event with ngIf from red to blue):

/* starting animations */
.my-special-animation.ng-enter {
  -webkit-animation:0.5s red-to-blue;
  animation:0.5s red-to-blue;
}

@keyframes red-to-blue {
  from { background:red; }
  to { background:blue; }
}

@-webkit-keyframes red-to-blue {
  from { background:red; }
  to { background:blue; }
}

Remember, there is no need for any destination CSS class styling (you don't need the -active class). And yes ngAnimate pays attention to the animation-delay and animation-iteration-count properties!

JavaScript Animations

If you want to perform animations that have more control then you can always use JavaScript animations. This works by defining a factory-like function inside of your module code like so:

myApp.animation('.my-special-animation', function() {
  return {
    enter : function(element, done) {
      jQuery(element).css({
        color:'#FF0000'
      });

      //node the done method here as the 2nd param
      jQuery(element).animate({
        color:'#0000FF'
      }, done);

      return function(cancelled) {
        /* this (optional) function is called when the animation is complete
           or when the animation has been cancelled (which is when
           another animation is started on the same element while the
           current animation is still in progress). */
        if(cancelled) {
          jQuery(element).stop();
        }
      }
    },

    leave : function(element, done) { done(); },
    move : function(element, done) { done(); },

    beforeAddClass : function(element, className, done) { done(); },
    addClass : function(element, className, done) { done(); },

    beforeRemoveClass : function(element, className, done) { done(); },
    removeClass : function(element, className, done) { done(); },

    allowCancel : function(element, event, className) {}
  };
});

JavaScript animations are triggered in the same as was transitions and keyframe animations. So, as long as there is a matching CSS class present on the element and that CSS class is defined as a JavaScript animation (in this case .my-special-animation is used), then the JavaScript animation will fire the matching event function (in this case the enter animation). The starting CSS class mentioned before will be also present on the element during the JavaScript animation (in this case .ng-enter), but not the ending one (in this case .ng-enter-active) unless a matching CSS transition animation is found on the element.

It is up to you how to perform a JavaScript animation. AngularJS doesn't care what you do. Just remember to call the done() function when the animation is over (in this case the jQuery(element).animate() calls it since it is passed in as a parameter). Also, an optional onEnd function can be returned which will be fired when the animation is complete or when the animation has been cancelled (the boolean parameter is true when a cancellation has occurred).

You can also provide a allowCancel callback which can be used to decide if a followup animation will replace the existing animation if there is an animation in progress. Deciding whether or not to cancel an existing animation is useful when dealing with random CSS classes which may not have animations associated with them.

Animating ngRepeat

ngRepeat animations:
enter for when a DOM element is added into the repeat list. leave for when a DOM element is removed from the repeat list. move for when a DOM element is moved from one position in the repeat list to another position.

The ngRepeat directive fires off the enter, leave and move events when items are inserted into, removed from, and moved around within the repeated list of items. All that you have to do is have a CSS class present and apply the matching Transition, Keyframe or JavaScript animation code to handle the animation on that CSS class value.

The code examples below show how to perform the same animation for ngRepeat using CSS Transitions, CSS Keyframe Animations, as well as JavaScript Animations. So if you want to use the code example below then just copy one of animation code examples!

<div ng-init="items=[1,2,3,4,5,6,7,8,9]">
  <input placeholder="Filter Repeat Items..." ng-model="f" /> 
  <div data-ng-repeat="item in items | filter:f track by item" class="my-repeat-animation">
    {{item}}
  </div>
</div>
.my-repeat-animation.ng-enter, 
.my-repeat-animation.ng-leave, 
.my-repeat-animation.ng-move {
  -webkit-transition: 0.5s linear all;
  transition: 0.5s linear all;
  position:relative;
}

.my-repeat-animation.ng-enter {
  left:-10px;
  opacity:0;
}
.my-repeat-animation.ng-enter.ng-enter-active {
  left:0;
  opacity:1;
}

.my-repeat-animation.ng-leave {
  left:0;
  opacity:1;
}
.my-repeat-animation.ng-leave.ng-leave-active {
  left:-10px;
  opacity:0;
}

.my-repeat-animation.ng-move {
  opacity:0.5;
}
.my-repeat-animation.ng-move.ng-move-active {
  opacity:1;
}
.my-repeat-animation.ng-enter,
.my-repeat-animation.ng-leave,
.my-repeat-animation.ng-move {
  position:relative;
}

.my-repeat-animation.ng-enter {
  -webkit-animation: 0.5s repeat-animation-enter;
  animation: 0.5s repeat-animation-enter;
}

.my-repeat-animation.ng-leave {
  -webkit-animation: 0.5s repeat-animation-leave;
  animation: 0.5s repeat-animation-leave;
}

.my-repeat-animation.ng-move {
  -webkit-animation: 0.5s repeat-animation-move;
  animation: 0.5s repeat-animation-move;
}

@keyframes repeat-animation-enter {
  from {
    left:-10px;
    opacity:0;
  }
  to {
    left:0;
    opacity:1;
  }
}

@-webkit-keyframes repeat-animation-enter {
  from {
    left:-10px;
    opacity:0;
  }
  to {
    left:0;
    opacity:1;
  }
}

@keyframes repeat-animation-leave {
  from {
    left:0;
    opacity:1;
  }
  to {
    left:-10px;
    opacity:0;
  }
}

@-webkit-keyframes repeat-animation-leave {
  from {
    left:0;
    opacity:1;
  }
  to {
    left:-10px;
    opacity:0;
  }
}

@keyframes repeat-animation-move {
  from { opacity:0.5; }
  to { opacity:1; }
}

@-webkit-keyframes repeat-animation-move {
  from { opacity:0.5; }
  to { opacity:1; }
}
myApp.animation('.my-repeat-animation', function() {
  return {
    enter : function(element, done) {
      jQuery(element).css({
        position:'relative',
        left:-10,
        opacity:0
      });
      jQuery(element).animate({
        left:0,
        opacity:1
      }, done);
    },

    leave : function(element, done) {
      jQuery(element).css({
        position:'relative',
        left:0,
        opacity:1
      });
      jQuery(element).animate({
        left:-10,
        opacity:0
      }, done);
    },

    move : function(element, done) {
      jQuery(element).css({
        opacity:0.5
      });
      jQuery(element).animate({
        opacity:1
      }, done);
    }
  };
});

Animating ngInclude, ngView and ngIf

ngInclude, ngView and ngIf animations:
enter for when the new content is to be animated in. leave for when the former content is to be animated out.

Much like ngRepeat, each of these directive trigger the enter and leave animation events when content is injected and removed from the container element. When old content is removed and new content is added then both sets of content will appear on the page if an animation is present and this allows for some cool animations to take place. Keep in mind that, while the template may be cached, the new element that is entered into the page is always a new instance of that element.

The code examples below show how to perform the same animation for ngView using CSS Transitions, CSS Keyframe Animations, as well as JavaScript Animations. So if you want to use the code example below then just copy one of animation code examples!

<div ng-init="myExp='one'">
  <button ng-click="myExp='one'">One</button>
  <button ng-click="myExp='two'">Two</button>
  <button ng-click="myExp='three'">Three</button>
  <div class="my-slide-container">
    <div ng-include="myExp" class="my-slide-animation"></div>
  </div>
</div>

<script type="text/ng-template" id="one">
  one
</script>

<script type="text/ng-template" id="two">
  two
</script>

<script type="text/ng-template" id="three">
  three
</script>
.my-slide-container {
  position:relative;
}
.my-slide-animation.ng-enter, .my-slide-animation.ng-leave {
  -webkit-transition:0.5s linear all;
  transition:0.5s linear all;

  position:absolute;
  top:0;
  left:0;
  right:0;
  height:600px;
}

.my-slide-animation.ng-enter {
  z-index:100;
  top:600px;
  opacity:0;
}
.my-slide-animation.ng-enter.ng-enter-active {
  top:0;
  opacity:1;
}

.my-slide-animation.ng-leave {
  z-index:101;
  top:0;
  opacity:1;
}
.my-slide-animation.ng-leave.ng-leave-active {
  top:-600px;
  opacity:0;
}
.my-slide-container {
  position:relative;
}

.my-slide-animation.ng-enter, .my-slide-animation.ng-leave {
  position:absolute;
  top:0;
  left:0;
  right:0;
  height:600px;
}

.my-slide-animation.ng-enter {
  -webkit-animation: 0.5s slide-animation-enter;
  animation: 0.5s slide-animation-enter;
}

.my-slide-animation.ng-leave {
  -webkit-animation: 0.5s slide-animation-leave;
  animation: 0.5s slide-animation-leave;
}

@keyframes slide-animation-enter {
  from {
    z-index:100;
    top:600px;
    opacity:0;
  }
  to {
    top:0;
    opacity:1;
  }
}

@keyframes slide-animation-leave {
  from {
    z-index:101;
    top:0;
    opacity:1;
  }
  to {
    top:-600px;
    opacity:0;
  }
}

@-webkit-keyframes slide-animation-enter {
  from {
    z-index:100;
    top:600px;
    opacity:0;
  }
  to {
    top:0;
    opacity:1;
  }
}

@-webkit-keyframes slide-animation-leave {
  from {
    z-index:101;
    top:0;
    opacity:1;
  }
  to {
    top:-600px;
    opacity:0;
  }
}
myApp.animation('.my-slide-animation', function() {
  return {
    enter : function(element, done) {
      jQuery(element).css({
        position:'absolute',
        'z-index':100,
        top:600,
        opacity:0
      });
      jQuery(element).animate({
        top:0,
        opacity:1
      }, done);
    },

    leave : function(element, done) {
      jQuery(element).css({
        position:'absolute',
        'z-index':101,
        top:0,
        opacity:1
      });
      jQuery(element).animate({
        top:-600,
        opacity:0
      }, done);
    }
  };
});

Animating ngSwitch

ngSwitch animations:
enter for when the new content is to be animated in. leave for when the former content is to be animated out.

The ngSwitch directive is a touch different from ngInclude and ngIf. While the animation events are the same, the HTML code is different and therefore the CSS classes are applied to all switch elements. This is because ngSwitch expects its child elements to be hard-coded in the HTML (so none of the child DOM elements are dynamically generated) and therefore each element needs to have the animation CSS class apart of it. If you really want to get around you this then you can set a CSS class on the parent element and style the .ng-enter and .ng-leave child elements via a selector, but this limits you when using JavaScript-enabled animations (since you'll have to fetch the child element inside of your JavaScript animation to perform an animation on it).

The code examples below show how to perform the same animation for ngSwitch using CSS Transitions, CSS Keyframe Animations, as well as JavaScript Animations. So if you want to use the code example below then just copy one of animation code examples!

<div ng-init="myExp='one'">
  <button ng-click="myExp='one'">One</button>
  <button ng-click="myExp='two'">Two</button>
  <button ng-click="myExp='three'">Three</button>
  <div ng-switch="myExp" class="my-switch-container">
    <div ng-switch-when="one" class="my-switch-animation">
      one
    </div>
    <div ng-switch-when="two" class="my-switch-animation">
      two
    </div>
    <div ng-switch-when="three" class="my-switch-animation">
      three
    </div>
  </div>
</div>
.my-switch-container {
  position:relative;
  height:500px;
}

.my-switch-animation.ng-enter,
.my-switch-animation.ng-leave {
  -webkit-transition:0.5s linear all;
  transition:0.5s linear all;
  height:500px;

  position:absolute;
  top:0;
  left:0;
  right:0;
}

.my-switch-animation.ng-enter {
  left:100%;
}

.my-switch-animation.ng-leave,
.my-switch-animation.ng-enter.ng-enter-active {
  left:0;
}

.my-switch-animation.ng-leave.ng-leave-active {
  left:-100%;
}
.my-switch-container {
  position:relative;
  height:500px;
}

.my-switch-animation.ng-enter, .my-switch-animation.ng-leave {
  height:500px;
  position:absolute;
  top:0;
  left:0;
  right:0;
}

.my-switch-animation.ng-enter {
  -webkit-animation:0.5s switch-enter;
  animation:0.5s switch-enter;
}

.my-switch-animation.ng-leave {
  -webkit-animation:0.5s switch-leave;
  animation:0.5s switch-leave;
}

@keyframes switch-enter {
  from { left:100%; }
  to { left:0; }
}

@-webkit-keyframes switch-enter {
  from { left:100%; }
  to { left:0; }
}

@keyframes switch-leave {
  from { left:0; }
  to { left:-100%; }
}

@-webkit-keyframes switch-leave {
  from { left:0; }
  to { left:-100%; }
}
myApp.animation('.my-switch-animation', function() {
  return {
    enter : function(element, done) {
      element = jQuery(element);
      element.css({
        position:'absolute',
        height:500,
        left:element.parent().width()
      });
      element.animate({
        left:0
      }, done);
    },

    leave : function(element, done) {
      element = jQuery(element);
      element.css({
        position:'absolute',
        height:500,
        left:0
      });
      element.animate({
        left:-element.parent().width()
      }, done);
    }
  };
});

Animating ngClass and class attribute expressions

ngClass and class-expression animations:
add for when the new CSS class is to be animated in. remove for when the old CSS class is to be animated out.

AngularJS 1.2 brings forth a new feature where any class-changing behavior within the ngClass attribute can be animated. This feature is now also used with ngShow and ngHide so before we explain how that works, lets take a look at how class-changing animation works.

Whenever a CSS class changes (whether via ngClass or if an expression is used within a class attribute) then the compiler picks it up and triggers the animations for both when the new CSS classes are added or the old ones are removed from the element. The CSS naming conventions are also a little different. Instead of prefixing the element with a CSS class in the form of .ng-event and .ng-event-active, the CSS classes applied are just an extended version of the new CSS class(es) being added in the form of .CLASS-add and .CLASS-remove (the active classes are also applied). The ngClass animations do not work by waiting for the final CSS class to be added, but instead apply the active class and the new CSS class on the element at the same time (.CLASS-add is added to the element first before the CLASS-active and CLASS are added). Upon removal, the CLASS-remove CSS class is added first and then CLASS-remove-active is added and the CLASS value is removed from the element.

The code examples below show how to perform the same animation for ngClass using CSS Transitions, CSS Keyframe Animations, as well as JavaScript Animations. So if you want to use the code example below then just copy one of animation code examples!

<div ng-init="disabled=false">
  <button ng-click="disabled=!disabled">Toggle Disable</button>
  <div ng-class="{disabled:disabled}" class="my-toggle-animation">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit...
  </div>
</div>
.my-toggle-animation {
  -webkit-transition:0.5s linear all;
  transition:0.5s linear all;

  background:white;
  padding:10px;
  border:1px solid black;
  color:black;
}

.my-toggle-animation.disabled {
  background:grey;
  color:#555;
  border-color:#555;
}
.my-toggle-animation {
  padding:10px;
  border:1px solid black;
}

.my-toggle-animation.disabled-add {
  -webkit-animation:0.5s disable-on;
  animation:0.5s disable-on;
}

@keyframes disable-on {
  from {
    background:white;
    color:black;
    border-color:black;
  }
  to {
    background:grey;
    color:#555;
    border-color:#555;
  }
}

@-webkit-keyframes disable-on {
  from {
    background:white;
    color:black;
    border-color:black;
  }
  to {
    background:grey;
    color:#555;
    border-color:#555;
  }
}

.my-toggle-animation.disabled-remove {
  -webkit-animation:0.5s disable-off;
  animation:0.5s disable-off;
}

.my-toggle-animation.disabled {
  background:grey;
  color:#555;
  border-color:#555;
}

@keyframes disable-off {
  from {
    background:grey;
    color:#555;
    border-color:#555;
  }
  to {
    background:white;
    color:black;
    border-color:black;
  }
}

@-webkit-keyframes disable-off {
  from {
    background:grey;
    color:#555;
    border-color:#555;
  }
  to {
    background:white;
    color:black;
    border-color:black;
  }
}
myApp.animation('.my-toggle-animation', function() {
  return {
    beforeAddClass : function(element, className, done) {
      if(className == 'disabled') {
        jQuery(element).animate({
          'color':'#666666',
          'background':'#AAAAAA'
        }, done);
      }
      else {
        done();
      }
    },

    beforeRemoveClass : function(element, className, done) {
      if(className == 'disabled') {
        jQuery(element).animate({
          'color':'#000000',
          'background':'#FFFFFF'
        }, done);
      }
      else {
        done();
      }
    }
  };
});

You can also use ngClass to hotswap the CSS classes on the element so that the next animation that is run can be run on a new set of CSS elements. This is essentially the same thing as before when using ngAnimate to change the animations directly on the scope.

Animating ngShow and ngHide

ngShow and ngHide animations:
ng-hide-add for when element is to be animated as hidden. ng-hide-remove for when element is to be animated as visible.

The ngShow and ngHide directives perform an animation whenever their expression changes. Whenever this occurs the .ng-hide class is added and removed from the element and, in between the CSS class addition and removal is when an animation is triggered. Just like with ngClass animations, the ng-hide class that is added follows the add and remove naming convention.

When the .ng-hide class is added to the element (after the animation is over) then the element will appear hidden (since the .ng-hide class has a style which sets display to none using a !important flag). When the .ng-hide class is removed then the element will appear visible for the duration of the animation (since the .ng-hide class is removed at the start of the animation). Therefore, to get around this, override the display property within your CSS animation code (check out the code examples below).

<div ng-init="isHidden=false">
  <button ng-click="isHidden=!isHidden">Show / Hide Element</button>
  <div class="my-show-hide-animation" ng-hide="isHidden">
    I am visible!
  </div>
</div>
.my-show-hide-animation.ng-hide-add,
.my-show-hide-animation.ng-hide-remove {
  -webkit-transition:0.5s linear all;
  transition:0.5s linear all;

  /* remember to add this */
  display:block!important;
  opacity:1;
}
.my-show-hide-animation.ng-hide {
  opacity:0;
}
.my-show-hide-animation.ng-hide-remove,
.my-show-hide-animation.ng-hide-add {
  /* remember, the .hg-hide class is added to element
     when the active class is added causing it to appear
     as hidden. Therefore set the styling to display=block
     so that the hide animation is visible */
  display:block!important;
}

.my-show-hide-animation.ng-hide-add {
  -webkit-animation:0.5s hide;
  animation:0.5s hide;
}

@keyframes hide {
  from { opacity:1; }
  to { opacity:0; }
}

@-webkit-keyframes hide {
  from { opacity:1; }
  to { opacity:0; }
}

.my-show-hide-animation.ng-hide-remove {
  -webkit-animation:0.5s show;
  animation:0.5s show;
}

@keyframes show {
  from { opacity:0; }
  to { opacity:1; }
}

@-webkit-keyframes show {
  from { opacity:0; }
  to { opacity:1; }
}
myApp.animation('.my-show-hide-animation', function() {
  return {
    beforeAddClass : function(element, className, done) {
      if(className == 'ng-hide') {
        jQuery(element).animate({
          opacity:0
        }, done);
      }
      else {
        done();
      }
    },
    removeClass : function(element, className, done) {
      if(className == 'ng-hide') {
        element.css('opacity',0);
        jQuery(element).animate({
          opacity:1
        }, done);
      }
      else {
        done();
      }
    }
  };
});

Looking at the JavaScript code above, you'll notice that we used beforeAddClass but not beforeRemoveClass. Why? Well, since .ng-hide class sets the element to not appear as visible, we want to perform our animation during the moments when the element is visible without changing the CSS or removing any styling. The beforeAddClass is called before .ng-hide is added and removeClass is called after it is removed.

Alright! That was easy! Right? The ngShow/ngHide animations are the same as ngClass animations, but just make sure to override the .ng-hide display property for the .ng-hide-add animations and you're set!

Rolling out animations with your own directives

If you want to control animations within your own directives you can do so by inject the $animate service into your directive definition function. Then, once injected, just use trigger the appropriate event by running the associated function on the $animate object (e.g. run $animate.enter(element, parent, after) when you want to perform an enter animation on the element node).

Here's a full breakdown of each function that can be called for the $animate service.

Event Function Description
enter $animate.enter(element, parent, after, callback); Appends the element object after the after node or within the parent node and then runs the enter animation on the element
leave $animate.leave(element, callback); Runs the leave animation and then removes the element form the DOM
move $animate.move(element, parent, after, callback); Moves the element node either after the after node or inside of the parent node and then runs the move animation on the element
addClass $animate.addClass(element, className, callback); Runs the addClass animation based on the className value and then adds the class to the element
removeClass $animate.removeClass(element, className, callback); Runs the removeClass animation based on the className value and then removes the class from the element

Here's a simple code example of how this works. Basically what we have is a directive that, when clicked, expands in size up until it loops around again.

myApp.directive('appClickToggle', function($animate) {
  return function(scope, element) {
    var currentStep = 0;
    var maxStep = 3;
    element.bind('click', function() {
      var remove = 'step-' + currentStep;
      $animate.removeClass(element, remove);
      currentStep = (currentStep + 1) % maxStep;
      var add = 'step-' + currentStep;
      $animate.addClass(element, add);
    });
  }
});
.my-slide-animation {
  width:200px;
  height:200px;
  position:absolute;
  left:50%;
  top:50%;
  margin-left:-100px;
  margin-top:-100px;
  background:#eee;
  border:1px solid black;

  -webkit-transition:0.5s linear all;
  transition:0.5s linear all;
}

.my-slide-animation.step-0 {
  -webkit-transform:scale(1);
  transform:scale(1);
  background:blue;
}

.my-slide-animation.step-1 {
  -webkit-transform:scale(1.5);
  transform:scale(1.5);
  background:red;
}

.my-slide-animation.step-2 {
  -webkit-transform:scale(2);
  transform:scale(2);
  background:green;
}

Enabling / Disabling Animations

Before, in AngularJS 1.1.5, animation could be easily enabled and disabled by changing the scope value which the ng-animate attribute paid attention to. Now, since this has been removed, just use ngClass instead. Since ngClass listens on the scope, then when you change a value, the classes on the element are changed and therefore the CSS classes are different and therefore different animations are picked up when the next animation is triggered. This also means that when you remove the animation-aware CSS classes then it will disable the animation.

With the exception of the older syntax, here is a link to an older article on yearofmoo which explains how to enable/disable animations in other ways.

Also, since animation in AngularJS is entirely class-based, you can simply remove or set a stylesheet-level media query on your animation.css file (if you placed your animations inside of a separate CSS file) and then only specific devices or media types can perform animations.

Click here to learn about disabling and enabling animations

Testing Animations

To test animations, make sure you have included the angular-animate.js as well as the angular-mocks.js file into your testing suite and load the correct modules in order. First load the ngAnimate module and then load the angular.mock module. This allows you intercept animations with the mocking tool in a synchronous manner. While this method does test for the DOM operations involved with an animation, it doesn't test the actual animation that is triggered. To make this work, you'll need to use an asynchronous test.

To test in an asynchronous manner, just include the ngAnimate module and run the animation for the specified amount of time and then run timeout within the test which, when the timeout is over, would then test the properties of the element. Or use a callback method for each function that is called. Since the animation is over, the new properties on the element are the expected effects of the animation. While this works, try your best to avoid anything asynchronous within your tests since it slows things down and your tests should always execute as fast as possible.

angular.module('MyApp', ['ngAnimate'])
  .animation('.enter-in', function() {
    return {
      enter : function(element, done) {
        jQuery(element).animate({
          opacity:1
        }, done);
      }
    }
  });
//you need to include angular-mocks.js into karma to make this work
describe('Testing Sync Animations', function() {

  beforeEach(module('MyApp'));
  beforeEach(module('mock.animate'));

  it("should synchronously test the animation",
    inject(function($animate, $document, $rootScope, $sniffer, $timeout) {

    var body = angular.element($document[0].body);
    var element = angular.element('<div class="enter-in">hello</div>');

    $animate.enter(element, body);
    $rootScope.$digest();
    expect(body.children().length).toBe(0);
    var interceptedElement = $animate.flushNext('enter').element;
    $timeout.flush();
    expect(interceptedElement).toEqual(element);
    expect(body.children().length).toBe(1);
  }));

});
//you need to include angular-mocks.js into karma to make this work
describe('Testing Async Animations', function() {

  beforeEach(module('MyApp'));

  /* the done() method is something mocha provides.
     to make this work in Jasmine, just follow their
     example with using a asynchronous test */
  it("should asynchronously test the animation", function(done) {
    inject(function($animate, $document, $rootScope) {
      var body = angular.element($document[0].body);
      var element = angular.element('<div class="enter-in">hello</div>');

      element.css('opacity', 0);
      $animate.enter(element, body, null, function() {
        expect(element.css('opacity')).to.equal("1");
        done(); 
      });
    });
  });

});

What about 3rd-party CSS/JS libraries?

Animate.css Integration

One of the major reasons why the ngAnimate directive was removed was because it was difficult to integrate 3rd-party CSS animation libraries into your code. A good example is with the animate.css library, which, to make it work with ngAnimate, you would have to hardcode the combination of CSS classes into the ngAnimate attribute for each event that takes place. This would be much easier with a driver or module that you load into your application and boom things are working. Well animate.css is the best choice for quick and dirty animations in CSS and there is a new Github repository that is being developed which includes support for it.

Click here to view the ngAnimate - Animate.css Plugin

Greensock.js Integration

Integrating greensock.js into your animation code is as simple as calling TweenMax or TweenLite within your JavaScript animation code (just like we did with jQuery). Since very little has changed (aside from the JavaScript animation API) the approach is similar to what it was in AngularJS 1.1.5. Therefore the link below shows you how to use greensock with ngAnimate in your AngularJS application.

Click here to view the previous Article on Greensock.js and ngAnimate

Effeckt.css

Effeckt.css is a top of the line CSS/JS animation library which provides "hooks" such that other frameworks can run animations. This is perfect for AngularJS! The only issue is that effect.css is still in development and its CSS naming convention doesn't fly with AngularJS. But all that you have to do to make this work is simple add in the correct CSS styles and you're good to go. Therefore, if anyone is interested in helping out for now, please take a look at Effeckt.css to see how you can get started.

Click here to visit the Effectk.css demo page

What's Next?

This article is for AngularJS 1.2 and has been around for the release candidate versions. In case you looked over the article back when 1.2rc1 was released, please look over the article again to see the latest and greatest features of the final 1.2 ngAnimate release.

The ngAnimate module is pretty feature-rich, but there is always room for improvement. Next features may include a nice new API for syncing animations together, JS-level staggering (CSS is already supported), animation scope events, and so on.

Thank you for trying out the animations and please let me know if there is anything I missed.