Skip to main content

Contact Manager – written in AngularJS, Express and MongoDB – Episode 2

6 min read

Older Article

This article was published 13 years ago. Some information may be outdated or no longer applicable.

In the previous post I walked through creating a RESTful API using Express and MongoDB. Now it’s time to build a frontend that connects to that API, pulls data, and displays it. I picked AngularJS because I wanted to push further into this technology after my earlier post about it.

If you read the previous article, you’ll remember the RESTful API lives on port 1222. Our frontend application will communicate on port 1223. This setup works fine in a test environment where everything runs on the same box. But running on different ports, while it sounds simple, isn’t as easy as it first appears. I’ll come back to that.

To kick things off, we need something to serve the content of our .html file(s). Express works well here too.

Create a file called server.js and drop in the following:

var express = require('express');
var app = express();
app.configure(function () {
  app.use(express.static(__dirname + '/'));
});
app.listen(1223, 'host');
console.log('Server is up and listening on port 1223');

If you create an index.html file with whatever content and place it in the same working directory, then run node server.js and point your browser to [http://host:1223](http://host:1223), you should see your HTML page rendered. That’s it. Server’s up and running. For more information check the official Express guide.

(I’m aware you can auto-generate a folder structure with Express using the express [opts] [app-name] syntax, but for simplicity I’d rather keep my folder/file structure clean.)

Let’s get some actual data flowing. Make sure the API from the previous post is running (node index.js, accessible at [http://host:1222](http://host:1222)). As with all my projects, I’m using Twitter Bootstrap. Here’s a basic framework for the app with a table to display contacts. (Make sure to download the appropriate .js and .css files and/or link to them via Google CDN.)

<!DOCTYPE html>
<html>
  <head>
    <title>Contact manager</title>
    <link href="bootstrap.css" rel="stylesheet" media="screen" />
    <script src="angular.min.js"></script>
  </head>
  <body>
    <h1>Contact manager</h1>
    <div>
      <table class="table table-hover">
        <thead>
          <tr>
            <th>Name</th>
            <th>Phone</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>Contact 1</td>
            <td>1234567</td>
          </tr>
          <tr>
            <td>Contact 2</td>
            <td>123999000</td>
          </tr>
        </tbody>
      </table>
    </div>
    <script src="jquery.js"></script>
    <script src="bootstrap.js"></script>
  </body>
</html>

(You can drop any of the .js/.css files other than angular.js if you don’t need them.)

This should result in the following:

You’ll notice it’s entirely static. Let’s wire in some AngularJS. First, we need to declare our controller and scope. The controller responsible for listing contacts is (cleverly) called ListController. For demonstration purposes I’ll manually create a JSON object with contact names and phone numbers first.

I won’t go into the details of declaring the scope of an AngularJS application here, but feel free to read the official documentation or my previous post on this topic.

A few things need changing in the HTML skeleton above. I want my headers and data to populate automatically. To get there, we need a JavaScript file. For simplicity I’m using inline scripts:

angular.module('contactmanager', []);
function ListController($scope) {
  $scope.headers = ['name', 'phone'];
  $scope.contacts = [
    { name: 'Tamas Piros', phone: '123456' },
    { name: 'Uncle Joe', phone: '088882' },
  ];
}

And modify the HTML to look like this:

<!DOCTYPE html>
<html ng-app="contactmanager">
  <head>
    <title>Contact manager</title>
    <link href="bootstrap.css" rel="stylesheet" media="screen" />
    <script src="angular.min.js"></script>
    <script>
      <!-- Insert the JavaScript from above here -->
    </script>
  </head>
  <body ng-controller="ListController">
    <h1>Contact manager</h1>
    <div>
      <table class="table table-hover">
        <thead>
          <tr>
            <th ng-repeat="header in headers">{{ header }}</th>
          </tr>
        </thead>
        <tbody>
          <tr ng-repeat="contact in contacts">
            <td>{{ contact.name }}</td>
            <td>{{ contact.phone }}</td>
          </tr>
        </tbody>
      </table>
    </div>
    <script src="jquery.js"></script>
    <script src="bootstrap.js"></script>
  </body>
</html>

We register the contactmanager module and create the ListController controller that adds headers and contacts to our application. Then, in the HTML table, we iterate through these JSON objects and print out the necessary information. Great, but it’s still static. Time to replace the contacts JSON object with real data from our Mongo database.

There are two ways to pull this off. Remember, I’m running the backend and frontend on the same host but on different ports. That means I can’t make simple “GET” requests to the API because they’ll fail with an “Access-Control-Allow-Origin” error. (For more on this, read this very interesting article.) To get around this issue in a single-server environment, we need JSONP. Instead of making a GET request that returns a JSON object, we make a JSONP call that returns a JSONP object.

AngularJS can make HTTP calls via its built-in $http service. We need to rework our JavaScript slightly:

function ListController($scope, $http) {
  $scope.headers = ['name', 'phone'];
  $http({
    method: 'jsonp',
    url: 'http://host:1222/contacts?callback=JSON_CALLBACK',
  })
    .success(function (data, status, headers, config) {
      $scope.contacts = data;
    })
    .error(function (data, status, headers, config) {
      //handle error
    });
}

We also need to modify the index method from part one to return a JSONP object instead of a JSON object:

exports.index = function (req, res) {
  return ContactModel.find(function (err, contacts) {
    if (!err) {
      res.jsonp(contacts);
    } else {
      console.log(err);
    }
  });
};

(Update all functions to return jsonp.)

There you go. Refreshing the frontend interface (http://host:1223/) should list all the contacts stored in the Mongo database.

Let’s bolt on a few more features. No contact manager should exist without search and filtering. Adding that via AngularJS is relatively easy, but I also want some nice-to-haves: column sorting and capitalised table headers. We need to modify ListController further:

angular.module('filters', []).filter('capitalise', function () {
  return function (input) {
    if (input != null)
      return input.substring(0, 1).toUpperCase() + input.substring(1);
  };
});

angular.module('contactmanager', ['filters']);

To capitalise the table headers, I created a custom filter. It’s crucial to register this filter when declaring contactmanager. You might wonder why I’m writing a function to capitalise headers rather than just creating $scope.headers with capitalised names. It’s simple: capitalised values in the JSON object would mean my database fields would need capitalised names too. I don’t like that idea. That’s one thing sorted. Let’s wire up the table heading sorting, capitalisation, and querying as well. Update the table in your index.html:

<ul class="inline">
  <li>You have {{contacts.length}} contacts.</li>
  <li>
    <div class="input-prepend">
      <span class="add-on"><i class="icon-search"></i></span>
      <input
        class="span2"
        id="inputIcon"
        type="text"
        placeholder="Search..."
        ng-model="query"
      />
    </div>
  </li>
  <li>
    <p>Sorted by: {{columnSort.sortColumn}}</p>
  </li>
</ul>
<table class="table table-hover">
  <thead>
    <tr>
      <th ng-repeat="header in headers">
        <a
          ng-click="columnSort.sortColumn=headers[$index];columnSort.reverse=!columnSort.reverse"
          >{{ headers[$index] | capitalise }}</a
        >
      </th>
    </tr>
  </thead>
  <tbody>
    <tr
      ng-repeat="contact in contacts | orderBy:columnSort.sortColumn:columnSort.reverse | filter: query"
    >
      <td>{{ contact.name }}</td>
      <td>{{ contact.phone }}</td>
    </tr>
  </tbody>
</table>

The final result should look similar to this:

I’d originally planned two articles, but I’ll need to write one more to cover the routing from AngularJS and calling the update/delete functions. Stay tuned.