Wednesday, 29 April, 2015 UTC


Summary

I know, I know it is too early to be building end to end apps with Angular 2.0, but I got so excited after looking at ng2-express-starter by my dear friend Antonio Fernandes. I was like, If we can integrate an Angular 2.0 app with an Express app, we can definitely add the MongoDB layer to make it a M.E.A.N. app. So, in this post, we will be developing a MEAN Todo App (Grrr..). This post is heavily inspired by Antonio’s Angular 2.0 Express starter app.
The final app would look like
You can play with a live instance of the above app here. (It might take upto a minute to load the page, please be patient or open the dev tools – network tab to check progress).
You can find the completed code here.
Couple of things before we proceed
  1. Thumbs up to Tim Jacobi for creating angular2-education repo, which consists of lot of useful links to learn Angular 2.0
  2. Angular 2.0 is still in development. The todo app we are going to build is developed using Angular 2.0 2.0.0-alpha. There is a very good chance that most or all of this code can be of no help when Angular 2.0 becomes stable.
Prerequisites
If you are new to Angular 2.0 check out
  1. Getting Started with Angular 2.0
  2. Tim Jacobi’s angular2-education repo
If you are new to MEAN Stack development check out
  1. MEAN Stack – A Hands on Tutorial (with Angular 1.x)
  2. MEAN Stack Workshop
Architecture
A MEAN stack app would typically consist of a server layer built with Nodejs & Express – Express being the web framework. MongoDB for data persistence & Angular as the client side MVW framework.
As you can see from the above diagram, Express exposes REST End points that would be consumed by the Angular 2.0 app. And when any request is made to the REST end point, Express contacts MongoDB and manages the data.
This is pretty much our architecture.
Note : If you are looking for creating a secure REST API with Nodejs checkout : Architecting a Secure RESTful Node.js App.
Setting up the Server
First, we are going to setup an express app, build the REST API to Create, Read, Update & Delete todos and then integrate it with the Angular app.
Create a folder named ng2do-mean-app. Open a terminal/prompt here and run
npm init
Fill up the details as applicable.
Next, we will add express dependencies to our project. Run
npm install --save body-parser cookie-parser ejs express mongojs morgan path
This will download the required dependencies to run the express app. Once the above installation is done, your package.json should look like
{
  "name": "ng2do-mean-app",
  "version": "0.1.0",
  "description": "Developing a MEAN app with Angular 2.0",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Arvind Ravulvaru",
  "license": "MIT",
  "dependencies": {
    "body-parser": "^1.12.3",
    "cookie-parser": "^1.3.4",
    "ejs": "^2.3.1",
    "express": "^4.12.3",
    "mongojs": "^0.18.2",
    "morgan": "^1.5.2",
    "path": "^0.11.14"
  }
}
At the root of the ng2do-mean-app create a file named server.js. Update it as below
var express = require('express');
var path = require('path');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var index = require('./routes/index');
var todos = require('./routes/todos');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.engine('html', require('ejs').renderFile);
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
    extended: false
}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', index);
app.use('/api/v1/', todos);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

var server = app.listen(3000, function() {
    var host = 'localhost';
    var port = server.address().port;
    console.log('App listening at http://%s:%s', host, port);
});

module.exports = app;
This is a typical express server. Couple of things, on line 7, 8 we are including two sets of routes and on line 24, 25 we are mapping the routes to end points.
Next, we are going to create the routes folder and the two route files we declared above.
Create a new folder named routes at the root of ng2do-mean-app folder. Create a file named index.js inside the routes folder. And update index.js as below
var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
    res.render('index.html');
});

module.exports = router;
We define a route to handle the
http://localhost:3000/
  and dispatch the index.html file.
Next, at the same level as index.js, create another file named todos.js. Update todos.js as below
var express = require('express');
var router = express.Router();
var mongojs = require('mongojs');
var db = mongojs('mongodb://admin:[email protected]:37827/ng2todoapp', ['todos']);

/* GET All Todos */
router.get('/todos', function(req, res, next) {
    db.todos.find(function(err, todos) {
        if (err) {
            res.send(err);
        } else {
            res.json(todos);
        }
    });
});

