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.

Pain Point: PKMN

There are 718 Pokémon. I can hardly remember the types of the 6 that I use throughout the course of the game, let alone the other 700+.

Whenever I’m looking for a weekend project the first thing I consider “is there a pain point I have at the moment, and how can I fix it?”. This simple question ensures that I’m building tools that solve problems... that solve my problems at least.

I’ve been, very slowly, playing through the latest Pokémon game on the 3DS. The basic idea is that each monster has a type (fire, water, grass, etc.) and each one is weak/strong against another: rock, paper, scissors style. The issue is that the types are not obvious from the name or graphic of the monster you're fighting. I have consistently gone to the net to quickly search for a Pokémon by name, scroll down some ridiculous Wiki style page to find their type, and then (because my brain doesn’t care) go find out which type is good against it! Nope. Too hard. Pain point.

I spent a few hours this weekend seeing how I could quickly fix this with a web app. Turns out it's about 4 hours. It involved finding Pokémon data in a usable format (was hoping for JSON but I managed to find some open source CSV instead) and then code a page to interact with it in an easy and mobile friendly (oh so important!) way. I built an AngularJS app which uses a JSON database storing information about every single Pokémon and their weaknesses.

SOLUTION: PKMN-TYPÉCAST

When you load up the app it lists a few Pokémon and allows you to search for the monster you’re battling. When you find the correct one you see a list of all the types that are VERY EFFECTIVE, NOT VERY EFFECTIVE or IMMUNE. Now I can, at a glance, work out exactly what move I should be using against my enemy.

Building quickly was important so I stuck with the tools that I’m currently using most. I’ve got a command line node script (./bin/generate-json), which takes JSON versions of CSV data found from another Github project veekun/pokedex, and maps it to my needs. Using many map and filter methods it generates a large JSON file which contains a list of every Pokémon, their type and their strengths, immunities, and weaknesses.

There is some complication when a Pokémon has more than one type, which this script handles by filtering out duplicate and conflicting types.

Once I had the data taken care of AngularJS was a dream to code the user facing app. There are, realistically, only two functions to get this entire app working. First, it loads the JSON database using $resource so that I have an Array of JS objects, each representing a Pokémon.

var Pokemon = $resource('data/db.json', {}, {
    get: { method:'GET', isArray: true }
});
var pokemons = Pokemon.get(function (pkmn) {
    $scope.pokemons = pkmn.slice(0,9);
});

This $resource function makes an HTTP request to the provided URL and maps the returned JSON Array to standard JS objects. Generally $resource is used to run queries against the URL (send POST/GET variables like ?limit=10) for searching and filtering, however I only need a single set of data so I decided load the entire database in one hit. Not the most efficient use of RAM, but there are only 700 records and it runs fine on the devices I own. The pokemons variable is actually a promise, returned from Pokemon.get, so I can’t use it immediately as it doesn’t actually have data — it will only have it once the request is completed. You can pass get() a callback to run with the data as soon as it has arrived, which I’ve used to pre-fill some initial Pokémon to the screen.

The second part of functionality allows the app to watch the search field in the browser and filter the visible Pokémon to match:

$scope.$watch('pokemonSearch', function (newValue, oldValue) {
    if (!newValue || newValue.length < 3) {
        return;
    }
    var pokemon = newValue.toLowerCase();
    $scope.pokemons = pokemons.filter(function (item) {
        return item.identifier.indexOf(pokemon.toLowerCase()) === 0;
    });
});

$watch is a special method that will invoke a provided function every time the $scope variable pokemonSearch is changed. This scope variable ($scope.pokemonSearch) is binded to the form through an attribute added to the HTML: ng-model="pokemonSearch”. This attribute tells AngularJS to map the value of this input field directly to $scope.pokemonSearch. What that means is: if you type into the input field in your browser $scope.pokemonSearch will update to match the user typed string and if you change $scope.pokemonSearch in JS your input value will update to match the JS value. This is what AngularJS calls two-way binding and is a large reason why the framework is so powerful.

The method provided to $watch does the filtering of Pokémon. If the newValue, that is the string which a user typed into the form input, is over a certain size I use filter() against the list of all Pokémons to return only those which start with newValue. This data is set to another two-way binding variable $scope.pokemons so that the list of Pokémon will automatically update in your browser!

This time, instead of a form input, I’ve used ng-repeat to bind the data directly to plain old HTML elements:

<li ng-repeat="pokemon in pokemons">

The ng-repeat directive takes an Array and repeats the element for each record. The current index becomes a local variable as named (pokemon) which you can use to display data for your users.

<h2 class="pokemon-name">{{pokemon.identifier}}</h2>
<h4 class="pokemon-types">
    Type
    <span ng-repeat="type in pokemon.types">
        {{type.identifier}}
    </span>
</h4>

Here you can see how {{pokemon}}, which represents a single object within the $scope.pokemons array, is used to display it’s details. AngularJS will automatically replace {{pokemon.indentifier}} with the value in the pokemon object every time it changes. I’ve used plain HTML to represent the data I want from each Pokemon. Now, any time that $scope.pokemons changes this list of <li> elements will automatically be updated and redrawn with the DOM to show the current data.

In short: the user’s input search field is tied to $scope.pokemonSearch. Anytime that changes $scope.pokemons is updated to only have Pokémon which match the search. Anytime $scope.pokemons is changed the Pokémon <li>s are redrawn for the user. That’s it. We have an app.

I’m hosting the whole thing on a github.io project page, because it’s completely static with the database stored as JSON file.

I’ve managed to solve this, albeit pointless pain point, with a few hours coding. It’s not a bad introduction to AngularJS for anyone who is interested in seeing the power of two-way binding. This same project could have been built with jQuery (or no framework at all) but it would have taken considerably more time, especially when mapping data from a form to JS to search and then back to HTML via template.

Time to get back into Pokémon. I want to move on to Zelda already.