Animation in AngularJS

Learn how to make use of the new animation hooks in AngularJS

Published on Apr 2, 2013

AngularJS is an outstanding, all-inclusive and extensive framework that is phenomenal for crafting together full-blown JavaScript MVC apps with small amounts of code. But how do you stick in animations into your application? You could simply use CSS transitions combined with CSS classes, but that doesn't hook into the guts of your app. Or you could somehow tie in callbacks into your directives, but that's a maintenance nightmare and it slows down your app too much (plus it's hard to test).

Up until now, it was safe to say that native animations were not present in AngularJS. Well animation is here and yearofmoo is well prepared to hook you up. So lets take a look at how exactly make use of this great new feature with the world's best JavaScript MVC framework.

1.0 AngularJS 1.2.0 and Higher

If you're using a newer version of AngularJS (1.2.0), please refer to this article which explains how to perform animations using the new ngAnimate API.

to top

2.0 AngularJS Now Directly Supports Animations

Yup. Exactly that. AngularJS now has animations it's core. Built right in and easy to play with. Over the winter 2013, the AngularJS team was very trusting to offer the challenge to build animations directly into AngularJS. I was originally set on making animations myself and putting them into plugins or a forked version of AngularJS, but the end result was too slow and difficult to work with. Now animations are apart of the core of AngularJS.

Working with the AngularJS team was a blast. Thank you Misko, Naomi, Brad, Igor as well as the rest of the AngularJS developer crew. As for the rest of the AngularJS fan club, I hope you enjoy the new animation features. Your websites will shine.

2.1 Upgrade your AngularJS script file...

To get a hold of animations in AngularJS, be sure to upgrade your AngularJS build to version 1.1.4--version 1.1.5 is now out and this is preferred over 1.1.4 due to some syntactical upgrades. This article has been updated to work with the changes in 1.1.5. Please use 1.1.5 or higher instead of using 1.1.4.

CDN Link provides the most recent build of AngularJS (it may be an unstable branch, but don't worry), so you can get it from there no problem. Keep in mind that the docs are not updated with this feature yet, so use the docs link provided in this article. You can also clone the angularjs github repo and run the command grunt package which will then build you a complete angular.js file under the build directory.

And..... Now that you've got the code, lets get crackin'

to top

3.0 Downloadable Code + Demo Repo

This wouldn't be a rockstar yearofmoo article unless a demo application and downloadable github repo were provided. To best learn and witness the awesomeness of AngularJS animations, please visit the demo website and be sure to download the demo repository so that you can play with the animations on your local machine.

to top

4.0 How to use Animations in AngularJS

There are two ways three ways to perform animations in AngularJS: Using CSS3 Transitions, CSS3 Animations and using JavaScript (these are referred to as CSS3-enabled and JavaScript-enabled animations). CSS3 transitions are much easier to use since they don't require any JavaScript code and JavaScript-enabled animations require you to define an animation within your module (much like you would define a filter or a directive).

4.1 Animations are assigned in the HTML

The ngAnimate attribute does the work between mapping animations to core DOM effects. This involves entering (injecting) content into the page, leaving (removing) content and moving content around. The show and hide effects can also be animated as well. You can assign animations into your HTML code by using the ngAnimate attribute:

<!-- set SPECIFIC animations to specific events -->
<div data-some-directive data-ng-animate="{enter: 'some-animation'}"></div>
<!-- set the same GENERAL animation type for all events -->
<div data-some-directive data-ng-animate="'some-animation'"></div>
<!-- use a $scope member -->
<div data-some-directive data-ng-animate="myAnimation"></div>
<!-- use a $scope function -->
<div data-some-directive data-ng-animate="myAnimationFn()"></div>

The enter label in this case refers to an animation event and the some-animation value refers to your own defined animation (whether it's a CSS3 or JavaScript animation is up to you). You can also use the other AngularJS directive HTML flavors (xHTML, XHTML, CSS Classes and non data attributes), but using ngAnimate as an element won't work and neither will the ngAnimate attribute by itself (you need use it with a working directive).

Also, if you're using a scope member/function as the ngAnimate attribute value, then be sure to return an object for specific animations and a string for general animations (much like the examples above).

4.2 Which ng directives support animations?

AngularJS animations are now apart of several common ng directives. The directives that are supported are ngRepeat, ngRepeat, ngRepeat, ngRepeat, ngRepeat, ngRepeat, You can also make your own directives and use the $animator service within there (this is explained later on in the article).

ngIf has also been added to AngularJS 1.1.5, however this directive is not covered in this article. The functionality of ngIf however is identical to ngSwitch only works with one piece content per directive.

to top

5.0 CSS3, jQuery, MooTools & Other Animation Frameworks

You can use CSS3 Transitions, any JavaScript animation library, CSS framework, or glob of DOM code you want to perform your animations. The only thing is that, if you're using something other than JQuery, you will need to get around the simple JQLite/JQuery wrapper defaults when defining animations in AngularJS, but other then that you're free to do as you wish. Lets look at some examples of animations defined in AngularJS.

5.1 Using CSS Animations (Keyframe Animations) for animations

CSS Animations are also present in AngularJS version 1.1.5. Since this article was originally set for version 1.1.4 (where CSS3 keyframe animations were not supported yet), there is no material that covers them in this article. While their usage is almost identical to using CSS3 Transitions, they are not explained in detail here. This wonderful feature is explained in detail in a followup article to this blog article known as Enhanced Animation in AngularJS.

5.2 Using CSS Transitions for animations

91.2512% of the time you'll be using CSS3 transitions to perform you animations in AngularJS. Since it's so easy to do and most modern browsers support them (IE <= 9 doesn't support them, but MS does auto-update now!).

CSS transitions are better optimized for animations than JavaScript animations, however, they do require some additional CSS code (lots of vendor prefixes) to get full support for all the animation effects (such as custom easing and timing). AngularJS comes provided with CSS3 animations support right out of the box so all you need to do is create the CSS classes and assign them into the ngAnimate attribute (no JavaScript required).

/* 
the setup class (the class without the active suffix) 
is is where you define the transition code and where
you define any initialization styles for your animation.
the active class (the class with the -active suffix)
is where the animation occurs. So setup any of the
styles that you wish to animate. Also note that you must include both
classes together so that the animation styles are not conflicting with
the setup styles (this avoids any CSS-specificity issues).
*/
.mycss-animation {
  transition:1s linear all; /* future proof */
  background:red;
}
.mycss-animation.mycss-animation-active {
  background:blue;
}
<!-- replace ng-directive with whatever directive you want as well as the event -->
<div ng-directive ng-animate="{event: 'mycss-animation'}"></div>
/*
  * Thank you to VisionMedia Move.js (https://github.com/visionmedia/move.js/)
    for the easing code!
*/
.animation-with-easing {
  /* linear animation */
  transition-timing-function: linear;
  /* in easing animation */
  transition-timing-function: ease-in;
  /* in-out easing animation */
  transition-timing-function: ease-in-out;
  /* ease-in-quad easing animation */
  transition-timing-function: cubic-bezier(0.550, 0.085, 0.680, 0.530);
  /* ease-in-transition-timing-function: cubic easing animation */
  transition-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
  /* ease-in-out-quart easing animation */
  transition-timing-function: cubic-bezier(0.770, 0.000, 0.175, 1.000);
  /* ease-in-out-back easing animation */
  transition-timing-function: cubic-bezier(0.680, -0.550, 0.265, 1.550);
  /* view the Github link for more CSS easing animations */
}

If you plan on using these then you can do so directly inside of the general transition property by placing it as the animation type (linear is the default). Or you can use it within the transition-timing-function ... Just don't forget to include vendor prefixes for all the other browsers.

As you can see, with CSS animations, there is absolutely no JavaScript code required at all. AngularJS does all the work to figure out the duration behind the scenes (just don't forget your vendor prefixes).

5.3 Using JavaScript for animations (JQuery)

You will more than likely use JQuery for your animation purposes on your AngularJS application when CSS animations just don't cut it. Or you will use JavaScript animations when you plan on supporting older browsers which do not support CSS3 transitions. And since there is a bit of a JQuery bias with AngularJS (thanks to JQLite) you can easily just start the animations directly on the element variable within your animation definition. Here's an example:

//you can inject stuff!
myModule.animation('cool-animation', ['$rootScope', function($rootScope) {
  return { 
    setup : function(element) {
      //this is called before the animation
      jQuery(element).css({
        'border-width':0
      }); 
    },
    start : function(element, done, memo) {
      //this is where the animation is expected to be run
      jQuery(element).animate({
        'border-width':20
      }, function() {
        //call done to close when the animation is complete
        done(); 
      });
    },
    cancel : function(element, done) {
      //this is called when another animation is started
      //whilst the previous animation is still chugging away
    }   
  };
}]);

5.4 Using JavaScript for animations (MooTools)

MooTools is not the same as JQuery and the element wrappers are different. So you need to break out of the AngularJS element wrapper to perform animations on any MooTools elements. This is very easy, just extract the actual DOM element from the JQLite wrapper. Then you can use the element.morph() and element.tween() methods to provide quick animations or you can go all out by instantiating the Fx Class. Here's a quick example of how to make a MooTools animation with AngularJS:

myModule.animation('cool-animation', function() {
  return {
    setup : function(element) {},
    start : function(element, done) {
      //break out of jqlite/jquery
      element = document.id(element[0]);
      new Fx.Morph(element).start({
        'background-color':['#FFFFFF','#FFCC00']
      }).chain(done);
    }
  };
});

MooTools is great for animations. The Fx Class provides a sleek API for managing the ins and outs of all animation events. Give it a try if you haven't already.

5.5 Using another animation library

You can use whatever you want to animate your DOM in your animation definitions. The only thing to keep in mind is that if you're using a JQuery-based animation library/framework, then you don't need to extract the DOM element from the jquery wrapper. However, if you're using something else then you will need to extract the DOM element from the wrapper (this is explained in the previous part which talks about MooTools).

to top

6.0 Animating ngRepeat

The ngRepeat directive is king. The most important directive in AngularJS (aside from the absolutely essential directives such as ngView and ngApp). To animate ngRepeat, you need to provide the ngAnimate attribute with animation values for enter, leave and move animation events on the element which contains the ng-repeat directive attribute.

The cool thing about this is that you don't have to anything extra in your controller code, scope or HTML templates (aside from adding ng-animate) to get this to work. All that is required is to define the animations and place them into the ng-animate directive HTML where the ng-repeat directive is defined. AngularJS takes care of all the rest.

Please do remember that with AngularJS 1.1.4+, you cannot include any duplicate values within your repeated list. This means that if you have an array like [1,2,2,3] then AngularJS will start to whine. This constriction is here to ensure that ngRepeat keeps track of the element positions to appropriately handle enter, move and leave animations while properly remembering their positions in the list. To get around this, simply make each item an object (if it isn't already) and include some unique value for that object as a object member (such as a date value or something). Typically this would be an ID value if you fetched your repeat contents from a database.

Here's some example HTML code to point out a nicely animated ng-repeat element:

<div data-ng-repeat="item in items" data-ng-animate="'custom'">
  {{item}}
</div>
.custom-enter,
.custom-leave,
.custom-move {
  transition: 1s linear all;
  position:relative;
}
.custom-enter {
  left:-10px;
  opacity:0;
}
.custom-enter.custom-enter-active {
  left:0;
  opacity:1;
}
.custom-leave {
  left:0;
  opacity:1;
}
.custom-leave.custom-leave-active {
  left:-10px;
  opacity:0;
}
.custom-move {
  opacity:0.5;
}
.custom-move.custom-move-active {
  opacity:1;
}
to top

7.0 Animating ngShow & ngHide

The ngShow and ngHide directives are really useful and very common in an AngularJS application. They are effectively the lightweight if/else branching mechanism for showing and hiding DOM elements on screen. And showing and hiding elements are also a hot spot for animations since it's a very effective way of ** catching the user's attention to reveal that to them that something new has come into view. Think about a form for example where user's register. Without AngularJS, you would have to make your own event listeners to latch themselves onto your input fields and validate data to see if the hidden content should be displayed. With AngularJS all you need to do is setup a ngShow or ngHide directive and evaluate it to true or false to show or hide the content. And now you can do this with a sleek animation in between.

Also, one thing to keep in mind, since AngularJS evaluates ngShow and ngHide on bootstrap (when the page is loaded), you need to, in your animation definitions/CSS code, try to avoid animating a show event if the element is already displayed. Version 1.1.5 of AngularJS now properly avoids animating elements that are instantiated before bootstrap. So basically the first animation is skipped thus preventing any flickr or premature hiding.

<div data-ng-hide="hidden == true" data-ng-animate="'fade'">
  ...
</div>
<div data-ng-show="hidden == false" data-ng-animate="'fade'">
  ...
</div>
.fade-hide, .fade-show {
  transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
}
.fade-hide {
  opacity:1;
}
.fade-hide.fade-hide-active {
  opacity:0;
}
.fade-show {
  opacity:0;
}
.fade-show.fade-show-active {
  opacity:1;
}

You can also use the ngShow and ngHide animations on the same element as ngRepeat, ngInclude and ngSwitch (and yes you can also use it for ngView, but I don't see many reasons why ... maybe a loading effect or something).

The ngInclude and ngSwitch directives are very powerful and show up in AngularJS applications from time to time (just thinking about the immense amount of DOM code I would have to write with standard JavaScript to emulate ngInclude makes my brain start to fry). The ngInclude directive works with templates (either with inline templates or downloadable templates) and ngSwitch works with content directly situated inside the element tags itself. So it's best to think of ngSwitch as a more efficient, but less capable version of ngInclude without anything dynamic to offer.

Both directives animate in the same mechanism. They both use the events enter (for when the content comes into view) and leave (for when the previously existing content leaves out of view).

The enter and leave events will always fire at the same time, but the leave event will only be called if there already is content on display (so when the first include is loaded then the leave animation will be skipped and if the content is null). One very important thing to keep in mind is that when defining your ngInclude templates, be 100% sure to wrap all your content into a wrapping HTML tag (like a div tag or so). This way you can contain all your to-be-animated content inside of single parent element which you can do some funky animations with.

<div data-ng-switch="wave" data-ng-animate="'wave'">
<div ng-switch-when="one">...</div>
  <div ng-switch-when="two">...</div>
  <div ng-switch-when="three">...</div>
</div>
.wave-enter, .wave-leave {
  transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
}
.wave-enter {
  position:absolute;
  left:100%;
}
.wave-enter-active {
  left:0;
}
.wave-leave {
  position:absolute;
  left:0;
}
.wave-leave-active {
  left:-100%;
}
to top

8.0 Animating ngView

The ngView directive is one that appears on every AngularJS application and can really make a quick impression of animations on your website. Think about how easy it is to setup; All you do is put a ng-animate attribute on your ngView directive HTML and then assign and define the enter and leave animations. And the nice thing is that the ngView directive works exactly the same way as does ngInclude (so be sure to read that to get a solid idea of how it works). Also be sure to wrap your template HTML together under one single HTML tag ... This way you can have solid access to fully animate the entire view as it is put into the page.

<div data-ng-view
     data-ng-animate="{enter: 'view-enter', leave: 'view-leave'}"></div>
.view-enter, .view-leave {
  transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
}
.view-enter {
  opacity:0;
  left:100px;
  width:100%;
  position:absolute;
}
.view-enter.view-enter-active {
  left:0;
  opacity:1;
}
.view-leave {
  position:absolute;
  left:0;
  width:100%;
  opacity:1;
}
.view-leave.view-leave-active {
  left:-100px;
  opacity:0;
}
to top

9.0 Using animations in your own directives

Using animations inside of your own directives can be achieved with use of the $animator service. This service binds the functionality of the ngAnimate attribute (and its assigned animations) into the flow of your own directive. Just be sure to call any one of the currently existing animation methods (enter, leave, move, show or hide) to kickstart the animation.

Below is an example of how to include animations for a custom directive. The only difference here is that we're making use of the animation triggers directly in our own directive.

myModule.directive('myCustomDirective',
  ['$animator', function($animator) {
    return {
      link : function($scope, element, attrs) {
        //the attrs object is where the ngAnimate attribute is defined
        var animator = $animator($scope, attrs);
        //injects the element into the DOM then animates
        animator.enter(element, parent); 
        //animates then removes the element from the DOM
        animator.leave(element); 
        //moves it around in the DOM then animates
        animator.move(element, parent, sibling);  
        //sets CSS display=block then animates
        animator.show(element);  
        //animates then sets CSS display=none
        animator.hide(element);  
        //animates a custom animation referenced in the ngAnimate attr
        //by the event name (so ngAnimate="{custom:'animation'}")
        animator.animate('custom', element);
      }
    };
}]);
<div data-my-custom-directive
     data-ng-animate="{enter: 'enter-animation',
                       leave: 'leave-animation',
                       move: 'move-animation',
                       show: 'show-animation',
                       hide: 'hide-animation',
                       custom: 'custom-animation'}"></div>
.enter-animation, .leave-animation,
.move-animation, .show-animation,
.hide-animation {
  transition:all linear 1s;
}
.enter-animation { }
.enter-animation.enter-animation-active { }
.leave-animation { }
.leave-animation.leave-animation-active { }
.move-animation { }
.move-animation.move-animation-active { }
.show-animation { }
.show-animation.show-animation-active { }
.hide-animation { }
.hide-animation.hide-animation-active { }
.custom-animation { }
.custom-animation.custom-animation-active { }
myModule.animation('enter-animation', function() {
  return {
    setup : function(element) { ... },
    start : function(element, done) { ... },
    cancel : function(element, done) { ... }
  };
});
myModule.animation('leave-animation', function() {
  return {
    setup : function(element) { ... },
    start : function(element, done) { ... },
    cancel : function(element, done) { ... }
  };
});
myModule.animation('move-animation', function() {
  return {
    setup : function(element) { ... },
    start : function(element, done) { ... },
    cancel : function(element, done) { ... }
  };
});
myModule.animation('show-animation', function() {
  return {
    setup : function(element) { ... },
    start : function(element, done) { ... },
    cancel : function(element, done) { ... }
  };
});
myModule.animation('hide-animation', function() {
  return {
    setup : function(element) { ... },
    start : function(element, done) { ... },
    cancel : function(element, done) { ... }
  };
});
myModule.animation('custom-animation', function() {
  return {
    setup : function(element) { ... },
    start : function(element, done) { ... },
    cancel : function(element, done) { ... }
  };
});

Now you're all set for making crazy custom animations in your crazy custom directives...

to top

10.0 What about Optional Animations and/or Mobile?

There are various ways to conditionally enable/disable animations in your AngularJS code and, depending on your webpage setup, one or more of these approaches should prove useful...

If you use a scope variable in your ngAnimate attribute, then you can easily just change the value of that variable to null to disable animations on that element or to something else to change the animation entirely.

If you're not using a variable in your ngAnimate attribute then what you can do is play around with your CSS code and CSS classes to temporarily disable animations. This approach works nicely since AngularJS examines the CSS classes and CSS styles each time it performs an animation and this means that if you remove the transition/animation styles on the element being animated then you effectively skip the animations on it (you can also hotswap the animation CSS code if you really have the guts to do so). Keep in mind that this trick is meant to skip CSS animations. So if you wish to skip JavaScript animations then you can do that directly in the JavaScript animation definition by calling the done callback right away or by using a scope member which is set to null on the ngAnimate attribute.

10.1 Skip Specific Animations by changing the $scope

In the following example you can disable animations directly by changing a particular scope member which has been attached onto a ngAnimate attribute:

//turn on the animation
$scope.myAnimation = { enter: 'cool-enter', leave: 'cool-leave' };
//change the animation
$scope.myAnimation = { enter: 'uncool-enter', leave: 'uncool-leave' };
//remove the animation
$scope.myAnimation = null;

And now, inside your template, just assign that scope member to the ngAnimate attribute on the directive that you wish to animate:

<div data-some-directive data-ng-animate="myAnimation"></div>

And yes, ngAnimate does watch its value. So once the value gets changed then AngularJS will pick up on it as soon as scope the digest kicks in next animation is performed.

10.2 Skip CSS Animations using a Body Class

You can attach a ngClass expression onto the body element in your AngularJS application which can then operate as a parent class to toggle your CSS transition definitions.

<html data-ng-app="MyModule" data-ng-init="animationsEnabled=true">
  ...
  <body data-ng-class="{animations: animationsEnabled}">
    ...
    <div data-ng-include="include_tpl" data-ng-animate="{enter: 'fade-enter'}"></div>

Then inside of your CSS code, prefix your CSS classes with that .animations CSS class.

body.animations .fade-enter {
  transition: 1s linear all;
}

So if you want to toggle CSS animations on and off, all you have to do is toggle the $rootScope.animationsEnabled to true or false. Inside your HTML template code, you will still assign the class (fade-enter in this case) inside of the ngAnimate attribute and continue normally.

But it will only animate if the animations CSS class is located on the body element (as defined in the CSS). If nothing is found then the animation will animate for 0 seconds (so nothing animates).

10.3 Skip CSS Animations using CSS Media Queries

Using Media Queries to toggle animations it the best approach to manage mobile animations. Seeing as CSS Media Queries are becoming more and more advanced you can easily determine which mobile browsers are better suited to perform animations (even target specific animations). Or, in general, you can determine animations for mobile devices just by using the screen size. Either way, it does the trick. This approach may require more CSS, but it doesn't require any JavaScript or scope parameters.

Below is an example of filtering out animations for mobile devices based on screen size.

.cool-animation {
  transition: 1s linear all;
  opacity:0;
}
.cool-animation.cool-animation-active {
  opacity:1;
}
@media only screen and (max-width : 640px) {
  .cool-animation {
    transition: none;
  }
}

All you need now is to setup your HTML. But this isn't anything special. Just setup your HTML code with ngAnimate just as it would normally.

10.4 Skip JavaScript animations

Any JavaScript-enabled animations (the animations that you define using myModule.animation()) can be easily circumvented to enable/disable animations since the definition itself is inside your module. All that is required, is to setup a scope parameter (or a service/config variable) that is used to flag if animations are enabled or disabled and then access that property inside of the animation definition. If the flag is a falsey value (meaning animations are disabled) then you simply call the provided done() function to skip the animation entirely.

myModule.animation('cool-animation', ['$rootScope', function() {
  return {
    setup : function() {
      if($rootScope.animationsEnabled) {
        element.css({
          opacity : 1
        });
      }
    },
    start : function(element, done, memo) {
      if($rootScope.animationsEnabled) {
        element.animate({
          opacity : 1
        }, done);
      }
      else {
        //you still need to call this...
        done();
      }
    }
  };
}]);

The HTML is the same as before. Just remember that you can enable and disable the animations by setting the $rootScope.animationsEnabled param to true or false.

to top

11.0 Upgrade your Apps!

Merging animations into an existing AngularJS is so simple now that you can easily provide top of the line animations in your already-built AngularJS animations in just a few minutes. Please email me or post in the comments your newly enriched, tricked-out, animation-powered AngularJS applications once they're ready :)

to top

12.0 More to Come...

The animation support in AngularJS is outstanding! Just about all the core features in AngularJS now have hooks for animations which make building applications better than ever. Now the next step is to sit back and check out all the crazy animation-related AngularJS tricks that will soon show up on the internet. Stay tuned and visit to see for new articles on yearofmoo because there will undoubtedly be some more articles on animations.

Also, if you feel that something is missing regarding animations in AngularJS then let me know since yearofmoo had an active role in the development of AngularJS animations. Send me an email.

to top