/* GET One Todo with the provided ID */
router.get('/todo/:id', function(req, res, next) {
    db.todos.findOne({
        _id: mongojs.ObjectId(req.params.id)
    }, function(err, todos) {
        if (err) {
            res.send(err);
        } else {
            res.json(todos);
        }
    });
});

/* POST/SAVE a Todo */
router.post('/todo', function(req, res, next) {
    var todo = req.body;
    if (!todo.text || !(todo.isCompleted + '')) {
        res.status(400);
        res.json({
            "error": "Invalid Data"
        });
    } else {
        db.todos.save(todo, function(err, result) {
            if (err) {
                res.send(err);
            } else {
                res.json(result);
            }
        })
    }
});

/* PUT/UPDATE a Todo */
router.put('/todo/:id', function(req, res, next) {
    var todo = req.body;
    var updObj = {};

    if (todo.isCompleted) {
        updObj.isCompleted = todo.isCompleted;
    }
    if (todo.text) {
        updObj.text = todo.text;
    }

    if (!updObj) {
        res.status(400);
        res.json({
            "error": "Invalid Data"
        });
    } else {
        db.todos.update({
            _id: mongojs.ObjectId(req.params.id)
        }, updObj, {}, function(err, result) {
            if (err) {
                res.send(err);
            } else {
                res.json(result);
            }
        });
    }


});

/* DELETE a Todo */
router.delete('/todo/:id', function(req, res) {
    db.todos.remove({
        _id: mongojs.ObjectId(req.params.id)
    }, '', function(err, result) {
        if (err) {
            res.send(err);
        } else {
            res.json(result);
        }
    });

});

module.exports = router;
Things to notice
Line 4 : I have created a new MongoDB instance for this example from MongoLab and configured it with Mongojs.
Line 7 : Endpoint to get all todos
Line 18 : Endpoint to get one todo based on the id provided
Line 31 : Endpoint to save a todo
Line 50 : Endpoint to update a todo
Line 79 : Endpoint to delete a todo
Test REST API
If you save all the file and run
node server.js
the express server would start on port 3000. If you navigate to 
http://localhost:3000/api/v1/todos
, you should be able to see all the todos (if any & if you have setup everything correctly). You can use Postman or any REST Client to test the remaining methods as well.
Pretty sweet right! Our REST API is completed. Now from our Angular 2.0 app, we are going to connect to the above REST API and manage our UI.
Setup the Client
The first thing we would be doing as part of the client side development is to download the Angular 2.0 starter app. This starter app consists of all the Angular 2.0 code and its dependencies.
From the root of ng2do-mean-app folder, run
git clone https://github.com/angular/quickstart.git public/vendor
As of today, the quickstart project does not ship with Angular 2.0 2.0.0-alpha source, which is needed for our project. To update the same, we will run the following commands from inside the public/vendor folder
npm install
gulp
npm install rx --save
The above steps are NEEDED till the quickstart project ships with Angular 2.0 2.0.0-alpha release.
Next, to manage client side dependencies, we are going to use bower. We will create a new bower.json file. From the project root, run
bower init
This will create a new bower.json file and it should look something like
{
  "name": "ng2do-mean-app",
  "version": "0.1.0",
  "authors": [
    "Arvind Ravulavaru <[email protected]>"
  ],
  "description": "Developing a MEAN app with Angular 2.0",
  "moduleType": [
    "es6"
  ],
  "license": "MIT",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ]
}
Next, create a file at the same level as bower.json named .bowerrc. This file will consists of bower config. Update .bowerrc as below
{
	"directory" : "public/vendor/bower_components"
}
Now, we will add Twitter Bootstrap to our app. Run
bower install bootstrap --save
This should download the Bootstrap source and place it inside the public/vendor/bower_components folder.
If you do remember, in routes/index.js we have a route that dispatches the index.html file. We will create that index.html file now.
Create a new folder named views at the root of our project & inside views create a new file named index.html. Update index.html as below.
<html>

<head>
    <title>M.E.A.N. Todo App with Angular 2.0</title>
    <link rel="stylesheet" href="vendor/bower_components/bootstrap/dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="css/app.css">
    <script src="/vendor/dist/es6-shim.js"></script>
</head>

