Software in context

Tags


A Conceptual Introduction to AngularJS

21st August 2014

AngularJS Logo

There are many AngularJS introductions out there that start in the wrong place, or with the wrong level of detail. Because Angular is both massive and opinionated, these can leave one feeling like they've discovered only a small section of an elephant. My goal here is to introduce this juggernaut of a framework in such a way that you grok both the whole elephant and its philosophical basis. As you start to use AngularJS in practice, the details should only enhance the picture. We'll cover:

  1. Why Declarative Programming should be used for UIs
  2. How AngularJS is built to enable Declarative Programming
  3. A sample app that demonstrates core Angular functionality

Let's get started.

Have you heard of Declarative Programming?

It's when the solution to a coding problem is described in terms of the end result, as opposed to the steps required to get there. A coworker defines it as "write data instead of code". Instead of coding what should happen, declarative programming lets us describe what we want. Functional Programming is a canonical application of DP. Compare the following styles of a factorial number generator:

Procedural (Java)

int factorial(int n) {  
  int result = 1;
  while (n > 0) {
    result *= n--;
  }
  return result;
}

Functional (Caml)

let rec factorial = function  
  | 0 -> 1
  | n -> n * factorial(n - 1);;

Note that the functional version has no ordered statements; the solution is simply declared as a set of cases. But the procedural version causes us to think in terms of time, as reinforced by the while construct.

Similarly, many database query languages like SQL follow a declarative pattern. Here's an example of a Cypher query that retrieves a person and their female friends from a social graph:

MATCH (p:PERSON {name: 'Caleb'})-[FRIEND]-(f:PERSON {gender: 'female'}) RETURN p,f  

Here we've declared the desired result in a compact syntax that's completely decoupled from the process implemented in the actual retrieval algorithm.

Declarative programming is useful because it hides tasks that don't need to be re-written all the time, while surfacing logic that actually matters to an application. In the case of the functional factorial program, the loop is the hidden procedure. In addition to removing the need for a programmer to have to manage while statements all the time, a Caml compiler can become a hardened expert on looping, e.g. by doing tail recursion. Similarly, since our Cypher query is unaware of the search algorithm it kicks off, a programmer can improve or swap out the underlying graph database without breaking everyone's queries.

Seen this way, declarative programming is simply an approach to writing more concise code. It's essentially a special case of Don't Repeat Yourself where we avoid re-programming procedural, or imperative, behaviors.

Should we Declare User Interfaces?

The answer is YES. This discussion focuses on the graphical UIs found on the web today, but this truth applies generally. Some support for this claim:

1. UIs aren't Algorithmic

Some problems that computers solve are algorithmic, like rendering an image to a screen, sorting a list of names alphabetically, or modeling an organization's business rules. Procedural languages are good for writing algorithms, because solving these problem efficiently requires clear thinking about the processes they model. In contrast, declarative languages are better for specifying applications. I take the general term application to mean any framework that combines algorithms and puts them to work in practice. User Interfaces are applications by definition; they connect humans with computers, but are primarily behavioral rather than computational.

2. UIs make Messy Procedures

Spaghetti

A lot of stuff happens in UIs. Users click things and enter information and the interface has to react, sometimes waiting for an arbitrary amount of network time. Coding a large UI procedurally would mean building out a giant conditional event tree: "if the user does X, do Z if Y completes successfully, otherwise do W". This easily translates to messy code. When it gets very messy, there are even special culinary names for it. If you've ever tried writing a single page app using just jQuery and JavaScript, you know what I'm talking about.

3. UIs are Everywhere

User interfaces are unique in that they're mediators between biological and digital parties, but they're also ubiquitous. Graphical web UIs, especially modern single page apps, all contain similar elements. To name only a few: navigation and routing, user input forms, interchange of data with a backend API, and presentations of data into sensible views. If we're working consistently in a medium like the web, we shouldn't have to redefine the procedures behind these components all the time. Common elements should be identified, codified and reproducible with as little code as possible.

Today the web is the hotbed of UI programming, and there's no reason these programming concepts shouldn't apply here. We should be declaring web applications, not describing their processes. This is the premise that AngularJS is founded on. Let's take a look at how Angular implements this philosophy.

How does AngularJS enable Declarative Programming?

Before we can answer this question, we have to understand what Angular actually is. We know it's an open-source JavaScript project out of Google. It also makes it easier to develop modern SPAs. But what shape does Angular take when it's actually incarnated on a web page somewhere?

