First steps with AngularJS and NodeJS
Introduction & installation
It was a long time I would try a first application with AngularJS. I have read a simple tutorial in one of my favourite (french) magazine GNU/Linux Mag written by Tristan Colombo. The main goal of this application is to record simple logs message with AngularJS, Twitter bootstrap for CSS, Node.js for server side.
1 2 3 4 |
mkdir myapp ; cd myapp mkdir js css wget https://ajax.googleapis.com/ajax/libs/angularjs/1.3.3/angular.min.js wget https://github.com/twbs/bootstrap/releases/download/v3.2.0/bootstrap-3.2.0-dist.zip |
After uncompressing and moving files, I have structured folders like this :
1 2 3 4 5 6 7 8 9 |
tree -L 2 . ├── css │ ├── bootstrap.min.css │ └── bootstrap-theme.min.css ├── index.html ├── js ├── angular.min.js ├── angular.min.js.map |
And my index.html seems like this :
1 2 3 4 5 6 7 8 9 10 11 12 |
<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <title>Doc minimal</title> <link rel="stylesheet" href="css/bootstrap.min.css" /> <link rel="stylesheet" href="css/bootstrap-theme.min.css" /> <script src="js/angular.min.js"></script> </head> <body> </body> </html> |
First expressions
AngularJS allow use of expressions and filters. To understand this concepts, I use this index.html :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<!doctype html> <html lang="en" ng-app> <head> <meta charset="utf-8" /> <title>Doc minimal</title> <link rel="stylesheet" href="css/bootstrap.min.css" /> <link rel="stylesheet" href="css/bootstrap-theme.min.css" /> <script src="js/angular.min.js"></script> </head> <body> 1 + 2 = {{ 1 + 2 }} <br /> {{"My Blog Madamadasune" | uppercase}}<br /> {{35.5432 | number : 2 | currency}}<br /> </body> </html> |
OK. The first expression computes a simple sum and displays the result. The second, executes the filter uppercase on text “My Blog Madamadasune”. The third uses the float value 35.5432, format this value in string with 2 decimals, and then diplay currency depending of locale.
Modules
Modules are used to architecture Angular application ; we simply use them to split behavioral concerns into distinguished module in order to have small, readable and logic module instead of one big module. We also create module in a specific file, for example my_module.js
1 2 3 4 |
(function () { var app = angular.module('my_module', []); ... })(); |
Module could depends on another modules. In that case, we have to liste dependencies in the second argument array. For example :
1 2 3 4 |
(function () { var app = angular.module('my_module', ['compute_mod', 'stat_mod']); ... })(); |
Finally, we just have to add a declaration on pages using this module :
1 2 3 4 5 |
<html lang="en" ng-app="my_module"> <head> ... <script src="my_module.js"></script> ... |
Controllers
Controllers implements behavioral nature of a module. Of course, a module can have many controllers. Let’s see a example :
1 2 3 4 5 6 7 8 |
(function () { var data = {name : 'Tom', id : 1 }; var app = angular.module('my_module', []); app.controller('dataController', function() { this.data = data; }); })(); |
This controller just assign some hard coded data to the data variable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!doctype html> <html lang="en" ng-app="my_module"> <head> <meta charset="utf-8" /> <title>My module app</title> <link rel="stylesheet" href="css/bootstrap.min.css" /> <link rel="stylesheet" href="css/bootstrap-theme.min.css" /> <script src="js/angular.min.js"></script> <script src="js/my_module.js"></script> </head> <body ng_controller="dataController as dataCtrl> Name : {{ dataCtrl.data.name }} </body> </html> |
In this page, we just diplay the attribute name of the data object. We just have to declare the controller by directive ng-controller on the tag using this controller.
Forms
Same style is used to build forms. The principle is to bind some attributes with directive ng-model with html fields. Moreover, we can bind event with ng directives and controllers functions.
1 2 3 4 5 6 7 8 9 |
... <body ng-controller="dataController as dataCtrl"> Name : {{dataCtrl.data.name}}<br /> You have type : {{dataCtrl.data.info}}<br /> <br /> <form name="addInfoForm"> <input type="text" ng-model="dataCtrl.data.info" placeholder="Type text here..." autofocus /> </form> </body> |
We’ll use more forms features in next full example.
It’s useful to know that Angular will automaticaly set CSS classes to html elements depending of some states. For example, an invalid field will receive ng-invalid CSS class.
Services
Services are commons built-in features but you can create your owns too. For example, $http service is useful to execute asynchronous request to a server. $log can write into javascript console. We have to declare the needed services by explicit declaration or by $inject. I have just seen array declaration, for example to use $log service :
1 2 3 4 |
app.controller('myCtrl', ['$log, function($log) { ... $log.warn('Write a warn log'); }]); |
Full example
You can get a full example on my github repository by forking or download sources at https://github.com/tlallart/angular-first-steps/.
After cloning or dowloading/uncompressing, you should have a folder like this :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ tree -L 2 . ├── css │ ├── bootstrap.min.css │ ├── bootstrap-theme.min.css │ └── main.css ├── index.html ├── js │ ├── angular.min.js │ ├── angular.min.js.map │ └── app.js ├── LICENSE ├── logs_server.js ├── README.md ├── tab_add.html ├── tab_log.html └── tabs.html |
To run, we need a Node.js server with SQLite3 module.
Personnaly, I have installed a binary of node.js and manually installed module. However, you could also install it with your package manager.
1 2 3 |
wget http://nodejs.org/dist/v0.10.33/node-v0.10.33-linux-x64.tar.gz tar xvzrf node-v0.10.33-linux-x64.tar.gz mv node-v0.10.33-linux-x64.tar.gz node.js |
Then modify my .bashrc to add node.js bin directory in my PATH and add NODE_PATH as environnement variable pointing to lib/node_modules folder :
1 2 3 |
export NODE_HOME=/home/tom/install/node.js export NODE_PATH=$NODE_HOME/lib/node_modules export PATH=$NODE_HOME/bin:$PATH |
Finally, to install SQLite3 module, I just run :
1 |
npm -g install sqlite3 |
Start the application, from sources folder by running :
1 |
node logs_server.js |
Then go to http://localhost:3000/index.html with a Web browser. You’ll see a page with theses 2 tabs :
How does it works?
Now, let’s see how this application works!
Index.html just contains imports of js and css files and a menu-tabs directive.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<!doctype html> <html lang="en" ng-app="logmodule"> <head> <meta charset="utf-8" /> <title>My great log application</title> <link rel="stylesheet" href="css/bootstrap.min.css" /> <link rel="stylesheet" href="css/bootstrap-theme.min.css" /> <link rel="stylesheet" href="css/main.css" /> <script src="js/angular.min.js"></script> <script src="js/app.js"></script> </head> <body> <menu-tabs></menu-tabs> </body> </html> |
Now let’s see js code, we’ll see other html code later.
First, the node.js server code :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
var http = require('http'); var fs = require('fs'); var url = require('url'); var path = require("path"); var sqlite3 = require('sqlite3').verbose(); var db = new sqlite3.Database('logs.db'); if (!fs.existsSync('logs.db')) { db.run('create table logs(timestamp datetime primary key, username text, msg text)'); }; var app = http.createServer(function (req, res) { var stmt; var data = ''; var queryData = url.parse(req.url, true).query; res.setHeader('Context-Type', 'application/json'); res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET'); if (queryData.display !== undefined) { stmt = 'select * from logs'; db.all(stmt, function(err, rows) { data = rows; res.end(JSON.stringify(data)); }); } else if (queryData.add !== undefined) { var timestamp = new Date().getTime(); stmt = db.prepare('insert into logs (timestamp, username, msg) values(? , ?, ?)'); stmt.run(timestamp, queryData.username, queryData.msg); stmt.finalize(); res.end(JSON.stringify({ time: timestamp})); } else { var my_path = url.parse(req.url).pathname; var full_path = path.join(process.cwd(), my_path); fs.exists(full_path, function (exists) { if (!exists) { res.writeHeader(404, {"Content-Type": "text/plain"}); res.write("404 Not Found\n"); res.end(); } else { fs.readFile(full_path, "binary", function(err, file) { if (err) { res.writeHeader(500, {"Content-Type": "text/plain"}); res.write(err + "\n"); res.end(); } else { res.writeHeader(200); res.write(file, "binary"); res.end(); } }); } }); }; }); app.listen(3000); process.on('SIGINT', function () { db.close(); }); process.on('SIGTERM', function () { db.close(); }); |
In this code, we need http, fs, url, path and sqlite3 modules.
First, we create db if it doesn’t already exist.
Then we handle 3 types of requests :
1) display returns the logs
2) add a log entry
3) any other request.
Let’s see the code of the tabs :
1 2 3 4 5 6 7 8 9 10 11 12 |
<section> <ul class="nav nav-tabs"> <li ng-class="{active: tab.isSet(1)}"> <a href="" ng-click="tab.setTab(1)">Logs</a> </li> <li ng-class="{active: tab.isSet(2)}"> <a href="" ng-click="tab.setTab(2)">Add</a> </li> </ul> <log-tab></log-tab> <add-tab></add-tab> </section> |
Here, we just handle the tabs navigation by setting active / inactive the tab depending on tab status.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<div class="panel" ng-show="tab.isSet(1)" ng-init="logCtrl.loadLogs()"> <div class="table-responsive"> <table class="table"> <caption>Current logs</caption> <thead> <tr><th>Time</th><th>Name</th><th>Msg</th></tr> </thead> <tbody> <tr ng-repeat="log in logCtrl.logs | orderBy : '-timestamp'"> <td class="col-xs-3">{{log.timestamp | date : 'yyyy-MM-dd HH:mm:ss'}}</td><td class="col-xs-3">{{log.username}}</td><td class="col-xs-6">{{log.msg}}</td> </tr> </tbody> </table> </div> </div> |
In this tab, we display a table containing logs entries. By using, ng-repeat directive, we can iterate through logCtrl.logs data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<div class="panel" ng-show="tab.isSet(2)"> <form class="form-horizontal" name="addLogForm" ng-submit="addLogForm.$valid && formCtrl.saveLog()" novalidate> <div class="form-group"> <label for="username" class="control-label col-xs-2">Name</label> <div class="col-xs-10"> <input type="text" class="form-control" id="username" placeholder="Your name..." ng-model="formCtrl.username"></input> </div> <label for="msg" class="control-label col-xs-2">Message</label> <div class="col-xs-10"> <textarea class="form-control" id="msg" placeholder="Type here..." cols="40" rows="5" ng-model="formCtrl.msg" required></textarea> </div> </div> <div class="form-group"> <div class="col-xs-offset-2 col-xs-10"> <input type="submit" class="btn btn-primary" ng-disabled="!addLogForm.$valid" value="Save"/> </div> </div> </form> </div> |
In this add form, we bind form elements with data model with directive ng-model. So, formCtrl controller will get a data object with username and msg field values from the html form when user will submit it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
(function () { var app = angular.module('logmodule', []); app.directive('menuTabs', function () { return { restrict: 'E', templateUrl: 'tabs.html', controller: function () { this.tab=1; this.setTab = function (tab_to_activate) { this.tab = tab_to_activate; }; this.isSet = function (tab_to_check) { return this.tab === tab_to_check; }; }, controllerAs: 'tab' }; }); app.factory('data', function() { return []; }); app.directive('logTab', ['$http', function($http) { return { restrict: 'E', templateUrl: 'tab_log.html', controller: function ($scope, data) { this.loadLogs = function() { this.logs = []; var obj = this; $http.get('http://localhost:3000?display').success(function (info) { obj.logs = info; }); $scope.data = obj; }; }, controllerAs: 'logCtrl' }; }]); app.directive('addTab', ['$http', '$log', function($http, $log) { return { restrict: 'E', templateUrl: 'tab_add.html', controller: function ($scope, data) { this.saveLog = function() { var obj = this; $log.warn('Request server...'); $http.get('http://localhost:3000?add&username=' + this.username + '&msg=' + this.msg).success(function (info) { $scope.data.logs.push({timestamp:info.time, username:obj.username, msg:obj.msg}); obj=msg; }); $log.warn('Request done'); }; }, controllerAs: 'formCtrl' }; }]); })(); |
Last file is app.js. This file declare the logModule module, create the directive menuTabs from template tabs.html with 2 functions : isSet(tabIndex) and setTab(tabIndex).
Then, this file create a ‘data’ object which is our model.
Finally, it create 2 controllers logCtrl and formCtrl for each tab. First have a function to display logs by sending a http request to node.js server using $http service, second have a function to send a new log by sending a request to node.js server using $http service too.
Conclusion
Well…. it’s not so hard! I haven’t writing Javascript code since years but, with tutorials and some MVC concepts knowledge it was enough. I think Angular really helps to better architecture our code and, even if it’s sometimes a bit confusing, it’s certainly a really good framework.
Next steps
For me, testing is really important and is part of our developpers work. I haven’t check testing framework (CasperJS? PhatomJS?) and build tools (Grunt?). I can’t start a real project without this kind of tools. So I found this free pdf book to read : Pro AngularJS.
Writing articles is pain in the ass.I know how you can get unlimited content for your blog, search in google:
Anightund’s rewriter