<body>
    <div class="container">
        <todo-app>
            <h2 class="text-center">Loading A M.E.A.N. Todo App...</h2>
        </todo-app>
    </div>
    <script>
    // Rewrite the paths to load the files
    System.paths = {
        'angular2/*': '/vendor/angular2/*.js', // Angular
        'rtts_assert/*': '/vendor/rtts_assert/*.js', // Runtime assertions
        'rx/*': '/vendor/node_modules/rx/*.js',
        'todo-app': 'components/todo-app/todo-app.es6', // The todo-app component
        'xhr-factory': 'services/xhr-factory.es6', // The todo-factory Service
        'todos-factory': 'services/todos-factory.es6' // The todo-factory Service
    };

    // Kick off the todo-application
    System.import('todo-app');
    </script>
</body>

</html>
Things to notice
Line 5 : We have included bootstrap css that we have downloaded using bower
Line 7 : The es6 shim file
Line 12 : Init our base component – todo-app
Line 18 : Paths where our assets are located
Line 28 : Kick off the app
In the next steps, we are going to create all the referenced files.

Client Side code structure

As of today, Angular 2.0 support only HTTP GET request using the XHR module. So, I have borrowed Antonio’s approach for making a HTTP GET, POST, PUT, DELETE calls.
So this is how we are going to structure the app
We will have a Todo App component, that will interact with the Todo Factory, which in turn uses the XHR factory to work with the REST API.
Developing the App
The first thing we are going to do is develop the todo-app component. Create a new folder named components inside the public folder. And create a new folder named todo-app inside the components folder.
Inside todo-app create a new file named todo-app.es6. Update todo-app.es6 as below
import {Component, View, bootstrap, For} from 'angular2/angular2';
import {TodoFactory} from 'todos-factory';

// Annotation section
@Component({
  selector: 'todo-app'
})

@View({
  templateUrl: 'components/todo-app/todo-app.html',
  directives : [For]
})

// Component controller
class TodoAppComponent {
 
  todos: Array;

  constructor() {
    this.todos = [];

    TodoFactory.getAll().then((data) =>{
       this.todos = data;
    });
  }

  addTodo($event, todoText) {
    if($event.which === 13) {
      var _todo = {
        text : todoText.value,
        isCompleted : false
      };

      TodoFactory.save(_todo).then((data)=>{
        // keep things in sync
        this.todos.push(data);
        todoText.value = '';
      })
    }
  }

  updateTodoText($event, todo){
  	if($event.which === 13){
  		todo.text = $event.target.value;
      var _todo = {
        _id : todo._id,
        text : todo.text ,
        isCompleted : todo.isCompleted
      };
     
      TodoFactory.update(_todo).then((data)=>{
         // console.log(data); -> {ok: true, n: 1, updatedExisting: true}
         // wait for the response before resetting the state 
         this.setEditState(todo, false);
      });
  	}
  }

  updateStatus(todo){
     var _todo = {
        _id : todo._id,
        text : todo.text ,
        isCompleted : !todo.isCompleted
      };

      TodoFactory.update(_todo).then((data)=>{
         // console.log(data); -> {ok: true, n: 1, updatedExisting: true}
         // wait for the response before updating the UI
         todo.isCompleted = !todo.isCompleted; 
      });
  	
  }

  deleteTodo(todo){
    var todos = this.todos;

  	TodoFactory.delete(todo._id).then((data)=>{
         if(data.n == 1){
          // save a n/w call by updating the local array
          // instead of making a GET call again to refresh the data 
          for (var i = 0; i < todos.length; i++) {
            if(todos[i]._id == todo._id){
              todos.splice(i, 1);
            }
          };

         }
      });
  }

  setEditState(todo, state){
  	if(state){
  	  	todo.isEditMode = state;
  	}else{
  		// don't store unwanted presentation logic in DB :/
  		delete todo.isEditMode;
  	}
  }
}
 
bootstrap(TodoAppComponent);
Things to notice
Line 1 : We import Component, View, bootstrap, For from Angular 2 source
Line 2 : We import TodoFactory, which we will be creating a bit later
Line 5 : We create a new component identified by the selector todo-app
Line 9 : We are going to load the template for this view from an external file and also specify what directives would be using, so that Angular can inject them.
Line 15 : We create a TodoAppComponent that consists of the component definition.
Line 17 : We create a new variable named
todos
  of type Array.