AngularJS is a JavaScript file that does 3 things when loaded on a web page:

  1. Extends HTML to enable dynamic views
  2. Exposes a JavaScript API for specifying Angular apps
  3. Runs a data binding engine on behalf of Angular apps

Let's unpack each of these.

1. Angular Extends HTML

This is by far the most important feature of Angular with respect to declarative programming. It's even how the project describes itself:

"HTML is great for declaring static documents, but it falters when we try to use it for declaring dynamic views in web-applications. AngularJS lets you extend HTML vocabulary for your application." —angularjs.org

Angular's term for these extensions (and Angular has a lot of esoteric terminology) is directive. The mission statement above implies you'll be the one doing the extending. This is true for more sophisticated apps, but Angular also ships with a robust set of directives that cover the bulk of use cases.

So how does a directive work? By way of example, lets build a quick popup dialog. Suppose the following HTML:

<div id='popup' class='hidden'>  
  Dialog box..
  <button id='closeBtn'>
</div>  
...
<button id='openBtn'>  

There's an initially hidden div for a popup box, and two buttons for opening and closing it. With a procedural approach, we might write some JavaScript/jQuery like:

$('#openBtn').click(function(){
  $('#popup').show();
});
$('#closeBtn').click(function(){
  $('#popup').hide();
});

This isn't spaghetti code (yet). But declarative programming claims that we shouldn't have to talk about HTML elements in JavaScript code. UIs should be declared! Let's redo our HTML with two Angular directives, ngShow and ngClick:

<div ng-show='popupVisible'>  
  Dialog box..
  <button ng-click='closePopup()'>
</div>  
...
<button ng-click='showPopup()'>  

Without Angular present on the page, these extra attributes would just be inert markup. But now, they cause our app to behave differently. We're "declaring dynamic views". The JavaScript required would look like:

$scope.showPopup = function() {
  $scope.popupVisible = true;
}
$scope.hidePopup = function() {
  $scope.popupVisible = false;
}

In addition to having written some fancy HTML, we've achieved a nice separation between our application's view and it's state. Our JavaScript is concerned only with modifying this state, in total isolation from the HTML elements on the page. This is a huge step toward proper code. Don't worry, we'll get into the $scope thing soon.

There are directives for just about everything, and as we'll shortly see, you also can write your own. Can you guess what some of these do?

  • ngClass
  • ngHide
  • ngMouseover
  • ngInclude
  • ngRepeat
  • ngSrc

If you want the answers, here's the official list of directives.

Angular also extends HTML by supporting a mustache-like template syntax. So now we can write things like:

<li ng-repeat=”c in contacts”>  
  <h1>{{c.name}}</h1>
  <p>Phone: {{c.phone}}</p>
  <p>Email: {{c.email}}</p>
</li>  

where the variables (e.g. contacts) referred to in the template are $scope properties.

Angular wants your HTML to be littered with double curly braces and ng-this and ng-that. The more complex and sophisticated your app gets, the more declarative your HTML should be. This is key to understanding the framework.

2. Angular Exposes a JavaScript API

It's convenient to think about our extended markup as the front-end of an Angular app, and our angular-specific JavaScript as the back-end. The front-end is purely declarative. With directives, we can use good old HTML to describe not only the structure of our elements, but their behavior. What about the backend?

The second part of Angular is the exposure of a JavaScript API: a set of functions and instantiable objects that can be used to create the logic of an application. Why is this necessary? Well, behind the UI of a typical web application is a layer that is more algorithmic or business-logic heavy. Data viewed and created by a user has to be validated, transformed, and moved around. The state of the application has to be managed. This is the territory of a procedural language like JavaScript, and the layer enabled by the Angular API. The following diagram shows the instantiable objects used for building the back-end of Angular apps:

AngularJS object diagram

