Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with:

Migrating From ui-if To ng-if In AngularJS

By Ben Nadel on

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.

Tweet This Groovy post by @BenNadel - Migrating From ui-if To ng-if In AngularJS Thanks my man — you rock the party that rocks the body!



Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.