AngularJS Tutorial — Build an App Using Directives and Data Binding

AngularJS is quickly gaining a reputation as one of the most forward-thinking JavaScript frameworks around, with good reason. Backed and developed by Google, Angular takes an approach to your front end that may seem a little odd at first, but you’ll soon wonder why you did things any other way.

Angular gives developers the ability to write front end code without resorting to directly manipulating the DOM. This tutorial will get you started with the framework by building an app using directives and data binding to define dynamic views and controllers.

If you’re familiar with CoffeeScript (not required by Angular) you’ll have more fun with this article, but a working knowledge of JavaScript should suffice.

You’ve likely seen a bunch of Todo apps before, so let’s build something fun — noughts and crosses!

We’ll start out by marking up our board.

Angular claims to extend the vocabulary of HTML instead of hiding the DOM behind JavaScript. The philosophy is that HTML is pretty good on its own, but we can add a few more elements and attributes in order to build a powerful, dynamic templating language that you’re already familiar with.

Our game board will just be a simple table. If we program by wishful thinking, all we really want to have to do is iterate over a game board, outputting a cell for each. The real code to do so is pretty close to our vision:

<table>
  <tr ng-repeat="row in board.grid">
    <td ng-repeat="cell in row">
      {{ cell.marker }}
    </td>
  </tr>
</table>

Wait, what are those funny ng things and mustache brackets for? Let’s back up a bit and take this one step at a time.

<tr ng-repeat="row in board.grid">

AngularJS directives

ng-repeat is an Angular directive, one of the provided HTML extensions. It allows us to iterate over a collection, instantiating the template for each item within. In our case, we’re telling Angular to repeat the <tr> for every row in the grid property of our board – assume for now the grid is a two dimensional array and board is an object on the window.

<td ng-repeat="cell in row">
  {{ cell.marker }}
</td>

We then use another ng-repeat directive to iterate over the cells in the row. The double curly braces here indicate an expression using Angular data binding – the contents of the td will be replaced with the marker property of the respective cell.

Pretty simple so far, right? You immediately get a sense for what the resulting markup will look like. We don’t have any need to use something heavy like jQuery to create new elements and populate them, we just make our template explicit. This is more maintainable – we know exactly where and how the DOM will be changed just by looking at our HTML, not tracking down some obscure JavaScript we don’t really remember writing.

Now that we can visualize the state of our board, we’ll provide it with a data source by defining what board really is.

app = angular.module('ngOughts', ['ng'])

Web begin by adding some JavaScript that defines an Angular module for our application. The first argument is the name of our app, ['ng'] means we require the Angular ‘ng’ module which provides the core Angular services.

We adjust our HTML to indicate we’ll be using our application module with the ng-app directive.

<html ng-app='ngOughts'>

MVC — defining a controller and views

Here’s where the MVC nature of Angular comes into play. We add a little more JS to call the controller function on our newly created application module, passing the name of our controller and a function that implements it.

app.controller "BoardCtrl", ($scope) ->

In this case, our controller function takes one argument, $scope, which is a dependency of our controller. Angular makes use of dependency injection in order to provide this service object to us, inferring the correct object from the name of our function parameter (there’s an alternate syntax that allows for minification too).

We now add an ng-controller directive to our HTML template to connect it to our controller:

<body ng-controller="BoardCtrl">
  <table>
    <tr ng-repeat="row in board.grid">
      ...
    </tr>
  </table>
</body>

Again, as simple as an attribute with the name of our controller. Here’s where things get interesting – the elements nested within our body tag now have access to the $scope service object. Our ng-repeat attribute will then look on the BoardCtrl scope for the board variable, so let’s define that:

app.controller "BoardCtrl", ($scope, Board) ->
    $scope.board = new Board

Now we’re getting somewhere. We’ve injected a Board into our controller, instantiated it and made it available on the scope of BoardCtrl.

Let’s go ahead and actually implement a simple Board class.

class Board
  SIZE = 3
  EMPTY  = ' '
  NOUGHT = 'O'
  CROSS  = 'X'
  PLAYER_MARKERS = [NOUGHT, CROSS]
  constructor: ->
    @reset()
  reset: ->
    @grid = [1..SIZE].map ->
      [1..SIZE].map ->
        new Cell(EMPTY)
  class Cell
    constructor: (@marker) ->

Adding a factory

We can then define a factory that just returns the Board class, allowing it to be injected into our controller.

angular.module("ngOughts").factory "Board", ->
  Board

It’s possible to define the Board directly inside the factory function, or even put the Board on the window object, but keeping it distinct here allows us to test Board in isolation from AngularJS and encourages re-usability.