This diagram and the one below were heavily inspired by slides from Dan Wahlin's AngularJS Fundamentals in 60-ish Minutes. Let's briefly run through these high-level objects:

  1. Module — The highest-level Angular object. Modules are how Angular packages its code, and how they want you to package yours. An Angular app as specified by the ng-app directive always corresponds to a module. The following objects are always created as children of modules.
  2. Config/Routes — An object that configures an Angular app; basically a container for app setup. Within config it's typical to see routes configured. Routing is a big part of single page apps: because we're managing "pages" in the browser now, we specify a mapping of URLs to states. With Angular this looks like pairing a view with a controller. This should all become clear in the demo app below.
  3. Controller — Probably the most important kind of object in the Angular back-end. A controller manages a special object called a scope that is accessible by the portion of the extended HTML that knows about that controller. The controller is the gateway between the information that the front-end sees, and the logic that creates and works on that information.
  4. Directive — We've already covered directives and seen that Angular ships with a ton of them, but you can and should be writing your own with the directive object. Any time you find yourself repeating chunks of HTML, or referring to DOM nodes in JavaScript code, create a directive to encapsulate your output.
  5. Factory/Service/Provider/Value — UIs deal with data that flows to and from the user. Somewhere in their internals, web apps have to do the nitty gritty of validating, parsing, combining, shipping, receiving, and caching this data. In Angular apps, data processing should happen in your Factories, Services, and Providers. These three are the source of much confusion for new Angular users, but they're not complicated. Each one is an Angular construct that simply wraps a different JavaScript approach to creating objects. Tyler McGinnis has a good comparison of the three. Values belong in the conversation because they too manage with data, but only constants that your app might need to know about.
  6. Filter — Filters let you package the transformation of data in a way that's usable with the | syntax in Angular-flavored HTML. For example, the expression 1408471200898 | date runs a timestamp through the built-in Angular date filter, outputting Aug 19, 2014. Now you can write your own filters.

These are just the core Angular objects, and you'll find them in most apps. But Angular also has a great ecosystem of plugin modules. It's hard to write a good app without using some of these. For example angular-ui-router is a module that can replace the built-in routing framework, and allows you to express complicated UIs as a set of states. There are also a host of utility functions for JavaScript, like angular.forEach, which enables safe, enclosed looping over objects and arrays.

So we've rounded out the picture by talking about the front- and back-ends of an app. But how does our fancy new markup know about the app we've defined? This is achieved by the data-binding that Angular executes as our app runs…

3. Angular Runs a Data-binding Engine

What happens when an app runs? Well, the process is typically kicked off by an ng-app directive that surrounds all the extended markup that represents an app. Angular finds this directive and then runs something called a digest loop on the app's behalf. This loop essentially watches for changes on the $scope objects and applies these changes to any interested party. In the case of the popup example above, the loop will be responsible for informing the ng-show directive of the popup div that the $scope.showPopup property has changed. With this understanding we can model the Angular runtime like this:

AngularJS runtime diagram

The <view> box is the Angular-flavored HTML (the front-end), and the Controller and Factory* represent the back-end. Our $scope lives between the two, and Angular is making sure both ends of the app have the latest version of it.

The digest loop and $scope are how AngularJS achieves two-way data binding, and the result is that its no longer necessary to refer to DOM nodes from JavaScript. Imagine that! You'll never have to write $('#something') again!

A Simple Angular App

We're done with the conceptual and introductory material. Let's put together a small Angular app to substantiate our claims that declarative programming is good for web-app development.

angular-blog-demo

We'll be creating a miniature blog that shows off different kinds of Lipsum. The blog simply has post pages and a home page:

Angular Demo Blog Screenshot

The code is available on GitHub and is set up to run as a node+express server. The important parts are found in the /public folder, which can be served statically:

/public
|
|---- index.html
|
|----/partials
|    |
|    |---- home.html
|    |---- post.html
|
|----/js
|    |
|    |---- angular-blog-demo.js
|    |---- angular.min.js
|    |---- angular-route.min.js
|
|----/css
|    |
|    |---- bootstrap.min.css
|
|----/api
     |
     |---- get_blog_info.json
     |---- get_posts.json

Twitter Bootstrap 3 is used to add some basic styling. The /api folder holds some static JSON files that look just like dynamic HTTP endpoints to our Angular app:

get_blog_info.json

{
  "title": "Angular Demo Blog",
  "description": "Created this morning."
}

get_posts.json

{
  "posts": [
    {
      "title": "Web 2.0 Ipsum",
      "publish_date": 1408471200898,
      "author": "Caleb",
      "id": 1,
      "content": "Webtwo ipsum dolor sit amet..."
    },
    {
      "title": "Hipster Ipsum",
      "publish_date": 1408385101999,
      "author": "Caleb",
      "id": 2,
      "content": "Wes Anderson umami biodiesel YOLO..."
    },
    {
      "title": "Samuel L. Ipsum",
      "publish_date": 1408298715678,
      "author": "Caleb",
      "id": 3,
      "content": "The path of the righteous man..."
    }
  ]
}

Let's look at the four angular-specific files one by one:

1. index.html

The Shell Page

