Skip to main content
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Paul Klinkenberg
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Paul Klinkenberg

Class Attribute Interpolation Is Safer In AngularJS 1.2 And Newer

By
Published in

NOTE: If you started using AngularJS with 1.2 or later, this post will only have academic value for you.

As of AngularJS 1.4, you can now mix Array and Object notation in the ngClass directive. This would enable you to pass in both dynamic class names as well as conditional classes. But, this got me thinking: why do I have a vague negative feeling when it comes to using class-attribute interpolation to generate dynamic class names? After digging through the code, I remembered that in older versions of AngularJS (pre-1.2), it could be unsafe to use the class interpolation in certain rare edge-cases. But, as of AngularJS 1.2, those edge-cases have been solved.

There have always been two ways to add dynamic classes to an element in AngularJS. You can use attribute interpolation to dynamically change the class attribute value:

class="widget widget-{{ item.subclass }}"

Or, you can use the ngClass directive to conditionally add or remove classes:

ng-class="{ active: item.isActive, expired: item.isExpired }"

In addition to these declarative approaches, you can also use a custom directive to add and remove classes from within the directive's link function. This is what the ngHide and ngShow directives do, conditionally adding and removing the ".ng-hide" class. But, in AngularJS 1.0.8 (and earlier), this is where we could run into problems with edge-cases in class attribute interpolation.

Before AngularJS 1.2, when animations were introduced, class attribute interpolation was just like any other attribute interpolation. Under the hood, it used $observe() to watch the class expression; then, when the expression result changed, it would overwrite the class attribute. This overwrite action would override any class-based changes that sibling directives may have made.

So for example, in a workflow like this, you end up losing some state:

  1. User goes to sort an element with class attribute interpolation.
  2. Sort plugin adds new class "X" during sort operation.
  3. During sort, the interpolation result changes.
  4. The attribute interpolation directive updates the class attribute, removing "X".

As of AngularJS 1.2, however, class attribute interpolation is now a special case. If you look at the interpolation directive, the logic now branches for "class" and "everything else." If the interpolation is being applied to a class attribute, the class attribute value is never overridden in totality. Instead, the changes in the class expression are used to conditionally add and remove specific classes using the $animate service. This means that, as of AngularJS 1.2, class attribute interpolation can safely live alongside other directives, like ngShow and ngHide, which mutate element classes.

To see this in action, I've put together a small demo which uses both class attribute interpolation and the ngClass directive to define the classes for items in an ngRepeat list:

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

	<title>
		Class Attribute Interpolation Much Safer In AngularJS 1.2 And Newer
	</title>

	<style type="text/css">

		li.friend {
			border: 1px solid #CCCCCC ;
			margin-bottom: 10px ;
			padding: 10px 10px 10px 10px ;
		}

		li.friend-nth0 {
			border-width: 1px ;
		}

		li.friend-nth1 {
			border-width: 4px ;
		}

		li.friend-nth2 {
			border-width: 8px ;
		}

		li.best-friend {
			border-color: gold ;
		}

	</style>
</head>
<body>

	<h1>
		Class Attribute Interpolation Much Safer In AngularJS 1.2 And Newer
	</h1>

	<ul ng-controller="AppController">
		<!--
			Notice that are using two different sources of class names: we're using the
			attribute interpolation on [class] to build dynamic class names and we're
			using [ng-class] to toggle conditional class names.
		-->
		<li
			ng-repeat="friend in friends"
			class="friend friend-nth{{ $index }}"
			ng-class="{ 'best-friend': friend.isBFF }">

			{{ friend.name }}

		</li>
	</ul>


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

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


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


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

				// I am the collection of friends being rendered.
				$scope.friends = [
					{
						id: 1,
						name: "Frannie",
						isBFF: false
					},
					{
						id: 2,
						name: "Joanna",
						isBFF: false
					},
					{
						id: 3,
						name: "Kim",
						isBFF: true
					}
				];

			}
		);

	</script>

</body>
</html>

Notice that we are using class attribute interpolation to dynamically generate the index-based class name. And, we're using the ngClass directive to dynamically add or remove the "best-friend" class. And, when we run this, both the approaches work quite nicely together:

Using both class attribute interpolation and the ngClass directive to generate dyanmic class state.

Now, just to be clear, class attribute interpolation and the ngClass directive have always been able to work well together. Even in AngularJS 1.0.8, the ngClass directive uses the $observe() method to make sure that its classes weren't clobbered by attribute interpolation. The above demo was just to showcase the two approaches working together, not to demonstrate a change in behavior.

Chances are, you didn't even know about this edge-case regarding attribute interpolation. And, if you started using AngularJS after 1.0.8, then it was never even an issue for you. But, if nothing else, it's just nice to see how the underlying framework evolves to deal with a more comprehensive set of use-cases.

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

Reader Comments

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