Line 19 : Inside the constructor method, we are going to fetch the list of todos using 
TodoFactory.getAll()
 and assign the same to
todos
. This will take care of loading any existing todos on page load.
Line 27 : This method is used to add a new todo to our DB. When the user enters a Todo and hits the return key, we fetch the text, create a todo object and call the 
TodoFactory.save()
 passing in the todo to be saved. Once we get a response, we update the local 
todos
 with the response from the server, to keep things in sync.
Line 42 : This method is used to update the todo text in the DB. It is pretty much same as the
addTodo()
 except, this time we have a
_id
  associate with the todo and we use
TodoFactory.update()
 to update the data against the given todo.
Line 59 : This method is used to update the todo status, i.e. the todo completion. Here we pick the todo’s isCompleted property and build a todo object. We then call the same 
TodoFactory.update()
 to update the data.
Line 74 : This method is used to delete a Todo. We pass the Id of the todo to be deleted to
TodoFactory.delete()
 and the todo gets deleted from the DB. Once the server responds with a success, we clear the same from the local
todos
  array to save a DB call.
Line 91 : This is a utility method, that helps us manage the state of the todo. When clicked on the Edit button, next to the todo, we set
isEditMode
  to true and when we are done editing, we set
isEditMode
  to false.
Finally on line 101, we bootstrap the TodoAppComponent.
Next, we are going to create the template. Inside the todo-app folder, create another file named todo-app.html. Update todo-app.html as below
<div class="row col-md-12">
    <h1 class="text-center">M.E.A.N. Todo App with Angular 2.0</h1>
    <hr>
    <div class="row col-md-12">
        <div>
            <input class="form-control input-lg" placeholder="Add a Todo" autofocus #todotext (keyup)="addTodo($event, todotext)">
        </div>
    </div>
    <div class="row col-md-12 todos">
        <br>
        <div class="alert alert-info text-center" [hidden]="todos.length > 0">
            <h3>No Todos yet!</h3>
        </div>
        <div *for="#todo of todos" class="col-md-12 col-sm-12 col-xs-12 todo" [class.strike]="todo.isCompleted">
            <div class="col-md-1 col-sm-1 col-xs-1">
                <input type="checkbox" [checked]="todo.isCompleted" (click)="updateStatus(todo)">
            </div>
            <div class="col-md-8 col-sm-8 col-xs-8">
                <span [class.hidden]="todo.isEditMode">{{todo.text}}</span>
                <input [class.hidden]="!todo.isEditMode" type="text" [value]="todo.text" (keypress)="updateTodoText($event, todo);">
                <input [class.hidden]="!todo.isEditMode" type="button" class="btn btn-warning" value="Cancel" (click)="setEditState(todo, false)" />
            </div>
            <div class="col-md-3 col-sm-3 col-xs-3">
                <input type="button" class="btn btn-info" [class.disabled]="todo.isCompleted" class="pull-right" value="Edit" (click)="setEditState(todo, true)" />
                <input type="button" class="btn btn-danger" class="pull-right" value="Delete" (click)="deleteTodo(todo)" />
            </div>
        </div>
    </div>
</div>
Before digging into the file, we will look at three changes to Angular 2.0 templating syntax.
  • Variable are declared using a hash before them – #name
  • Event Handlers are declared using a parenthesis around them – (click)
  • Property binding are done using square braces – [class]
Keeping the above in mind, we will go through the above template
Line 6 : We create a input element, which is bound to a local variable named
todotext
 which is represented by 
#todotext
 on the tag. We have bound a 
(keyup)
 event that calls 
addTodo()
 in our component definition.
Line 11 : If there are no todos, we show a message – No Todos yet!. This is achieved using 
[hidden]
 property binding
Line 14 : We use the 
*for
 syntax (analogous to ng-repeat) to iterate over all the todos in our collection and display them. Also do notice 
