Right in the jangular

Having hacked away with jQuery for years when I first started using AngularJS it was made clear: thinking Angular v thinking jQuery is the hardest part.

And it's true. While I understood what they were trying to say, I couldn't see it. I couldn't figure out how to think differently. Then everything became clear.

The example

The best example of this difference that I have come across is the interactive gallery. It’s a common user interaction: a number of items on the page with a featured item which is special in some way (bigger, wider, has a border - whatever). To show exactly what I mean I’ve developed the same functionality using jQuery and AngularJS here. This example should help make clear two important distinctions: event triggers and DOM interaction.

The markup

Our gallery is going to be an HTML list which uses classes to describe the feature item. All styling concepts will be ignored as your JS should make zero difference to your CSS (or you might be doing it wrong…).

Please note the HTML is presented twice because there are minor declarative differences between AngularJS and jQuery. You don’t need to change our markup structure or clarity for either framework.

The jQuery

To do this in jQuery I would rely on a set of callbacks for the interactive controls (.gallery-control-prev and .gallery-control-next) which manually check for the current state and apply changes as required. An example of this:

<div class="gallery">  
    <ul class="gallery-items">
        <li id="item-1" class="gallery-item state--active">
            ITEM ONE
        </li>
        <li id="image-2" class="gallery-item">
            ITEM TWO
        </li>
        <li id="image-3" class="gallery-item">
            ITEM THREE
        </li>
    </ul>
    <ul class="gallery-controls">
        <li class="gallery-control gallery-control-prev">
            <a href="#">Previous</a>
        </li>
        <li class="gallery-control gallery-control-next">
            <a href="#">Next</a>
        </li>
    </ul>
</div>  
$.fn.gallery = function () {
    return $(this).each(function () {
        var $gallery = $(this);
        $gallery.find('.gallery-control-prev').on('click', function () {
            var $current = $gallery.find('.state--active');
            var $prev = $current.prev('.gallery-item');
            if ($current.length && $prev.length) {
                $current.removeClass('state--active')
                $prev.addClass('state--active');
            }
        });
        $gallery.find('.gallery-control-next').on('click', function () {
            var $current = $gallery.find('.state--active');
            var $next = $current.next('.gallery-item');
            if ($current.length && $next.length) {
                $current.removeClass('state--active')
                $next.addClass('state--active');
            }
        });
    });
};

Ignoring the lack of optimization (for the sake of succinct and easy to understand code) this will do exactly what you would expect.

Every time a .gallery-control-next/.gallery-control-prev element is clicked a function is called. This function:

  1. Find the current state—active item.
  2. Find the next or previous element
  3. Update the classes of the current and new element.

What is important to see is everything is event driven and everything is controlled directly through interactions with the DOM. A click (event) triggers a global function. That function searches the DOM (interaction) for elements by class name. It searches the DOM (interaction) for next element. It explicitly updates an element’s (interaction) class string.

This solution thought forces the programmer to understand how the DOM works and leverage that understand to represent the gallery’s state.

The AngularJS

The AngularJS solution I’ve created uses a ngController with a few small methods to create the same functionality:

<div class="gallery" ng-controller="GalleryCtrl">  
    <ul class="gallery-items">
        <li id="item-1" 
            class="gallery-item" 
            ng-class="{ 'state--active': isCurrent(0) }">
            ITEM ONE
        </li>
        <li id="image-2" 
            class="gallery-item" 
            ng-class="{ 'state--active': isCurrent(1) }">
            ITEM TWO
        </li>
        <li id="image-3" 
            class="gallery-item" 
            ng-class="{ 'state--active': isCurrent(2) }">
            ITEM THREE
        </li>
    </ul>
    <ul class="gallery-controls">
        <li class="gallery-control gallery-control-prev" 
            ng-click="prev()">
            <a href="#">Previous</a>
        </li>
        <li class="gallery-control gallery-control-next" 
            ng-click="next()">
            <a href="#">Next</a>
        </li>
    </ul>
</div>  
angular.module('gallery', []).controller('GalleryCtrl', ['$scope', function ($scope) {  
    var current = 0;
    var items = [0, 1, 2];
    $scope.isCurrent = function (item) {
        return item === current;
    }
    $scope.next = function () {
        if (items.indexOf(current + 1) > -1) {
            current++;
        }
    }
    $scope.prev = function () {
        if (items.indexOf(current - 1) > -1) {
            current--;
        }
    }
}]);

The JS code has 2 local variables (current and items) which keep the gallery’s state. items is a list of each item (in a real world situation this could be an array of item objects from a JSON API that could lazily generate the HTML as well~). current is a reference to the current featured item.

There are 3 methods:

  • isCurrent() returns true when the item provided is current
  • next() and prev() increment or decrement the current item

Local data + methods huh? My JS doesn’t even reference the underlying HTML!

This code is not event driven and you won’t see a single line of code that references the DOM. It separates completely, the interaction of the DOM from the code. The thinking is different because it is defined by objects and methods agnostic of the user interface! You do not need to understand how the DOM works to write and test your application code.

The clarity

“So next and previous are basically events in AngularJS anyway, right?”

The difference is that you don't need to think in terms of creating event driven callbacks. There is no global anonymous function hidden away without any ties to the HTML. It uses an HTML declared directive (ng-click=“next”) to describe the interaction. Even better this is clearly defined within the HTML for anyone writing or using the code in the future.

”The item classes change in both examples too?“

Yes, but does the JS code do that? No. It’s important that in AngularJS you (generally) don’t interact with the DOM in any direct way. You write HTML that states: if this element isCurrent() then it has a class of state--active. This decoupling removed any thoughts of the DOM!

Neither way is incorrect, they both produce the exact same user interaction in the browser. They do, however, follow clearly different thought pathways to derive the solution.