AngularJS with Animation and Callback
Wednesday, Mar 19, 2014
What is the problem
When we use AngularJS, manipulating the DOM element is something we often want to do. I encountered a problem recently that I have an object bound to a bunch of fields in a container, then what I need to do is to bind a new object, but with an fade in and fade out animation. So it is pretty much like this:
- Fade out the container with old object
- Bind the new object
- Fade in the container with new object
How did I solve it
I first went to Google (of course this is what you guys would do as well), and found this article AngularJS - Animating the Angular Way by EggHead. It is a really good tutorial talks about how to define a directive to watch for the attribute and then use the $animate service to do the animation. However, this article did not address how to solve the problem of updating the scope after the animation. So here I just want to quickly give you the way of how to do it.
Actually it is pretty simple, all you need is to add a call back function, and some tricks.
Here is an example, or you can try to open the demo here.
<!DOCTYPE html>
<html>
<head>
<title>AngularJS Animate And Callback Demo :: XYZHOU.com</title>
</head>
<body>
<div id="ng-app" ng-app="myApp" ng-controller="myController" ng-init="init()">
<div fade-me="isFade" ng-click="flip()">
Click this: {{myValue}}
</div>
</div>
<script src="//code.jquery.com/jquery-2.1.0.min.js"></script>
<script src="//code.angularjs.org/1.2.14/angular.min.js"></script>
<script src="//code.angularjs.org/1.2.14/angular-animate.min.js"></script>
<script>
var myApp = angular.module('myApp', ['ngAnimate']);
myApp.controller('myController', ['$scope', function($scope) {
$scope.init = function() {
// init values
$scope.isFade = false;
$scope.myValue = "Foo";
};
$scope.flip = function() {
// flip the value only when the container is faded out
if ($scope.isFade)
$scope.myValue = ($scope.myValue === "Foo") ? "Bar" : "Foo";
// flip the fade value
$scope.isFade = !$scope.isFade;
}
}]);
// define the directive
myApp.directive("fadeMe", ['$animate', function($animate) {
return function(scope, element, attrs) {
// watch the fade-me directive
scope.$watch(attrs.fadeMe, function(newVal) {
if (newVal) {
$animate.addClass(element, "fade", function() {
// this its the call back, wrap it in $apply to update the angular models
scope.$apply(scope.flip());
});
} else {
$animate.removeClass(element, "fade");
}
});
}
}]);
// define the animate service
myApp.animation(".fade", function() {
return {
addClass: function(element, className, callback) {
element.animate({ opacity: 0 }, callback);
},
removeClass: function(element, className, callback) {
element.animate({ opacity: 1 }, callback);
}
}
});
</script>
</body>
</html>
Wrap it up
- Import necessary scripts (jQuery for animate(), angularjs, angularjs-animate).
- Create the directive and define the animate service.
- Watch the fade value in the directive and provide a callback with $apply to update the angular model.
The key idea here is to use scope.$apply to update the angular models, because I think the callback is just passed to jQuery as a normal callback function, and has nothing to do with angular. It is about the same issue when you want to use a jQuery plugin.