[class.strike]
, (analogous to ng-class) if the current todo is in a completed state, we want to add a class named strike.
Line 16 : We provide a check box for the user to check or uncheck the todo. The state of the checkbox is maintained by 
[checked]="todo.isCompleted"
. And when a user clicks on the checkbox, we set the value of isCompleted for that todo from
updateStatus()
.
Line 19 : We display the todo text if it is not in edit mode
Line 20, 21 : we show the editable text box and a cancel button when the todo is in edit mode.
Line 24 : Button to toggle edit mode
Line 25 : Button to delete a todo
Now you can figure out what is happening on a tag based on the 3 new changes we saw earlier. Also, the classes which you see here are specific to Twitter Bootstrap.
That is it, our todo-app component is ready.
Now, we need to develop the two services. Create a new folder inside the public folder named services. Create a new file inside it named todos-factory.es6 and update it as below
import {$http} from 'xhr-factory';

export const TodoFactory = {
 
  getAll :function(){
  	return $http.get('/api/v1/todos');
  },

  get:function(id){
  	return $http.get('/api/v1/todo/'+id);
  },

  save: function(todo){
  	return $http.post('/api/v1/todo', todo);
  },

  update: function(todo){
  	return $http.put('/api/v1/todo/'+todo._id, todo);
  },

  delete: function(id){
  	return $http.delete('/api/v1/todo/'+id);
  }

};
Quite self explanatory.
Important : Angular 2.0 DOES NOT have a $http service. I have created one using the xhr-factory and named it to $http for familiarity sakes.
Next, create a new file named xhr-factory.es6 inside the public/services folder and update it as below
export const $http = {
	get: function(url: string) {
		return _sendRequest(url, null, 'GET');
    },
    post: function(url: string, payload: any){
    	return _sendRequest(url, payload, 'POST');
    },
    put: function(url: string, payload: any){
    	return _sendRequest(url, payload, 'PUT');
    },
    delete: function(url: string, payload: any){
    	return _sendRequest(url, null, 'DELETE');
    }
}

// borrowed from https://github.com/afj176/ng2-express-starter/blob/master/public/components/contact/Contact.js#L36
function _sendRequest(url: string, payLoad: any, type: string): Promise {
        return new Promise(function(resolve, reject) {
            var req = new XMLHttpRequest();
            req.open(type, url);
            req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");

            req.onload = function() {
                if (req.status == 200) {
                    resolve(JSON.parse(req.response));
                } else {
                    reject(JSON.parse(req.response));
                }
            };

            req.onerror = function() {
                reject(JSON.parse(req.response));
            };

            if (payLoad) {
                req.send(JSON.stringify(payLoad));
            } else {
                req.send(null);
            }
        });
    }
All we are doing is making a HTTP Request using the
XMLHttpRequest()
 and returning a promise.
Pretty simple right! With this we are done with app logic. For some styling, we will create a new folder inside the public folder named css. Inside the css folder, create a new file named app.css and update it as below.
* {
    -webkit-border-radius: 0 !important;
    -moz-border-radius: 0 !important;
    border-radius: 0 !important;
    font-family: calibri;
}

.strike span {
    text-decoration: line-through;
    color: #ccc;
}

.todos {
    padding: 20px;
}

.todo {
    padding: 10px;
    font-size: 21px;
    border-bottom: 1px solid;
}

.todo .btn {
    margin-left: 5px;
    width: 72px;
}

.todo:hover {
    background: #e7e7e7;
}

@media (max-width: 991px) {
    .todo .btn {
        margin-bottom: 10px;
    }
    .todo input[type=checkbox] {
        width: 25px;
        height: 25px;
    }
}

@media (max-width: 991px) {
    .todo .btn-warning {
        margin-top: 10px;
        margin-left: 0px
    }
}

@media (max-width: 450px) {
    .todo .col-xs-8 {
        width: 85%;
    }
    .todo .col-xs-3 {
        width: 100%;
        text-align: center;
        border-top: 1px dashed #aaa;
        padding-top: 10px;
        margin-top: 10px;
    }
}
Test the App
That is it! Save all your files and run
node server.js
Next, navigate to 
http://localhost:3000
 and you should see
Add a few todos and you can click on edit
You can mark todos as completed or delete them
Simple and sweet MEAN App with Angular 2.0

Thanks for reading! Do comment.
@arvindr21
 
The post Developing a MEAN app with Angular 2.0 appeared first on The Jackal of Javascript.