Skip to main content
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Jake Scott
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Jake Scott

Animating Static Child Nodes Using ngAnimate In AngularJS

By
Published in Comments (2)

The other day, I found myself with an interesting problem. I had a container that I was conditionally including using the ngIf directive in AngularJS. And, as the container was added and removed from the DOM (Document Object Model), I wanted nested elements within the container to animate; but, I didn't necessarily have a meaningful animation for the container itself (it was a non-visual element). Thankfully, after some experimentation, I was able to get child nodes to animate, using ngAnimate, even though AngularJS was only observing the container.

Run this demo in my JavaScript Demos project on GitHub.

The solution to this problem was to give the dynamic container element a transition duration. While I didn't have a meaningful animation for the container itself, simply giving it a duration was enough to get AngularJS to add the ng-animate CSS classes. Then, once I was able to get those in place, I was able to animate the child nodes by defining contextual rules that were based on the container's ng-animate classes.

So, for example, one of the CSS blocks for the nested elements looked like this:

div.container.ng-enter div.box { ... }

Here, you can see that the div.box styles will only be applied in the context of a container that is in the "ng-enter" phase of the animation life-cycle. Essentially, I'm animating descendant elements by hooking into the animation state of the ancestor element. With this approach, I am can animate "static" elements while AngularJS is adding or removing "dynamic" elements.

To see this in action, I have a demo with a dynamic container. The container is managed by the ngIf directive. As the container is added and removed, I'm animating child nodes by hooking into the animation states of the container:

<!doctype html>
<html ng-app="Demo">
<head>
	<meta charset="utf-8" />

	<title>
		Animating Child Nodes Using ngAnimate In AngularJS
	</title>

	<link rel="stylesheet" type="text/css" href="./demo.css"></link>
</head>
<body ng-controller="AppController">

	<h1>
		Animating Child Nodes Using ngAnimate In AngularJS
	</h1>

	<p>
		<a ng-click="toggleContainer()">Show Container</a>
	</p>

	<!--
		NOTE: The "container" element is the one that AngularJS is checking for animation
		settings; however, it is NOT the element that we are "truly" animating. Rather,
		we are conditionally animating the child / descendant / nested nodes based on the
		state of the container.
	-->
	<div ng-if="isShowingContainer" class="container">

		<div class="backdrop"></div>

		<a ng-click="toggleContainer()" class="hide">Hide Container</a>

		<div class="box box1">One</div>
		<div class="box box2">Two</div>
		<div class="box box3">Three</div>
		<div class="box box4">Four</div>

	</div>


	<!-- Load scripts. -->
	<script type="text/javascript" src="../../vendor/angularjs/angular-1.4.3.min.js"></script>
	<script type="text/javascript" src="../../vendor/angularjs/angular-animate-1.4.3.min.js"></script>
	<script type="text/javascript">

		// Create an application module for our demo.
		angular.module( "Demo", [ "ngAnimate" ] );


		// --------------------------------------------------------------------------- //
		// --------------------------------------------------------------------------- //


		// I control the root of the application.
		angular.module( "Demo" ).controller(
			"AppController",
			function AppController( $scope ) {

				$scope.isShowingContainer = false;


				// ---
				// PUBLIC METHODS.
				// ---


				// If the container is visible, I hide it. If it is hidden, I show it.
				$scope.toggleContainer = function() {

					$scope.isShowingContainer = ! $scope.isShowingContainer;

				};

			}
		);

	</script>

</body>
</html>

And, here's the CSS that I'm using. Take note of the "ng-enter" and "ng-leave" classes for div.container - they have a duration, but no meaningful animation. All of the animation is done by the nested elements.

a {
	color: red ;
	cursor: pointer ;
	text-decoration: underline ;
	user-select: none ;
		-moz-user-select: none ;
		-webkit-user-select: none ;
}

div.container {
	bottom: 0px ;
	left: 0px ;
	position: fixed ;
	right: 0px ;
	top: 0px ;
	z-index: 2 ;
}

div.backdrop {
	background-color: rgba( 0, 0, 0, 0.8 ) ;
	bottom: 0px ;
	left: 0px ;
	position: absolute ;
	right: 0px ;
	top: 0px ;
}

a.hide {
	background-color: #FF0099 ;
	border-radius: 4px 4px 4px 4px ;
	color: #FFFFFF ;
	height: 50px ;
	left: 50% ;
	line-height: 50px ;
	margin: -25px 0px 0px -120px ;
	position: absolute ;
	text-align: center ;
	top: 50% ;
	width: 240px ;
}

div.box {
	background-color: #F0F0F0 ;
	border: 1px solid #CCCCCC ;
	border-radius: 4px 4px 4px 4px ;
	height: 100px ;
	line-height: 100px ;
	position: absolute ;
	text-align: center ;
	width: 100px ;
}

div.box1 {
	left: 150px ;
	top: 150px ;
}

