AngularJS — 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?