<!DOCTYPE html>  
<html>  
  <head>
    <meta charset="utf-8">
    <title>Angular Blog Demo</title>
    <link rel="stylesheet" href="/css/bootstrap.min.css">
    <script src="/js/angular.min.js"></script>
    <script src="/js/angular-route.min.js"></script>
    <script src="/js/angular-blog-demo.js"></script>
  </head>
  <body ng-app="blogDemoApp">
    <div class="container">
      <ng-view>
    </div>
  </body>
</html>  

This is the single page app "shell page", Angular-style. We pull in our dependencies and then our application JavaScript. Note that angular-route exists as its own module that has to be loaded separately. Two directives are used here:

  1. ng-app tells Angular that we want a module called blogDemoApp to own the enclosed HTML. Without this, no live Angular app will exist on the page.
  2. ng-view is used in conjunction with angular-route. It's basically a placeholder for the rendered partial html file associated with the current URL (as defined in our routes).

2. home.html

The Home Page

<div class="row">  
  <div class="col-lg-12">
    <h1>{{title}} <small>{{description}}</small></h1>
    <hr>
  </div>
</div>  
<div ng-repeat="post in latest_posts" class="row">  
  <div class="col-lg-8">
    <a href="#/post/{{post.id}}"><h2>{{post.title}}</h2></a>
    <div><h3><small> {{post.publish_date | date}}</small></h3></div>
    <p>{{post.content}}</p>
  </div>
</div>  

This is an HTML "partial" for the blog home page. The Angular routing system, as we'll see below, knows that when we're looking at #/ it should download this file and render it into the <ng-view> area. Here we're using some rudimentary template syntax, the ngRepeat directive for listing out our posts, and that date filter mentioned above.

3. post.html

The Post Page

<div class="row">  
  <div class="col-lg-12">
    <h1>{{post.title}}</h1>
    <h3>by {{post.author}}</h3>
    <div>Posted on: {{post.publish_date | date}}</div>
  </div>
</div>  
<hr>  
<div class="row">  
  <div class="col-lg-12">
    <p>{{post.content}}</p>
  </div>
</div>  

Just like the home partial, except this view is only rendered into <ng-view> when we're looking at #/post/:id.

4. angular-blog-demo.js

This is where the application back-end is defined. This file is a bit longer, so we'll break it up into bite-sized chunks.

4.1 Define the App Module and its Dependencies

var angularBlogDemo = angular.module('blogDemoApp', ['ngRoute']);  

4.2 Define the App Config and Routes

angularBlogDemo.config(['$routeProvider', function($routeProvider) {

  $routeProvider
    .when('/', {
      templateUrl: '/partials/home.html',
      controller: 'HomeController'
    })
    .when('/post/:id', {
      controller: 'PostController',
      templateUrl: '/partials/post.html'
    })
    .otherwise({
      redirectTo: '/'
    });

}]);

Here's where the routing we've been alluding to is specified. In addition to URLs being paired with views (HTML partial files), a matching controller is also specified. This is where the front- and back-ends of the Angular app are married.

4.3 Define the Home Page Controller

angularBlogDemo.controller('HomeController', ['$scope', 'BlogInfoService', 'PostService', function($scope, BlogInfoService, PostService) {

  BlogInfoService.getBlogInfo()
    .then(function(blogInfo) {
      $scope.title = blogInfo.title;
      $scope.description = blogInfo.description;
    });

  PostService.getLatestPosts()
    .then(function(posts) {
      $scope.latest_posts = posts;
    });

}]);

By now you may be wondering about the weird array syntax. Why do we have to list out the object's dependencies twice? once as strings and another time as JavaScript names? Well, Angular loves dependency injection. It can even guess based on the names of the arguments to the controller function which objects and services we're talking about ($scope, BlogInfoService, etc.) and inject these for use when the code runs. We won't get into the myriad benefits of DI here, but unless we also list these out as strings, minifying this code will cause Angular's DI to break bceause minification changes the argument names.

Anyway, this is the controller that manages the $scope available to the home.html view. We make two service calls, one to get the blog info, and another to get the latest posts. When these calls are complete, the $scope is updated with the new information, which will get published post-haste to the appropriate view by the data binding engine. Simple, right?

Define the Post Page Controller

angularBlogDemo.controller('PostController', ['$scope', '$routeParams', 'PostService', function($scope, $routeParams, PostService) {

  $scope.post_id = $routeParams.id;

  PostService.getPostById($scope.post_id)
    .then(function(post) {
      $scope.post = post;
    });
}]);

