Client-Side Applications with AngularJS
One of the core concepts in AngularJS is two-way data binding. This means that manipulating a variable controlled by Angular—for example, in a text box—will automatically update any other places that variable is bound. Here’s a very basic example: a text box that updates a value in the DOM as you type.
A full exploration of the Angular framework is beyond the scope of this post, so we’ll stick to the features I used in rebuilding the app.
In Angular, services are where you put your business logic; you can liken them to models in a server-side MVC framework like Ruby on Rails, but are more generic. In our case, we’ve created two new services.
Realms, which depends on the built-in
$http service (Angular’s built-in services begin with a dollar sign), simply defines a function that takes a callback, makes the JSONP request to the Blizzard API, and then calls the callback with the resulting data once complete. This replaces the
RealmList collection from the Backbone app, and the
hashChange simply watches for the URL’s hash to change and calls a provided function with its new value whenever it does. The call to the function is wrapped in
$rootScope.$apply, which ensures that the function runs within the context of Angular’s dirty tracking. This is only necessary when dealing with asynchronous code that isn’t managed by AngularJS by default—for example, when dealing with native browser events or third-party jQuery plugins. This will replace the routing features from the Backbone app (AngularJS has a full-featured router, but we don’t use it for this app.)
Once registered, both services are available to other services and controllers in our application by name; we’ll see this in a moment when we define our controller.
Controllers are the glue between your views and your models. User events on the DOM will generally trigger functions on a controller, which will then in turn manipulate some model. The change in the model will automatically be propagated back into the view via Angular’s two-way data binding.
I only defined one controller for this application; though the terminology is a little different, this will basically replace all three of the views we created in the Backbone application. The controller uses various dependencies, injected in at runtime, to manage the interaction between the user and the models. You can see the list of dependencies in the controller’s function definition:
app.controller 'RealmsController', ($scope, $timeout, $window, Realms, hashChange) ->
hashChange ourselves in the previous section; the other three variables, which start with a dollar sign, are provided to us by the framework:
$scopeis an object that is shared between the controller and the view; any property attached to the scope is automatically available in the view.
$scopeis where we put all our models, so that they can be shown in the view. Every controller gets its own
setTimeoutthat ensures that Angular’s dirty-checking is triggered in the asynchronous function, similar to the
$rootScope.$applytrick we used in the
windowobject, provided mostly for its ability to be easily mocked out in tests.
The controller itself sets up four values on the
realmsis an array of servers that we’ll eventually get from the JSONP API, but for now we initialize it as an empty array.
searchis the current search term, initialized to an empty string.
Datefor the last time we got fresh data from the API; here we initialize it to null.
updateHashis a function that, when called, will take the current value of the
searchscope variable and make sure the current URL shows it in its hash. This is defined on the scope because we will be accessing it from the view later.
We then use our
hashChange service to make sure that our
search scope variable changes automatically when the URL’s hash changes. Finally, we create a local function called
refresh that fetches new data from the API and schedules another update in five minutes. The refresh function uses the
Realms service to fetch the data and set all the relevant data in its callback.
We then kick everything off by calling
refresh once manually.
In Angular, your views are described declaratively with HTML. This is perhaps the biggest difference between it and a framework like Backbone. The view not only lays out the template, but also declares the bindings between the HTML and the controller. Here’s our full application view, defined in one file.
Let’s break this down piece by piece.
Angular views are described using directives, which are usually manifested as special HTML attributes or elements. For example, the
ng-app attribute on the
body tag tells Angular to kick off the
wowRealmStatus module on that section of the DOM; the
ng-controller directive on the first
div tag tells Angular to load up the
RealmsController for that particular part of the page. Angular has several useful built-in directives; we’ll cover only the ones that drive out the functionality of our application here.
First, notice that our search
input has an
ng-model directive on it. This tells Angular to bind the value of the text input to the
search property on the view’s associated
$scope (which, you may recall, is shared by the
RealmsController we defined earlier). This means that every time the user types in the box, the
$scope.search property in the controller is automatically updated; conversely, when
$scope.search is updated in the controller, the value shown in the text box will automatically update.
There is also an
ng-change attribute on the input; this tells Angular to call the given function every time the value in the input changes. In this case, it’s calling our
updateHash function to ensure the URL is correct.
The image shown next to the input has an
ng-show attribute on it; this ensures that the image is only visible if the expression passed to the attribute is true. In this case, we only show the loading spinner if the
loading scope value is truthy, which we manage in our
The paragraph containing the last updated time is interesting; it shows one of two spans depending on if the
lastUpdate scope value is truthy or not; if it is, we use Angular’s curly-brace-based string interpolation to show the date. The pipe
| character invokes a filter, and it works just like pipes do on UNIX-like systems—the value
lastUpdate is passed to a function called
date; the string
'MMM d, h:mm a' is passed as the second parameter to this function. In this case, we’re using the date filter, and it returns the given date formatted by the specified string. Filters allow you to keep all your formatting-related logic in the view, where it’s easy to see what’s happening and compose multiple filters together in interesting ways.
We then have a paragraph that is only shown if
search is truthy (e.g., not an empty string). If it is, we see a link with the text “Show All” that uses the URL’s hash fragment to reset the search term to empty (remember our
Finally, we get to the really interesting part. The final
div tag has an attribute called
ng-repeat. This causes the DOM element to be repeated for every element in an array or object. In this case, for every element in the
realms scope property, we’re duplicating the
div and assigning a new local scope variable called
realm to the array value at the current iteration. Inside the
div, we’re using curly-braces and filters (described in the next section) to show various pieces of information about each realm.
ng-repeat expression itself is being piped into a filter: the
filter filter. In this case, we’re passing as a second argument an object literal, that says to filter out from the
realms array any value that has a
name property that doesn’t matche the current value of the
search scope variable. In this way, we get search functionality for free!
We mentioned filters briefly in the last section, when we said that filters are special functions you can invoke in your views via the pipe
| operator, and that they look and behave much like you’d expect based on their behavior on UNIX-like systems. Here are the filters I defined for the app.
The comments on each filter describe what it does; notice that they only return new values, not manipulate existing data. Filters allow you to transform data in your view so that you don’t have to worry about data formatting in your controllers or services, and make it easy to see how your data is transformed at a glance in your views.
As you can see, the code for the Angular version of this app is quite short. The functionality is nicely encapsulated, and Angular’s data binding saves us a lot of boilerplate code that we’d normally have to write for view updates. You can check out the code for this project on GitHub, and the working implementation is up on GitHub pages.
If you’re interested in a more advanced example, take a look at MovieKue, which behaves more like a traditional single-page application. It’s still under development, but it’s feature-complete enough to garner useful information about writing a larger AngularJS app. (It also uses Firebase, a cool real-time database-in-the-cloud, which I recommend checking out.)
Overall, this was a whirlwind tour of the AngularJS framework. Be sure to check it out if you’re interested. I hope you found this example and comparison to the Backbone version of the app useful. Please let me know in the comments or via email if you have any questions!