Skip to main content
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Dave Ferguson and Simon Free and Tim Cunningham and Jason Dean
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Dave Ferguson Simon Free Tim Cunningham Jason Dean

Migrating From ui-if To ng-if In AngularJS

By
Published in

When I first started using AngularJS a few years ago, I also started using a library called Angular-UI. This was a supplemental module that filled in a few of the holes in the AngularJS core. For me, the best part of Angular-UI was a directive called "ui-if". This directive conditionally included DOM (Document Object Model) elements based on an AngularJS expression. AngularJS added this directive, as "ng-if", in AngularJS 1.1.5. But, these two directives are different in a subtle way, that's important to understand.

Run this demo in my JavaScript Demos project on GitHub.

When AngularJS first added the ngIf directive in 1.1.5, it behaved the same as the Angular-UI version. But, as of AngularJS 1.2.1, the behavior is different. To demonstrate, I have a counter that can be incremented or decremented; and, if the counter is non-zero, I'm going to include an element that displays the count. I'm doing this with both the ngIf and the old uiIf directive:

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

	<title>
		Migrating From ui-if To ng-if In AngularJS
	</title>

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

	<h1>
		Migrating From ui-if To ng-if In AngularJS
	</h1>

	<p>
		Clicks:
		<a ng-click="incrementCount( 1 )">Increment</a>
		&mdash;
		<a ng-click="incrementCount( -1 )">Decrement</a>
	</p>

	<!-- Conditionally include element with UI-IF (Angular-UI). -->
	<p ui-if="clickCount" bn-log="ui-if : {{ clickCount }}">
		You've clicked me {{ clickCount }} time(s)!
	</p>

	<!-- Conditionally include element with NG-IF (AngularJS). -->
	<p ng-if="clickCount" bn-log="ng-if : {{ clickCount }}">
		You've clicked me {{ clickCount }} time(s)!
	</p>


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

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


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


		// I control the root of the application.
		app.controller(
			"AppController",
			function( $scope, $interpolate ) {

				$scope.clickCount = 0;


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


				// I increment the click count by the given delta.
				$scope.incrementCount = function( increment ) {

					$scope.clickCount += ( increment || 1 );

				};

			}
		);


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


		// I log out the value of the directive attirbute with a timestamp. This is
		// here to track when the DOM is being linked.
		app.directive(
			"bnLog",
			function() {

				// I bind the JavaScript events to the scope.
				function link( $scope, element, attributes ) {

					console.log( "Linked", attributes.bnLog );

				}


				// Return the directive configuration.
				return({
					link: link,
					restrict: "A"
				});

			}
		);


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


		// Define "ui-if" for our application.
		// --
		// NOTE: This was taken from the old Angular-UI source code v0.2.1.
		app.directive(
			"uiIf",
			function() {
				return {
					transclude: 'element',
					priority: 1000,
					terminal: true,
					restrict: 'A',
					compile: function (element, attr, linker) {
						return function (scope, iterStartElement, attr) {
							iterStartElement[0].doNotMove = true;
							var expression = attr.uiIf;
							var lastElement;
							var lastScope;
							scope.$watch(expression, function (newValue) {
								if (lastElement) {
									lastElement.remove();
									lastElement = null;
								}
								if (lastScope) {
									lastScope.$destroy();
									lastScope = null;
								}
								if (newValue) {
									lastScope = scope.$new();
									linker(lastScope, function (clone) {
										lastElement = clone;
										iterStartElement.after(clone);
									});
								}
								iterStartElement.parent().trigger("$childrenChanged");
							});
						};
					}
				};
			}
		);

	</script>

</body>
</html>

On each of the conditional elements, I have a bnLog directive that is there to tell me when the bnLog directive is linked. This is intended to signal when DOM elements were destroyed and re-created; every time one of the conditional elements is created, I'll get a log item.

As I increment the count a few times, from zero to 4, I get the following console log output:

Linked ui-if : 1
Linked ng-if : 1
Linked ui-if : 2
Linked ui-if : 3
Linked ui-if : 4

Notice that when the clickCount is moved from zero (falsey) to 1 (truthy), both the uiIf and the ngIf directives create and link the conditional DOM element. But, as we proceed higher than 1, notice that the ngIf directive stops linking while the uiIf directive continues to link.

This is because the uiIf directive is actually destroying and re-creating the conditional DOM element every time the value of the uiIf expression changes, even if the change keeps the value truthy. When AngularJS introduced ngIf, it also worked this way; but in more recent releases, AngularJS converts the value to a boolean before it takes any action.

For me, this is actually a good thing because it's how I always used uiIf. I never liked the way it watched the value so closely; as such, I've always converted the value to a boolean as part of the directive expression:

<div ui-if=" !! someValue "> ... </div>

Notice that I am using the double-not (double-bang) operator to convert the value to a boolean before it gets passed off to the internals of the uiIf $watch() callback. So, for me personally, migrating from uiIf to ngIf won't be a considerable change. But, if you were taking "advantage" of the way uiIf worked, you have to be careful when you start using ngIf.

After AngularJS added the ngIf directive, Angular-UI dropped the uiIf directive. So, there's a good chance that a lot of you have no idea what I'm talking about. But if you're still using Angular-UI, it might be something you need to consider when upgrading.

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