div.box2 {
	right: 150px ;
	top: 150px ;
}

div.box3 {
	bottom: 150px ;
	right: 150px ;
}

div.box4 {
	bottom: 150px ;
	left: 150px ;
}



/* CSS Transition Information. */


/*
	This is the container element managed by the NG-IF directive. As such, this is the
	element that AngularJS is going to be checking to see if it has any animation
	settings associated with it. Even though we are are not "truly" animating this
	element, we want AngularJS to "transition" this element in and out in order to give
	the descendant nodes time to transition in their own way.
*/
div.container.ng-enter {
	transition-duration: 500ms ;
}

div.container.ng-leave {
	transition-duration: 250ms ;
}


/*
	Now that the container is set to transition (in a non-functional way), we can
	configure the descendant nodes to transition based on the NG-ENTER AND NG-LEAVE
	classes that get associated with the transitioning container.

	In order to make sure that none of the nested transitions don't exceed the container
	transition in duration, we're just going to have the descendant nodes inherit the
	duration that the container is using.

	NOTE: We don't have to worry about the "NG-ANIMATE-CHILDREN" directive since
	AngularJS isn't actually managing these children - it's only managing the container.
	CSS is taking care of the rest.
*/
div.container.ng-enter div.backdrop,
div.container.ng-leave div.backdrop,
div.container.ng-enter a.hide,
div.container.ng-leave a.hide,
div.container.ng-enter div.box1,
div.container.ng-leave div.box1,
div.container.ng-enter div.box2,
div.container.ng-leave div.box2,
div.container.ng-enter div.box3,
div.container.ng-leave div.box3,
div.container.ng-enter div.box4,
div.container.ng-leave div.box4 {
	transition-duration: inherit ;
	transition-property: bottom, left, right, top, opacity ;
	transition-timing-function: ease ;
}


/* Backdrop. */

div.container.ng-enter div.backdrop {
	opacity: 0.0 ;
}

div.container.ng-enter-active div.backdrop {
	opacity: 1.0 ;
}

div.container.ng-leave-active div.backdrop {
	opacity: 0.0 ;
}


/* Close link. */

div.container.ng-enter a.hide {
	opacity: 0.0 ;
	top: -5% ;
}

div.container.ng-enter-active a.hide {
	opacity: 1.0 ;
	top: 50% ;
}

div.container.ng-leave-active a.hide {
	opacity: 0.0 ;
	top: 105% ;
}


/* Box 1. */

div.container.ng-enter div.box1 {
	top: -100px ;
	left: -100px ;
}

div.container.ng-enter-active div.box1 {
	top: 150px ;
	left: 150px ;
}

div.container.ng-leave-active div.box1 {
	top: -150px ;
	left: -150px ;
}


/* Box 2. */

div.container.ng-enter div.box2 {
	right: -100px ;
	top: -100px ;
}

div.container.ng-enter-active div.box2 {
	right: 150px ;
	top: 150px ;
}

div.container.ng-leave-active div.box2 {
	right: -150px ;
	top: -150px ;
}


/* Box 3. */

div.container.ng-enter div.box3 {
	bottom: -100px ;
	right: -100px ;
}

div.container.ng-enter-active div.box3 {
	bottom: 150px ;
	right: 150px ;
}

div.container.ng-leave-active div.box3 {
	bottom: -150px ;
	right: -150px ;
}


/* Box 4. */

div.container.ng-enter div.box4 {
	bottom: -100px ;
	left: -100px ;
}

div.container.ng-enter-active div.box4 {
	bottom: 150px ;
	left: 150px ;
}

div.container.ng-leave-active div.box4 {
	bottom: -150px ;
	left: -150px ;
}

When I run the above page and toggle the container, we get the following output:

Animating static child nodes using ngAnimate in AngularJS.

As you can see, the transitional CSS is being applied to the nested elements because the nested elements are piggy-backing off of the ng-animate classes that have been added to the parent. The beauty of this is that it is still driven completely by the CSS, which is a huge part of what makes the ngAnimate module such an elegant solution.

Want to use code from this post? Check out the license.

Reader Comments

15,811 Comments

WARNING: I think there may be something about the ngAnimate code that only allows this approach to work if there is only a *single* container element animating. If you have multiple, concurrently animating containers, this seems to break down in an odd way (several elements skipping the animation).

I am currently debugging to see if I can figure out why this would change depending on the number of elements. I suspect it has to do with when the elements are added to the DOM - but not sure.

15,811 Comments

@All,

OK, a quick follow-up to this post. There is an issue when you have more than one container - the child nodes have to take the injected "transition-delay" into account:

www.bennadel.com/blog/2909-child-animations-have-to-take-the-magical-transition-delay-into-account-in-angularjs.htm

I am not entirely sure why this works fine with just a single container; but, with more than one container, you need to "inherit" the transition-delay in the child nodes.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel