AngularJS — Build an App Using Directives and Data Binding

Share this article

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?

Frequently Asked Questions about AngularJS Directives and Data Binding

What is the difference between AngularJS directives and components?

AngularJS directives and components are both powerful features of the AngularJS framework. Directives allow you to create reusable custom HTML elements and attributes, while components are a special kind of directive that uses a simpler configuration. Components are suitable for building a component-based application structure, which is more modern and widely used in front-end development today. However, directives are more flexible and can manipulate the DOM directly, which is not possible with components.

How does data binding work in AngularJS?

Data binding in AngularJS is the automatic synchronization of data between the model and view components. The way that AngularJS implements data binding lets you treat the model as the single-source-of-truth in your application. The view is a projection of the model at all times. When the model changes, the view reflects the change, and vice versa.

Can you explain the difference between one-way and two-way data binding?

One-way data binding is a simple flow of data from the model to the view or vice versa. It means that if the model state changes, the view will not update. Two-way data binding, on the other hand, means that if the model state changes, the view will update and if the view changes (due to user interaction, for example), the model state will update. This allows for a real-time effect, meaning that changes (like user input) will be immediately reflected in the model state.

How can I create a custom directive in AngularJS?

To create a custom directive in AngularJS, you need to use the .directive function. You can name your directive and then create a factory function which will hold all the directive options. The factory function is injected with dependencies (if any), and then it returns an object which defines the directive options.

What are isolated scopes in AngularJS directives?

Isolated scopes in AngularJS directives allow you to create a new scope for your directive. This means that any changes in your directive’s scope won’t affect the parent scope, and vice versa. This is useful when you want to create reusable and modular components.

How can I use transclusion in AngularJS directives?

Transclusion is a feature in AngularJS that allows you to insert custom content inside a directive. By setting the transclude option to true in your directive definition object, you can then use the ng-transclude directive in your directive’s template to insert the custom content.

What is the link function in AngularJS directives?

The link function is used in AngularJS directives to manipulate the DOM or to add event listeners. It’s executed after the template has been cloned. This function is often used for tasks such as setting up DOM event handlers, watching model properties for changes, and updating the DOM.

How can I use the controller function in AngularJS directives?

The controller function is a JavaScript constructor function that is used to augment the AngularJS scope. When a controller is attached to the DOM via the ng-controller directive, AngularJS will instantiate a new controller object, using the specified controller’s constructor function. A new child scope will be created and made available as an injectable parameter to the controller’s constructor function as $scope.

What is the difference between ‘@’, ‘=’, ‘&’, in the scope option of directives?

These symbols are used to bind data to directive scope. ‘@’ is used for string binding, ‘=’ is used for two-way model binding, and ‘&’ is used for method binding. They allow you to isolate the scope, making your directives more modular and reusable.

How can I test my AngularJS directives?

AngularJS provides a module called ngMock, which allows you to inject and mock AngularJS services in unit tests. You can use it to test your directives. You can also use other testing frameworks like Jasmine or Mocha in combination with ngMock to write and run your tests.

Alex SmithAlex Smith
View Author

Alex is a developer for the SitePoint group.

angularAngular Tutorialsdata bindingdirectives
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week
Loading form