So now we have an empty board. Exciting stuff, right? Let’s set things up so that clicking a cell
places a marker there.

<table>
  <tr ng-repeat="row in board.grid">
    <td ng-repeat="cell in row" ng-click="board.playCell(cell)">
      {{ cell.marker }}
    </td>
  </tr>
</table>

We’ve added an ng-click directive to each of our <td> elements. When the table cell is clicked, we’ll invoke the playCell function on the board with the clicked cell object. Filling in the Board implementation:

class Board
  SIZE = 3
  EMPTY  = ' '
  NOUGHT = 'O'
  CROSS  = 'X'
  PLAYER_MARKERS = [NOUGHT, CROSS]
  constructor: ->
    @reset()
  reset: ->
    @current_player = 0
    @grid = [1..SIZE].map ->
      [1..SIZE].map ->
        new Cell(EMPTY)
  playCell: (cell) ->
    return if cell.hasBeenPlayed()
    cell.mark(@currentPlayerMarker())
    @switchPlayer()
  currentPlayerMarker: ->
    PLAYER_MARKERS[@current_player]
  switchPlayer: ->
    @current_player ^= 1
  class Cell
    constructor: (@marker) ->
    mark: (@marker) ->
    hasBeenPlayed: ->
      @marker != EMPTY

Two way data binding

Okay, so now that we’ve updated the board model, we need to go back and update the view, right?

Nope! Angular data binding is two way – it observes changes to models and propagates them back to the view. Similarly, updating the view will update the corresponding models. Our marker will be updated in our Board internal grid and the content of the <td> will immediately change to reflect that.

This cuts out so much of that brittle, selector reliant boilerplate code you previously needed to write. You can focus on your app logic and behavior, not the plumbing.

It would be nice if we knew when someone won though. Let’s implement that. We’ll omit the code for checking win conditions here, but it’s present in the final code. Let’s say when we find a win, we set the winning property on each of the cells that comprised it.

We could then alter our <td> to something like the following:

<td ng-repeat="cell in row" ng-click="board.playCell(cell)" ng-class="{'winning': cell.winning}">
  {{ cell.marker }}
</td>
.winning {
  background: green;
  color: white;
}

If winning is true, ng-class will apply the ‘winning’ CSS class to the <td>, letting us set a pleasant green background to honor our victory. Rematch you say? We’ll need a board reset button:

<button ng-click="board.reset()">reset board</button>

Adding this within our controller, we’ll call reset upon clicking the button. The board markers will be wiped, all CSS classes cleared and we’re ready to go again – with zero updating of DOM elements required by us.

Let’s really gloat over our victory:

<h1 ng-show="board.won">{{ board.winning_marker }} won the game!</h1>

The ng-show directive allows us to conditionally show the <h1> element when the game has been won and data binding lets us interpolate the winner’s marker. Simple and expressive.

More composable, testable app

It’s interesting to note that most of our code has dealt with plain old JavaScript. That’s intentional – no extending framework objects, just writing and invoking JS. This approach lends itself to more composable, testable applications that feel light weight. Our design concerns are separated by MVC, but we don’t need to write a stack of code just to hook things together.

AngularJS isn’t without limits though. Many complain about the official documentation and the relatively steep learning curve, some have SEO concerns and others just get grossed out by the use of non-standard HTML attributes and elements.

There are solutions to these problems though, and the unique approach of AngularJS to web development is one definitely worth taking some time to explore.

You can see the final code in action on Plunkr or download it from GitHub.

Comments on this article are closed. Have a question about AngularJS? Why not ask it on our forums?

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Adrian

    So basically what xforms was meant to achieve but not necessarily a form..

  • http://www.brettwidmann.com Brett Widmann

    Great article, and great timing, as I’ve been working with AngularJS for a few months now. It’s good to see it’s getting even more exposure. What a wonderful piece of technology!

  • Dave

    Good tutorial – it might help the coffescript newbies if you had an equivalent post that uses only javascript – save us old dogs having to learn two things at once :-)

  • Chad Garrett

    That’s Tic-Tac-Toe for us in the United States. Most here in the U.S. have never heard the name Noughts and Crosses. Partly because we hardly use nought as a word at all, and don’t use cross to refer to an X shape either.

  • Rich Lovelock

    Interesting article but concur with Dave, you’re doing an article on AngularJS not CoffeeScript, would have been much clearer done purely with JS if you’re trying to teach about AngularJS.

  • Hostels

    The Coffeescript makes the tutorial unreadable for the people who don’t know Coffeescript.