From 81923f1e41560327f7de6e8fddfda0d2612658f3 Mon Sep 17 00:00:00 2001 From: Matias Niemelä Date: Tue, 18 Jun 2013 13:59:57 -0400 Subject: feat(ngAnimate): complete rewrite of animations - ngAnimate directive is gone and was replaced with class based animations/transitions - support for triggering animations on css class additions and removals - done callback was added to all animation apis - $animation and $animator where merged into a single $animate service with api: - $animate.enter(element, parent, after, done); - $animate.leave(element, done); - $animate.move(element, parent, after, done); - $animate.addClass(element, className, done); - $animate.removeClass(element, className, done); BREAKING CHANGE: too many things changed, we'll write up a separate doc with migration instructions --- Gruntfile.js | 5 + angularFiles.js | 5 +- css/angular.css | 4 + docs/component-spec/annotationsSpec.js | 39 +- .../angular-bootstrap/bootstrap-prettify.js | 5 +- docs/components/angular-bootstrap/bootstrap.js | 9 +- docs/src/example.js | 2 +- docs/src/ngdoc.js | 67 +- docs/src/templates/css/animations.css | 43 +- docs/src/templates/index.html | 13 +- docs/src/templates/js/docs.js | 2 +- karma-docs.conf.js | 1 + src/Angular.js | 6 +- src/AngularPublic.js | 3 +- src/loader.js | 28 +- src/ng/animate.js | 112 ++ src/ng/animation.js | 61 - src/ng/animator.js | 446 ------ src/ng/directive/ngClass.js | 118 +- src/ng/directive/ngIf.js | 27 +- src/ng/directive/ngInclude.js | 14 +- src/ng/directive/ngRepeat.js | 34 +- src/ng/directive/ngShowHide.js | 64 +- src/ng/directive/ngSwitch.js | 26 +- src/ngAnimate/animate.js | 714 +++++++++ src/ngMock/angular-mocks.js | 37 + src/ngRoute/directive/ngView.js | 22 +- test/matchers.js | 12 + test/ng/animateSpec.js | 53 + test/ng/animationSpec.js | 15 - test/ng/animatorSpec.js | 773 ---------- test/ng/compileSpec.js | 12 +- test/ng/directive/ngIfSpec.js | 86 +- test/ng/directive/ngIncludeSpec.js | 98 +- test/ng/directive/ngRepeatSpec.js | 216 +-- test/ng/directive/ngShowHideSpec.js | 159 +- test/ng/directive/ngSwitchSpec.js | 112 +- test/ngAnimate/animateSpec.js | 1524 ++++++++++++++++++++ test/ngRoute/directive/ngViewSpec.js | 183 ++- test/testabilityPatch.js | 2 +- 40 files changed, 2961 insertions(+), 2191 deletions(-) create mode 100644 src/ng/animate.js delete mode 100644 src/ng/animation.js delete mode 100644 src/ng/animator.js create mode 100644 src/ngAnimate/animate.js create mode 100644 test/ng/animateSpec.js delete mode 100644 test/ng/animationSpec.js delete mode 100644 test/ng/animatorSpec.js create mode 100644 test/ngAnimate/animateSpec.js diff --git a/Gruntfile.js b/Gruntfile.js index 264fe874..6d0395fb 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -160,6 +160,10 @@ module.exports = function(grunt) { dest: 'build/angular-resource.js', src: util.wrap(['src/ngResource/resource.js'], 'module') }, + animate: { + dest: 'build/angular-animate.js', + src: util.wrap(['src/ngAnimate/animate.js'], 'module') + }, route: { dest: 'build/angular-route.js', src: util.wrap([ @@ -178,6 +182,7 @@ module.exports = function(grunt) { min: { angular: 'build/angular.js', + animate: 'build/angular-animate.js', cookies: 'build/angular-cookies.js', loader: 'build/angular-loader.js', mobile: 'build/angular-mobile.js', diff --git a/angularFiles.js b/angularFiles.js index dc98bcc1..c06f8adc 100755 --- a/angularFiles.js +++ b/angularFiles.js @@ -10,8 +10,7 @@ angularFiles = { 'src/auto/injector.js', 'src/ng/anchorScroll.js', - 'src/ng/animation.js', - 'src/ng/animator.js', + 'src/ng/animate.js', 'src/ng/browser.js', 'src/ng/cacheFactory.js', 'src/ng/compile.js', @@ -66,6 +65,7 @@ angularFiles = { ], 'angularSrcModules': [ + 'src/ngAnimate/animate.js', 'src/ngCookies/cookies.js', 'src/ngResource/resource.js', 'src/ngRoute/routeUtils.js', @@ -107,6 +107,7 @@ angularFiles = { 'test/*.js', 'test/auto/*.js', 'test/ng/**/*.js', + 'test/ngAnimate/*.js', 'test/ngCookies/*.js', 'test/ngResource/*.js', 'test/ngRoute/**/*.js', diff --git a/css/angular.css b/css/angular.css index 0cd9d7dc..6833234f 100644 --- a/css/angular.css +++ b/css/angular.css @@ -8,3 +8,7 @@ ng\:form { display: block; } + +.ng-hide { + display: none; +} diff --git a/docs/component-spec/annotationsSpec.js b/docs/component-spec/annotationsSpec.js index 321118ad..a17c906c 100644 --- a/docs/component-spec/annotationsSpec.js +++ b/docs/component-spec/annotationsSpec.js @@ -67,41 +67,33 @@ describe('Docs Annotations', function() { var $scope, parent, element, url, window; beforeEach(function() { - module(function($provide, $animationProvider) { + module(function($provide, $animateProvider) { $provide.value('$window', window = angular.mock.createMockWindow()); - $animationProvider.register('foldout-enter', function($window) { + $animateProvider.register('.foldout', function($window) { return { - start : function(element, done) { + enter : function(element, done) { $window.setTimeout(done, 1000); - } - } - }); - $animationProvider.register('foldout-hide', function($window) { - return { - start : function(element, done) { + }, + show : function(element, done) { $window.setTimeout(done, 500); - } - } - }); - $animationProvider.register('foldout-show', function($window) { - return { - start : function(element, done) { + }, + hide : function(element, done) { $window.setTimeout(done, 200); } } }); }); - inject(function($rootScope, $compile, $templateCache, $rootElement, $animator) { - $animator.enabled(true); + inject(function($rootScope, $compile, $templateCache, $rootElement, $animate) { + $animate.enabled(true); url = '/page.html'; $scope = $rootScope.$new(); parent = angular.element('
'); - element = angular.element(''); //we're injecting the element to the $rootElement since the changes in - //$animator only detect and perform animations if the root element has + //$animate only detect and perform animations if the root element has //animations enabled. If the element is not apart of the DOM //then animations are skipped. + element = angular.element(''); parent.append(element); $rootElement.append(parent); body.append($rootElement); @@ -142,16 +134,19 @@ describe('Docs Annotations', function() { $httpBackend.flush(); window.setTimeout.expect(1).process(); window.setTimeout.expect(1000).process(); + window.setTimeout.expect(0).process(); //hide element.triggerHandler('click'); window.setTimeout.expect(1).process(); - window.setTimeout.expect(500).process(); + window.setTimeout.expect(200).process(); + window.setTimeout.expect(0).process(); //show element.triggerHandler('click'); window.setTimeout.expect(1).process(); - window.setTimeout.expect(200).process(); + window.setTimeout.expect(500).process(); + window.setTimeout.expect(0).process(); })); }); @@ -160,7 +155,7 @@ describe('Docs Annotations', function() { var window, $scope, ctrl; beforeEach(function() { - module(function($provide, $animationProvider) { + module(function($provide, $animateProvider) { $provide.value('$window', window = angular.mock.createMockWindow()); }); inject(function($rootScope, $controller, $location, $cookies, sections) { diff --git a/docs/components/angular-bootstrap/bootstrap-prettify.js b/docs/components/angular-bootstrap/bootstrap-prettify.js index ad5340e5..fa40c6e7 100644 --- a/docs/components/angular-bootstrap/bootstrap-prettify.js +++ b/docs/components/angular-bootstrap/bootstrap-prettify.js @@ -183,8 +183,8 @@ directive.ngEvalJavascript = ['getEmbeddedTemplate', function(getEmbeddedTemplat }]; -directive.ngEmbedApp = ['$templateCache', '$browser', '$rootScope', '$location', '$sniffer', - function($templateCache, $browser, docsRootScope, $location, $sniffer) { +directive.ngEmbedApp = ['$templateCache', '$browser', '$rootScope', '$location', '$sniffer', '$animate', + function($templateCache, $browser, docsRootScope, $location, $sniffer, $animate) { return { terminal: true, link: function(scope, element, attrs) { @@ -193,6 +193,7 @@ directive.ngEmbedApp = ['$templateCache', '$browser', '$rootScope', '$location', deregisterEmbedRootScope; modules.push(['$provide', function($provide) { + $provide.value('$animate', $animate); $provide.value('$templateCache', $templateCache); $provide.value('$anchorScroll', angular.noop); $provide.value('$browser', $browser); diff --git a/docs/components/angular-bootstrap/bootstrap.js b/docs/components/angular-bootstrap/bootstrap.js index 170e8805..20a5741d 100644 --- a/docs/components/angular-bootstrap/bootstrap.js +++ b/docs/components/angular-bootstrap/bootstrap.js @@ -335,12 +335,11 @@ directive.tabPane = function() { }; }; -directive.foldout = ['$http', '$animator','$window', function($http, $animator, $window) { +directive.foldout = ['$http', '$animate','$window', function($http, $animate, $window) { return { restrict: 'A', priority : 500, link: function(scope, element, attrs) { - var animator = $animator(scope, { ngAnimate: "'foldout'" }); var container, loading, url = attrs.url; if(/\/build\//.test($window.location.href)) { url = '/build/docs' + url; @@ -353,7 +352,7 @@ directive.foldout = ['$http', '$animator','$window', function($http, $animator, loading = true; var par = element.parent(); container = angular.element('
-           * module.animation('animation-name', function($inject1, $inject2) {
+           * module.animation('.animation-name', function($inject1, $inject2) {
            *   return {
-           *     //this gets called in preparation to setup an animation
-           *     setup : function(element) { ... },
-           *
-           *     //this gets called once the animation is run
-           *     start : function(element, done, memo) { ... }
+           *     eventName : function(element, done) {
+           *       //code to run the animation
+           *       //once complete, then run done()
+           *       return function cancellationFunction(element) {
+           *         //code to cancel the animation
+           *       }
+           *     }
            *   }
            * })
            * 
            *
-           * See {@link ng.$animationProvider#register $animationProvider.register()} and
-           * {@link ng.directive:ngAnimate ngAnimate} for more information.
+           * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and
+           * {@link ngAnimate ngAnimate module} for more information.
            */
-          animation: invokeLater('$animationProvider', 'register'),
+          animation: invokeLater('$animateProvider', 'register'),
 
           /**
            * @ngdoc method
diff --git a/src/ng/animate.js b/src/ng/animate.js
new file mode 100644
index 00000000..7e515594
--- /dev/null
+++ b/src/ng/animate.js
@@ -0,0 +1,112 @@
+'use strict';
+
+/**
+ * @ngdoc object
+ * @name ng.$animateProvider
+ *
+ * @description
+ * Default implementation of $animate that doesn't perform any animations, instead just synchronously performs DOM
+ * updates and calls done() callbacks.
+ *
+ * In order to enable animations the ngAnimate module has to be loaded.
+ *
+ * To see the functional implementation check out src/ngAnimate/animate.js
+ */
+var $AnimateProvider = ['$provide', function($provide) {
+
+  this.$$selectors = [];
+
+
+  /**
+   * @ngdoc function
+   * @name ng.$animateProvider#register
+   * @methodOf ng.$animateProvider
+   *
+   * @description
+   * Registers a new injectable animation factory function. The factory function produces the animation object which
+   * contains callback functions for each event that is expected to be animated.
+   *
+   *   * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` must be called once the
+   *   element animation is complete. If a function is returned then the animation service will use this function to
+   *   cancel the animation whenever a cancel event is triggered.
+   *
+   *
+   *
+   *   return {
+     *     eventFn : function(element, done) {
+     *       //code to run the animation
+     *       //once complete, then run done()
+     *       return function cancellationFunction() {
+     *         //code to cancel the animation
+     *       }
+     *     }
+     *   }
+   *
+   *
+   * @param {string} name The name of the animation.
+   * @param {function} factory The factory function that will be executed to return the animation object.
+   */
+  this.register = function(name, factory) {
+    var classes = name.substr(1).split('.');
+    name += '-animation';
+    this.$$selectors.push({
+      selectors : classes,
+      name : name
+    });
+    $provide.factory(name, factory);
+  };
+
+  this.$get = function() {
+    return {
+      enter : function(element, parent, after, done) {
+        var afterNode = after && after[after.length - 1];
+        var parentNode = parent && parent[0] || afterNode && afterNode.parentNode;
+        // IE does not like undefined so we have to pass null.
+        var afterNextSibling = (afterNode && afterNode.nextSibling) || null;
+        forEach(element, function(node) {
+          parentNode.insertBefore(node, afterNextSibling);
+        });
+        (done || noop)();
+      },
+
+      leave : function(element, done) {
+        element.remove();
+        (done || noop)();
+      },
+
+      move : function(element, parent, after, done) {
+        // Do not remove element before insert. Removing will cause data associated with the
+        // element to be dropped. Insert will implicitly do the remove.
+        this.enter(element, parent, after, done);
+      },
+
+      show : function(element, done) {
+        element.removeClass('ng-hide');
+        (done || noop)();
+      },
+
+      hide : function(element, done) {
+        element.addClass('ng-hide');
+        (done || noop)();
+      },
+
+      addClass : function(element, className, done) {
+        className = isString(className) ?
+                      className :
+                      isArray(className) ? className.join(' ') : '';
+        element.addClass(className);
+        (done || noop)();
+      },
+
+      removeClass : function(element, className, done) {
+        className = isString(className) ?
+                      className :
+                      isArray(className) ? className.join(' ') : '';
+        element.removeClass(className);
+        (done || noop)();
+      },
+
+      enabled : noop
+    };
+  };
+}];
diff --git a/src/ng/animation.js b/src/ng/animation.js
deleted file mode 100644
index faed84ca..00000000
--- a/src/ng/animation.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * @ngdoc object
- * @name ng.$animationProvider
- * @description
- *
- * The $AnimationProvider provider allows developers to register and access custom JavaScript animations directly inside
- * of a module.
- *
- */
-$AnimationProvider.$inject = ['$provide'];
-function $AnimationProvider($provide) {
-  var suffix = 'Animation';
-
-  /**
-   * @ngdoc function
-   * @name ng.$animation#register
-   * @methodOf ng.$animationProvider
-   *
-   * @description
-   * Registers a new injectable animation factory function. The factory function produces the animation object which
-   * has these two properties:
-   *
-   *   * `setup`: `function(Element):*` A function which receives the starting state of the element. The purpose
-   *   of this function is to get the element ready for animation. Optionally the function returns an memento which
-   *   is passed to the `start` function.
-   *   * `start`: `function(Element, doneFunction, *)` The element to animate, the `doneFunction` to be called on
-   *   element animation completion, and an optional memento from the `setup` function.
-   *
-   * @param {string} name The name of the animation.
-   * @param {function} factory The factory function that will be executed to return the animation object.
-   * 
-   */
-  this.register = function(name, factory) {
-    $provide.factory(camelCase(name) + suffix, factory);
-  };
-
-  this.$get = ['$injector', function($injector) {
-    /**
-     * @ngdoc function
-     * @name ng.$animation
-     * @function
-     *
-     * @description
-     * The $animation service is used to retrieve any defined animation functions. When executed, the $animation service
-     * will return a object that contains the setup and start functions that were defined for the animation.
-     *
-     * @param {String} name Name of the animation function to retrieve. Animation functions are registered and stored
-     *        inside of the AngularJS DI so a call to $animate('custom') is the same as injecting `customAnimation`
-     *        via dependency injection.
-     * @return {Object} the animation object which contains the `setup` and `start` functions that perform the animation.
-     */
-    return function $animation(name) {
-      if (name) {
-        var animationName = camelCase(name) + suffix;
-        if ($injector.has(animationName)) {
-          return $injector.get(animationName);
-        }
-      }
-    };
-  }];
-}
diff --git a/src/ng/animator.js b/src/ng/animator.js
deleted file mode 100644
index a9ea5743..00000000
--- a/src/ng/animator.js
+++ /dev/null
@@ -1,446 +0,0 @@
-'use strict';
-
-// NOTE: this is a pseudo directive.
-
-/**
- * @ngdoc directive
- * @name ng.directive:ngAnimate
- *
- * @description
- * The `ngAnimate` directive works as an attribute that is attached alongside pre-existing directives.
- * It effects how the directive will perform DOM manipulation. This allows for complex animations to take place
- * without burdening the directive which uses the animation with animation details. The built in directives
- * `ngRepeat`, `ngInclude`, `ngSwitch`, `ngShow`, `ngHide` and `ngView` already accept `ngAnimate` directive.
- * Custom directives can take advantage of animation through {@link ng.$animator $animator service}.
- *
- * Below is a more detailed breakdown of the supported callback events provided by pre-exisitng ng directives:
- *
- * | Directive                                                 | Supported Animations                               |
- * |---------------------------------------------------------- |----------------------------------------------------|
- * | {@link ng.directive:ngRepeat#animations ngRepeat}         | enter, leave and move                              |
- * | {@link ngRoute.directive:ngView#animations ngView}        | enter and leave                                    |
- * | {@link ng.directive:ngInclude#animations ngInclude}       | enter and leave                                    |
- * | {@link ng.directive:ngSwitch#animations ngSwitch}         | enter and leave                                    |
- * | {@link ng.directive:ngIf#animations ngIf}                 | enter and leave                                    |
- * | {@link ng.directive:ngShow#animations ngShow & ngHide}    | show and hide                                      |
- *
- * You can find out more information about animations upon visiting each directive page.
- *
- * Below is an example of a directive that makes use of the ngAnimate attribute:
- *
- * - * - *- * - * The `event1` and `event2` attributes refer to the animation events specific to the directive that has been assigned. - * - * Keep in mind that if an animation is running, no child element of such animation can also be animated. - * - *- * - * - * //!annotate="animation" ngAnimate|This *expands* to `{ enter: 'animation-enter', leave: 'animation-leave', ...}` - * - * - * - * //!annotate="computeCurrentAnimation\(\)" Scope Function|This will be called each time the scope changes... - * - * 
- * - * - * - *- * - * The following code below demonstrates how to perform animations using **CSS animations** with ngAnimate: - * - *
- * - * - * - *- * - * ngAnimate will first examine any CSS animation code and then fallback to using CSS transitions. - * - * Upon DOM mutation, the event class is added first, then the browser is allowed to reflow the content and then, - * the active class is added to trigger the animation. The ngAnimate directive will automatically extract the duration - * of the animation to determine when the animation ends. Once the animation is over then both CSS classes will be - * removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end - * immediately resulting in a DOM element that is at it's final state. This final state is when the DOM element - * has no CSS transition/animation classes surrounding it. - * - *
- * var ngModule = angular.module('YourApp', []);
- * ngModule.animation('animate-enter', function() {
- *   return {
- *     setup : function(element) {
- *       //prepare the element for animation
- *       element.css({ 'opacity': 0 });
- *       var memo = "..."; //this value is passed to the start function
- *       return memo;
- *     },
- *     start : function(element, done, memo) {
- *       //start the animation
- *       element.animate({
- *         'opacity' : 1
- *       }, function() {
- *         //call when the animation is complete
- *         done()
- *       });
- *     }
- *   }
- * });
- * 
- *
- * As you can see, the JavaScript code follows a similar template to the CSS3 animations. Once defined, the animation
- * can be used in the same way with the ngAnimate attribute. Keep in mind that, when using JavaScript-enabled
- * animations, ngAnimate will also add in the same CSS classes that CSS-enabled animations do (even if you're not using
- * CSS animations) to animated the element, but it will not attempt to find any CSS3 transition or animation duration/delay values.
- * It will instead close off the animation once the provided done function is executed. So it's important that you
- * make sure your animations remember to fire off the done function once the animations are complete.
- *
- * @param {expression} ngAnimate Used to configure the DOM manipulation animations.
- *
- */
-
-var $AnimatorProvider = function() {
-  var NG_ANIMATE_CONTROLLER = '$ngAnimateController';
-  var rootAnimateController = {running:true};
-
-  this.$get = ['$animation', '$window', '$sniffer', '$rootElement', '$rootScope',
-      function($animation, $window, $sniffer, $rootElement, $rootScope) {
-    $rootElement.data(NG_ANIMATE_CONTROLLER, rootAnimateController);
-
-    /**
-     * @ngdoc function
-     * @name ng.$animator
-     * @function
-     *
-     * @description
-     * The $animator.create service provides the DOM manipulation API which is decorated with animations.
-     *
-     * @param {Scope} scope the scope for the ng-animate.
-     * @param {Attributes} attr the attributes object which contains the ngAnimate key / value pair. (The attributes are
-     *        passed into the linking function of the directive using the `$animator`.)
-     * @return {object} the animator object which contains the enter, leave, move, show, hide and animate methods.
-     */
-     var AnimatorService = function(scope, attrs) {
-        var animator = {};
-
-        /**
-         * @ngdoc function
-         * @name ng.animator#enter
-         * @methodOf ng.$animator
-         * @function
-         *
-         * @description
-         * Injects the element object into the DOM (inside of the parent element) and then runs the enter animation.
-         *
-         * @param {jQuery/jqLite element} element the element that will be the focus of the enter animation
-         * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the enter animation
-         * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the enter animation
-        */
-        animator.enter = animateActionFactory('enter', insert, noop);
-
-        /**
-         * @ngdoc function
-         * @name ng.animator#leave
-         * @methodOf ng.$animator
-         * @function
-         *
-         * @description
-         * Runs the leave animation operation and, upon completion, removes the element from the DOM.
-         *
-         * @param {jQuery/jqLite element} element the element that will be the focus of the leave animation
-         * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the leave animation
-        */
-        animator.leave = animateActionFactory('leave', noop, remove);
-
-        /**
-         * @ngdoc function
-         * @name ng.animator#move
-         * @methodOf ng.$animator
-         * @function
-         *
-         * @description
-         * Fires the move DOM operation. Just before the animation starts, the animator will either append it into the parent container or
-         * add the element directly after the after element if present. Then the move animation will be run.
-         *
-         * @param {jQuery/jqLite element} element the element that will be the focus of the move animation
-         * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the move animation
-         * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the move animation
-        */
-        animator.move = animateActionFactory('move', move, noop);
-
-        /**
-         * @ngdoc function
-         * @name ng.animator#show
-         * @methodOf ng.$animator
-         * @function
-         *
-         * @description
-         * Reveals the element by setting the CSS property `display` to `block` and then starts the show animation directly after.
-         *
-         * @param {jQuery/jqLite element} element the element that will be rendered visible or hidden
-        */
-        animator.show = animateActionFactory('show', show, noop);
-
-        /**
-         * @ngdoc function
-         * @name ng.animator#hide
-         * @methodOf ng.$animator
-         *
-         * @description
-         * Starts the hide animation first and sets the CSS `display` property to `none` upon completion.
-         *
-         * @param {jQuery/jqLite element} element the element that will be rendered visible or hidden
-        */
-        animator.hide = animateActionFactory('hide', noop, hide);
-
-        /**
-         * @ngdoc function
-         * @name ng.animator#animate
-         * @methodOf ng.$animator
-         *
-         * @description
-         * Triggers a custom animation event to be executed on the given element
-         *
-         * @param {string} event the name of the custom event
-         * @param {jQuery/jqLite element} element the element that will be animated
-        */
-        animator.animate = function(event, element) {
-          animateActionFactory(event, noop, noop)(element);
-        }
-        return animator;
-
-        function animateActionFactory(type, beforeFn, afterFn) {
-          return function(element, parent, after) {
-            var ngAnimateValue = scope.$eval(attrs.ngAnimate);
-            var className = ngAnimateValue
-                ? isObject(ngAnimateValue) ? ngAnimateValue[type] : ngAnimateValue + '-' + type
-                : '';
-            var animationPolyfill = $animation(className);
-            var polyfillSetup = animationPolyfill && animationPolyfill.setup;
-            var polyfillStart = animationPolyfill && animationPolyfill.start;
-            var polyfillCancel = animationPolyfill && animationPolyfill.cancel;
-
-            if (!className) {
-              beforeFn(element, parent, after);
-              afterFn(element, parent, after);
-            } else {
-              var activeClassName = className + '-active';
-
-              if (!parent) {
-                parent = after ? after.parent() : element.parent();
-              }
-              var disabledAnimation = { running : true };
-              if ((!$sniffer.transitions && !polyfillSetup && !polyfillStart) ||
-                  (parent.inheritedData(NG_ANIMATE_CONTROLLER) || disabledAnimation).running) {
-                beforeFn(element, parent, after);
-                afterFn(element, parent, after);
-                return;
-              }
-
-              var animationData = element.data(NG_ANIMATE_CONTROLLER) || {};
-              if(animationData.running) {
-                (polyfillCancel || noop)(element);
-                animationData.done();
-              }
-
-              element.data(NG_ANIMATE_CONTROLLER, {running:true, done:done});
-              element.addClass(className);
-              beforeFn(element, parent, after);
-              if (element.length == 0) return done();
-
-              var memento = (polyfillSetup || noop)(element);
-
-              // $window.setTimeout(beginAnimation, 0); this was causing the element not to animate
-              // keep at 1 for animation dom rerender
-              $window.setTimeout(beginAnimation, 1);
-            }
-
-            function parseMaxTime(str) {
-              var total = 0, values = isString(str) ? str.split(/\s*,\s*/) : [];
-              forEach(values, function(value) {
-                total = Math.max(parseFloat(value) || 0, total);
-              });
-              return total;
-            }
-
-            function beginAnimation() {
-              element.addClass(activeClassName);
-              if (polyfillStart) {
-                polyfillStart(element, done, memento);
-              } else if (isFunction($window.getComputedStyle)) {
-                //one day all browsers will have these properties
-                var w3cAnimationProp = 'animation';
-                var w3cTransitionProp = 'transition';
-
-                //but some still use vendor-prefixed styles
-                var vendorAnimationProp = $sniffer.vendorPrefix + 'Animation';
-                var vendorTransitionProp = $sniffer.vendorPrefix + 'Transition';
-
-                var durationKey = 'Duration',
-                    delayKey = 'Delay',
-                    animationIterationCountKey = 'IterationCount',
-                    duration = 0;
-
-                //we want all the styles defined before and after
-                var ELEMENT_NODE = 1;
-                forEach(element, function(element) {
-                  if (element.nodeType == ELEMENT_NODE) {
-                    var elementStyles = $window.getComputedStyle(element) || {};
-
-                    var transitionDelay     = Math.max(parseMaxTime(elementStyles[w3cTransitionProp     + delayKey]),
-                                                       parseMaxTime(elementStyles[vendorTransitionProp  + delayKey]));
-
-                    var animationDelay      = Math.max(parseMaxTime(elementStyles[w3cAnimationProp      + delayKey]),
-                                                       parseMaxTime(elementStyles[vendorAnimationProp   + delayKey]));
-
-                    var transitionDuration  = Math.max(parseMaxTime(elementStyles[w3cTransitionProp     + durationKey]),
-                                                       parseMaxTime(elementStyles[vendorTransitionProp  + durationKey]));
-
-                    var animationDuration   = Math.max(parseMaxTime(elementStyles[w3cAnimationProp      + durationKey]),
-                                                       parseMaxTime(elementStyles[vendorAnimationProp   + durationKey]));
-
-                    if(animationDuration > 0) {
-                      animationDuration *= Math.max(parseInt(elementStyles[w3cAnimationProp    + animationIterationCountKey]) || 0,
-                                                   parseInt(elementStyles[vendorAnimationProp + animationIterationCountKey]) || 0,
-                                                   1);
-                    }
-
-                    duration = Math.max(animationDelay  + animationDuration,
-                                        transitionDelay + transitionDuration,
-                                        duration);
-                  }
-                });
-                $window.setTimeout(done, duration * 1000);
-              } else {
-                done();
-              }
-            }
-
-            function done() {
-              if(!done.run) {
-                done.run = true;
-                afterFn(element, parent, after);
-                element.removeClass(className);
-                element.removeClass(activeClassName);
-                element.removeData(NG_ANIMATE_CONTROLLER);
-              }
-            }
-          };
-        }
-
-        function show(element) {
-          element.css('display', '');
-        }
-
-        function hide(element) {
-          element.css('display', 'none');
-        }
-
-        function insert(element, parent, after) {
-          var afterNode = after && after[after.length - 1];
-          var parentNode = parent && parent[0] || afterNode && afterNode.parentNode;
-          var afterNextSibling = afterNode && afterNode.nextSibling;
-          forEach(element, function(node) {
-            if (afterNextSibling) {
-              parentNode.insertBefore(node, afterNextSibling);
-            } else {
-              parentNode.appendChild(node);
-            }
-          });
-        }
-
-        function remove(element) {
-          element.remove();
-        }
-
-        function move(element, parent, after) {
-          // Do not remove element before insert. Removing will cause data associated with the
-          // element to be dropped. Insert will implicitly do the remove.
-          insert(element, parent, after);
-        }
-      };
-
-    /**
-     * @ngdoc function
-     * @name ng.animator#enabled
-     * @methodOf ng.$animator
-     * @function
-     *
-     * @param {Boolean=} If provided then set the animation on or off.
-     * @return {Boolean} Current animation state.
-     *
-     * @description
-     * Globally enables/disables animations.
-     *
-    */
-    AnimatorService.enabled = function(value) {
-      if (arguments.length) {
-        rootAnimateController.running = !value;
-      }
-      return !rootAnimateController.running;
-    };
-
-    return AnimatorService;
-  }];
-};
diff --git a/src/ng/directive/ngClass.js b/src/ng/directive/ngClass.js
index 75e35a1e..a5b2acb6 100644
--- a/src/ng/directive/ngClass.js
+++ b/src/ng/directive/ngClass.js
@@ -2,59 +2,72 @@
 
 function classDirective(name, selector) {
   name = 'ngClass' + name;
-  return ngDirective(function(scope, element, attr) {
-    var oldVal = undefined;
-
-    scope.$watch(attr[name], ngClassWatchAction, true);
-
-    attr.$observe('class', function(value) {
-      var ngClass = scope.$eval(attr[name]);
-      ngClassWatchAction(ngClass, ngClass);
-    });
+  return ['$animate', function($animate) {
+    return {
+      restrict: 'AC',
+      link: function(scope, element, attr) {
+        var oldVal = undefined;
+
+        scope.$watch(attr[name], ngClassWatchAction, true);
+
+        attr.$observe('class', function(value) {
+          var ngClass = scope.$eval(attr[name]);
+          ngClassWatchAction(ngClass, ngClass);
+        });
+
+
+        if (name !== 'ngClass') {
+          scope.$watch('$index', function($index, old$index) {
+            var mod = $index & 1;
+            if (mod !== old$index & 1) {
+              if (mod === selector) {
+                addClass(scope.$eval(attr[name]));
+              } else {
+                removeClass(scope.$eval(attr[name]));
+              }
+            }
+          });
+        }
 
 
-    if (name !== 'ngClass') {
-      scope.$watch('$index', function($index, old$index) {
-        var mod = $index & 1;
-        if (mod !== old$index & 1) {
-          if (mod === selector) {
-            addClass(scope.$eval(attr[name]));
-          } else {
-            removeClass(scope.$eval(attr[name]));
+        function ngClassWatchAction(newVal) {
+          if (selector === true || scope.$index % 2 === selector) {
+            if (oldVal && !equals(newVal,oldVal)) {
+              removeClass(oldVal);
+            }
+            addClass(newVal);
           }
+          oldVal = copy(newVal);
         }
-      });
-    }
 
 
-    function ngClassWatchAction(newVal) {
-      if (selector === true || scope.$index % 2 === selector) {
-        if (oldVal && !equals(newVal,oldVal)) {
-          removeClass(oldVal);
+        function removeClass(classVal) {
+          $animate.removeClass(element, flattenClasses(classVal));
         }
-        addClass(newVal);
-      }
-      oldVal = copy(newVal);
-    }
 
 
-    function removeClass(classVal) {
-      if (isObject(classVal) && !isArray(classVal)) {
-        classVal = map(classVal, function(v, k) { if (v) return k });
-      }
-      element.removeClass(isArray(classVal) ? classVal.join(' ') : classVal);
-    }
+        function addClass(classVal) {
+          $animate.addClass(element, flattenClasses(classVal));
+        }
 
+        function flattenClasses(classVal) {
+          if(isArray(classVal)) {
+            return classVal.join(' ');
+          } else if (isObject(classVal)) {
+            var classes = [], i = 0;
+            forEach(classVal, function(v, k) {
+              if (v) {
+                classes.push(k);
+              }
+            });
+            return classes.join(' ');
+          }
 
-    function addClass(classVal) {
-      if (isObject(classVal) && !isArray(classVal)) {
-        classVal = map(classVal, function(v, k) { if (v) return k });
+          return classVal;
+        };
       }
-      if (classVal) {
-        element.addClass(isArray(classVal) ? classVal.join(' ') : classVal);
-      }
-    }
-  });
+    };
+  }];
 }
 
 /**
@@ -70,6 +83,10 @@ function classDirective(name, selector) {
  * When the expression changes, the previously added classes are removed and only then the
  * new classes are added.
  *
+ * @animations
+ * add - happens just before the class is applied to the element
+ * remove - happens just before the class is removed from the element
+ *
  * @element ANY
  * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
  *   of the evaluation can be a string representing space delimited class
@@ -78,7 +95,7 @@ function classDirective(name, selector) {
  *   element.
  *
  * @example
-   + * + * + * + *+ * + * Keep in mind that if an animation is running, any child elements cannot be animated until the parent element's + * animation has completed. + * + *+ * 
+ * + * + *+ * + * The following code below demonstrates how to perform animations using **CSS animations** with Angular: + * + *+ * + *+ *
+ * + * + *+ * + * Both CSS3 animations and transitions can be used together and the animate service will figure out the correct duration and delay timing. + * + * Upon DOM mutation, the event class is added first (something like `ng-enter`), then the browser prepares itself to add + * the active class (in this case `ng-enter-active`) which then triggers the animation. The animation module will automatically + * detect the CSS code to determine when the animation ends. Once the animation is over then both CSS classes will be + * removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end + * immediately resulting in a DOM element that is at its final state. This final state is when the DOM element + * has no CSS transition/animation classes applied to it. + * + *+ * + *+ *
+ * //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your application.
+ * var ngModule = angular.module('YourApp', []);
+ * ngModule.animation('.my-crazy-animation', function() {
+ *   return {
+ *     enter: function(element, done) {
+ *       //run the animation
+ *       //!annotate Cancel Animation|This function (if provided) will perform the cancellation of the animation when another is triggered
+ *       return function(element, done) {
+ *         //cancel the animation
+ *       }
+ *     }
+ *     leave: function(element, done) { },
+ *     move: function(element, done) { },
+ *     show: function(element, done) { },
+ *     hide: function(element, done) { },
+ *     addClass: function(element, className, done) { },
+ *     removeClass: function(element, className, done) { },
+ *   }
+ * });
+ * 
+ *
+ * JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run
+ * a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits
+ * the element's CSS class attribute value and then run the matching animation event function (if found).
+ * In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function
+ * be executed. It should be also noted that only simple or compound class selectors are allowed.
+ *
+ * Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned.
+ * As explained above, these callbacks are triggered based on the animation event. Therefore if an enter animation is run,
+ * and the JavaScript animation is found, then the enter callback will handle that animation (in addition to the CSS keyframe animation
+ * or transition code that is defined via a stylesheet).
+ *
+ */
+
+angular.module('ngAnimate', ['ng'])
+
+  /**
+   * @ngdoc object
+   * @name ngAnimate.$animateProvider
+   * @description
+   *
+   * The $AnimationProvider provider allows developers to register and access custom JavaScript animations directly inside
+   * of a module. When an animation is triggered, the $animate service will query the $animation function to find any
+   * animations that match the provided name value.
+   *
+   * Please visit the {@link ngAnimate ngAnimate} module overview page learn more about how to use animations in your application.
+   *
+   */
+  .config(['$provide', '$animateProvider', function($provide, $animateProvider) {
+    var selectors = $animateProvider.$$selectors;
+
+    var NG_ANIMATE_STATE = '$$ngAnimateState';
+    var rootAnimateState = {running:true};
+
+    $provide.decorator('$animate', ['$delegate', '$injector', '$window', '$sniffer', '$rootElement',
+      function($delegate, $injector, $window, $sniffer, $rootElement) {
+        
+      var noop = angular.noop;
+      var forEach = angular.forEach;
+
+      $rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
+
+       function lookup(name) {
+        if (name) {
+          var classes = name.substr(1).split('.'),
+              classMap = {};
+
+          for (var i = 0, ii = classes.length; i < ii; i++) {
+            classMap[classes[i]] = true;
+          }
+
+          var matches = [];
+          for (var i = 0, ii = selectors.length; i < ii; i++) {
+            var selectorFactory = selectors[i];
+            var found = true;
+            for(var j = 0, jj = selectorFactory.selectors.length; j < jj; j++) {
+              var klass = selectorFactory.selectors[j];
+              if(klass.length > 0) {
+                found = found && classMap[klass];
+              }
+            }
+            if(found) {
+              matches.push($injector.get(selectorFactory.name));
+            }
+          }
+          return matches;
+        }
+      };
+
+      /**
+       * @ngdoc object
+       * @name ngAnimate.$animate
+       * @requires $window, $sniffer, $rootElement
+       * @function
+       *
+       * @description
+       * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move)
+       * as well as during addClass and removeClass operations. When any of these operations are run, the $animate service
+       * will examine any JavaScript-defined animations (which are defined by using the $animateProvider provider object)
+       * as well as any CSS-defined animations against the CSS classes present on the element once the DOM operation is run.
+       *
+       * The `$animate` service is used behind the scenes with pre-existing directives and animation with these directives
+       * will work out of the box without any extra configuration.
+       *
+       * Please visit the {@link ngAnimate ngAnimate} module overview page learn more about how to use animations in your application.
+       *
+       */
+      return {
+        /**
+         * @ngdoc function
+         * @name ngAnimate.$animate#enter
+         * @methodOf ngAnimate.$animate
+         * @function
+         *
+         * @description
+         * Appends the element to the parent element that resides in the document and then runs the enter animation. Once
+         * the animation is started, the following CSS classes will be present on the element for the duration of the animation:
+         * + * .ng-enter + * .ng-enter-active + *+ * + * Once the animation is complete then the done callback, if provided, will be also fired. + * + * @param {jQuery/jqLite element} element the element that will be the focus of the enter animation + * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the enter animation + * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the enter animation + * @param {function()=} done callback function that will be called once the animation is complete + */ + enter : function(element, parent, after, done) { + $delegate.enter(element, parent, after); + performAnimation('enter', 'ng-enter', element, parent, after, done); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#leave + * @methodOf ngAnimate.$animate + * @function + * + * @description + * Runs the leave animation operation and, upon completion, removes the element from the DOM. Once + * the animation is started, the following CSS classes will be added for the duration of the animation: + *
+ * .ng-leave + * .ng-leave-active + *+ * + * Once the animation is complete then the done callback, if provided, will be also fired. + * + * @param {jQuery/jqLite element} element the element that will be the focus of the leave animation + * @param {function()=} done callback function that will be called once the animation is complete + */ + leave : function(element, done) { + performAnimation('leave', 'ng-leave', element, null, null, function() { + $delegate.leave(element, done); + }); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#move + * @methodOf ngAnimate.$animate + * @function + * + * @description + * Fires the move DOM operation. Just before the animation starts, the animate service will either append it into the parent container or + * add the element directly after the after element if present. Then the move animation will be run. Once + * the animation is started, the following CSS classes will be added for the duration of the animation: + *
+ * .ng-move + * .ng-move-active + *+ * + * Once the animation is complete then the done callback, if provided, will be also fired. + * + * @param {jQuery/jqLite element} element the element that will be the focus of the move animation + * @param {jQuery/jqLite element} parent the parent element of the element that will be the focus of the move animation + * @param {jQuery/jqLite element} after the sibling element (which is the previous element) of the element that will be the focus of the move animation + * @param {function()=} done callback function that will be called once the animation is complete + */ + move : function(element, parent, after, done) { + $delegate.move(element, parent, after); + performAnimation('move', 'ng-move', element, null, null, done); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#show + * @methodOf ngAnimate.$animate + * @function + * + * @description + * Reveals the element by removing the `ng-hide` class thus performing an animation in the process. During + * this animation the CSS classes present on the element will be: + * + *
+ * .ng-hide //already on the element if hidden + * .ng-hide-remove + * .ng-hide-remove-active + *+ * + * Once the animation is complete then all three CSS classes will be removed from the element. + * The done callback, if provided, will be also fired once the animation is complete. + * + * @param {jQuery/jqLite element} element the element that will be rendered visible or hidden + * @param {function()=} done callback function that will be called once the animation is complete + */ + show : function(element, done) { + performAnimation('show', 'ng-hide-remove', element, null, null, function() { + $delegate.show(element, done); + }); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#hide + * @methodOf ngAnimate.$animate + * + * @description + * Sets the element to hidden by adding the `ng-hide` class it. However, before the class is applied + * the following CSS classes will be added temporarily to trigger any animation code: + * + *
+ * .ng-hide-add + * .ng-hide-add-active + *+ * + * Once the animation is complete then both CSS classes will be removed and `ng-hide` will be added to the element. + * The done callback, if provided, will be also fired once the animation is complete. + * + * @param {jQuery/jqLite element} element the element that will be rendered visible or hidden + * @param {function()=} done callback function that will be called once the animation is complete + */ + hide : function(element, done) { + performAnimation('hide', 'ng-hide-add', element, null, null, function() { + $delegate.hide(element, done); + }); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#addClass + * @methodOf ngAnimate.$animate + * + * @description + * Triggers a custom animation event based off the className variable and then attaches the className value to the element as a CSS class. + * Unlike the other animation methods, the animate service will suffix the className value with {@type -add} in order to provide + * the animate service the setup and active CSS classes in order to trigger the animation. + * + * For example, upon execution of: + * + *
+ * $animate.addClass(element, 'super'); + *+ * + * The generated CSS class values present on element will look like: + *
+ * .super-add + * .super-add-active + *+ * + * And upon completion, the generated animation CSS classes will be removed from the element, but the className + * value will be attached to the element. In this case, based on the previous example, the resulting CSS class for the element + * will look like so: + * + *
+ * .super + *+ * + * Once this is complete, then the done callback, if provided, will be fired. + * + * @param {jQuery/jqLite element} element the element that will be animated + * @param {string} className the CSS class that will be animated and then attached to the element + * @param {function()=} done callback function that will be called once the animation is complete + */ + addClass : function(element, className, done) { + performAnimation('addClass', className, element, null, null, function() { + $delegate.addClass(element, className, done); + }); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#removeClass + * @methodOf ngAnimate.$animate + * + * @description + * Triggers a custom animation event based off the className variable and then removes the CSS class provided by the className value + * from the element. Unlike the other animation methods, the animate service will suffix the className value with {@type -remove} in + * order to provide the animate service the setup and active CSS classes in order to trigger the animation. + * + * For example, upon the execution of: + * + *
+ * $animate.removeClass(element, 'super'); + *+ * + * The CSS class values present on element during the animation will look like: + * + *
+ * .super //this was here from before + * .super-remove + * .super-remove-active + *+ * + * And upon completion, the generated animation CSS classes will be removed from the element as well as the + * className value that was provided (in this case {@type super} will be removed). Once that is complete, then, if provided, + * the done callback will be fired. + * + * @param {jQuery/jqLite element} element the element that will be animated + * @param {string} className the CSS class that will be animated and then removed from the element + * @param {function()=} done callback function that will be called once the animation is complete + */ + removeClass : function(element, className, done) { + performAnimation('removeClass', className, element, null, null, function() { + $delegate.removeClass(element, className, done); + }); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#enabled + * @methodOf ngAnimate.$animate + * @function + * + * @param {boolean=} If provided then set the animation on or off. + * @return {boolean} Current animation state. + * + * @description + * Globally enables/disables animations. + * + */ + enabled : function(value) { + if (arguments.length) { + rootAnimateState.running = !value; + } + return !rootAnimateState.running; + } + }; + + /* + all animations call this shared animation triggering function internally. + The event variable refers to the JavaScript animation event that will be triggered + and the className value is the name of the animation that will be applied within the + CSS code. Element, parent and after are provided DOM elements for the animation + and the onComplete callback will be fired once the animation is fully complete. + */ + function performAnimation(event, className, element, parent, after, onComplete) { + if(nothingToAnimate(className, element)) { + (onComplete || noop)(); + } else { + var classes = ((element.attr('class') || '') + ' ' + className), + animationLookup = (' ' + classes).replace(/\s+/g,'.'), + animations = []; + forEach(lookup(animationLookup), function(animation, index) { + animations.push({ + start : animation[event], + done : false + }); + }); + + if (!parent) { + parent = after ? after.parent() : element.parent(); + } + var disabledAnimation = { running : true }; + + //skip the animation if animations are disabled, a parent is already being animated + //or the element is not currently attached to the document body. + if ((parent.inheritedData(NG_ANIMATE_STATE) || disabledAnimation).running) { + //avoid calling done() since there is no need to remove any + //data or className values since this happens earlier than that + (onComplete || noop)(); + return; + } + + var animationData = element.data(NG_ANIMATE_STATE) || {}; + + //if an animation is currently running on the element then lets take the steps + //to cancel that animation and fire any required callbacks + if(animationData.running) { + cancelAnimations(animationData.animations); + animationData.done(); + } + + element.data(NG_ANIMATE_STATE, { + running:true, + animations:animations, + done:done + }); + + if(event == 'addClass') { + className = suffixClasses(className, '-add'); + } else if(event == 'removeClass') { + className = suffixClasses(className, '-remove'); + } + + element.addClass(className); + + forEach(animations, function(animation, index) { + var fn = function() { + progress(index); + }; + + if(animation.start) { + if(event == 'addClass' || event == 'removeClass') { + animation.cancel = animation.start(element, className, fn); + } else { + animation.cancel = animation.start(element, fn); + } + } else { + fn(); + } + }); + } + + function nothingToAnimate(className, element) { + return !(className && className.length > 0 && element.length > 0); + } + + function cancelAnimations(animations) { + forEach(animations, function(animation) { + (animation.cancel || noop)(element); + }); + } + + function suffixClasses(classes, suffix) { + var className = ''; + classes = angular.isArray(classes) ? classes : classes.split(/\s+/); + forEach(classes, function(klass, i) { + if(klass && klass.length > 0) { + className += (i > 0 ? ' ' : '') + klass + suffix; + } + }); + return className; + } + + function progress(index) { + animations[index].done = true; + for(var i=0;i
$location.path() = {{main.$location.path()}}
@@ -71,12 +68,12 @@ ngRouteModule.directive('ngView', ngViewFactory);
           transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
         }
 
-        .example-animate-container {
+        .example-$animate-container {
           position:relative;
           height:100px;
         }
 
-        .example-animate-container > * {
+        .example-$animate-container > * {
           display:block;
           width:100%;
           border-left:1px solid black;
@@ -162,15 +159,14 @@ ngRouteModule.directive('ngView', ngViewFactory);
  * @description
  * Emitted every time the ngView content is reloaded.
  */
-ngViewFactory.$inject = ['$route', '$anchorScroll', '$compile', '$controller', '$animator'];
-function ngViewFactory(   $route,   $anchorScroll,   $compile,   $controller,   $animator) {
+ngViewFactory.$inject = ['$route', '$anchorScroll', '$compile', '$controller', '$animate'];
+function ngViewFactory(   $route,   $anchorScroll,   $compile,   $controller,   $animate) {
   return {
     restrict: 'ECA',
     terminal: true,
     link: function(scope, element, attr) {
       var lastScope,
-          onloadExp = attr.onload || '',
-          animate = $animator(scope, attr);
+          onloadExp = attr.onload || '';
 
       scope.$on('$routeChangeSuccess', update);
       update();
@@ -184,7 +180,7 @@ function ngViewFactory(   $route,   $anchorScroll,   $compile,   $controller,
       }
 
       function clearContent() {
-        animate.leave(element.contents(), element);
+        $animate.leave(element.contents());
         destroyLastScope();
       }
 
@@ -195,7 +191,7 @@ function ngViewFactory(   $route,   $anchorScroll,   $compile,   $controller,
         if (template) {
           clearContent();
           var enterElements = jqLite('').html(template).contents();
-          animate.enter(enterElements, element);
+          $animate.enter(enterElements, element);
 
           var link = $compile(enterElements),
               current = $route.current,
diff --git a/test/matchers.js b/test/matchers.js
index 67efd3e7..ef89e3ee 100644
--- a/test/matchers.js
+++ b/test/matchers.js
@@ -30,11 +30,23 @@ beforeEach(function() {
     return -1;
   }
 
+  function isNgElementHidden(element) {
+    return angular.element(element).hasClass('ng-hide');
+  };
+
   this.addMatchers({
     toBeInvalid: cssMatcher('ng-invalid', 'ng-valid'),
     toBeValid: cssMatcher('ng-valid', 'ng-invalid'),
     toBeDirty: cssMatcher('ng-dirty', 'ng-pristine'),
     toBePristine: cssMatcher('ng-pristine', 'ng-dirty'),
+    toBeShown: function() {
+      this.message = valueFn("Expected element to not have 'ng-hide' class");
+      return !isNgElementHidden(this.actual);
+    },
+    toBeHidden: function() {
+      this.message = valueFn("Expected element to have 'ng-hide' class");
+      return isNgElementHidden(this.actual);
+    },
 
     toEqual: function(expected) {
       if (this.actual && this.actual.$$log) {
diff --git a/test/ng/animateSpec.js b/test/ng/animateSpec.js
new file mode 100644
index 00000000..7f440bb5
--- /dev/null
+++ b/test/ng/animateSpec.js
@@ -0,0 +1,53 @@
+describe("$animate", function() {
+
+  describe("without animation", function() {
+    beforeEach(inject(function($compile, _$rootElement_, $rootScope) {
+      element = $compile('')($rootScope);
+      $rootElement = _$rootElement_;
+    }));
+
+    it("should add element at the start of enter animation", inject(function($animate, $compile, $rootScope) {
+      var child = $compile('')($rootScope);
+      expect(element.contents().length).toBe(0);
+      $animate.enter(child, element);
+      expect(element.contents().length).toBe(1);
+    }));
+
+    it("should remove the element at the end of leave animation", inject(function($animate, $compile, $rootScope) {
+      var child = $compile('')($rootScope);
+      element.append(child);
+      expect(element.contents().length).toBe(1);
+      $animate.leave(child);
+      expect(element.contents().length).toBe(0);
+    }));
+
+    it("should reorder the move animation", inject(function($animate, $compile, $rootScope) {
+      var child1 = $compile('