In this controller we grab the post ID from the URL using the $routeParams service, and use it to lookup a post from the post service. We set the scope when its all done.

4.4 Define a BlogInfo Service

angularBlogDemo.factory('BlogInfoService', ['$http', '$q', function($http, $q) {

  return {
    getBlogInfo: function() {
      return $http.get('/api/get_blog_info.json')
        .then(function(response) {
          if (response.data) {
            return response.data;
          } else {
            return $q.reject('No data in response.');
          }
        }, function(response) {
          return $q.reject('Server or connection error.');
        });
    }
  };

}]);

We define an AngularJS factory called BlogInfoService. I know the naming seems a bit off, why call it a service if its a factory? Remember that Anuglar Factories, Services, and Providers are just names for different JavaScript styles of creating objects. In the Factory case, Angular calls the function we pass it once, and assigns the result to the the BlogInfoService name. I chose this style due to personal preference, and named it a "service" because that's what it is to my controller.

The getBlogInfo method uses the built-in $http service to perform a GET request for the JSON (which happens to be a static file). If you were wondering, all this .then, $q.reject, and $q.resolve business has to do with promises. We won't get into them here, but promises are a simpler way of expressing a result that is available only asynchronously. Instead of having success and failure callbacks everywhere, we can pass a promise object that will eventually be resolved or rejected. If you want to go deeper with promises, they are explained as a cartoon by Andy Shora.

When our blog info data comes back successfully, we resolve the promise that's already been returned to the caller.

4.5 Define a PostService Service

angularBlogDemo.factory('PostService', ['$http', '$q', function($http, $q) {

  // post data structures
  var post_list = [],
      posts_by_id = {};

  function loadPosts(posts) {
    post_list = [];
    angular.forEach(posts, function(post) {
      post_list.push(post);
      if (typeof post.id !== 'undefined') {
        posts_by_id[post.id] = post;
      }
    });
  }

  function fetchLatestPosts() {
    return $http.get('/api/get_posts.json')
      .then(function(response) {
        if (response.data && response.data.posts) {
          loadPosts(response.data.posts);
          return response.data.posts;
        } else {
          return $q.reject('No data in response.');
        }
      }, function(response) {
        return $q.reject('Server or connection error.');
      });
  }

  return {

    getPostById: function(id) {
      var deferred = $q.defer();

      if (posts_by_id.hasOwnProperty(id)) {
        deferred.resolve(posts_by_id[id]);
      } else {
        fetchLatestPosts()
          .then(function(posts){
            deferred.resolve(posts_by_id[id]);
          }, function(msg) {
            deferred.reject(msg);
          });
      }
      return deferred.promise;
    },

    getLatestPosts: function() {
      var deferred = $q.defer();

      if (post_list.length) {
        deferred.resolve(post_list);
      } else {
        fetchLatestPosts()
          .then(function(posts){
            deferred.resolve(posts);
          }, function(msg) {
            deferred.reject(msg);
          });
      }
      return deferred.promise;
    }
  };

}]);

The PostService is much like the BlogInfoService, except that we're caching the blog posts we get from the JSON response in local data structures, instead of having to make a network call every time someone navigates to a new page in the blog. Notice we have some code defined before the return statement. This is one of the ways to achieve "private" object memebers with JavaScript. Everything defined in the returned object is exposed for calls made on PostService, but the other stuff in our factory function is not visible to the outside world; the two post containers, and the private functions loadPosts, and fetchLatestPosts are only accessible from within the outer function's scope.

Don't be Scared!

Thanks! Like I said at the beginning, there is a lot of Angular material out there on the web. Hopefully more of it will make sense to you now.

I have one final point. A common objection to AngularJS is that its too different. Here are some complaints I've heard:

  1. There are too many new terms
  2. There's sooo much documentation
  3. It feels like I'm writing Java
  4. It wants me to do everything "The Angular Way"

These are all true. But hopefully with a conceptual introduction to this unstoppable framework, you can start to see these points as indicators of a healthy revolution, instead of as drawbacks. AngularJS is bringing some Software Engineering rigor to the creation of web apps that has never existed on the front-end. It's taking people by surprise. But it's also enabling them to rapidly create clean single page apps, and helping us cook meat and veggies instead of spaghetti code.

Related posts:

Caleb Sotelo
AUTHOR

Caleb Sotelo

I'm a Software Engineer and Director of OpenX Labs. I try to write about software in a way that helps people truly understand it. Follow me @calebds.

View Comments