Hello! My name is Andrew MacKinnon and I'm a Systems Engineer at Synergy Marketing.
Several months ago between projects I decided to learn more about frontend JavaScript frameworks and see if it could be applied to the next project I was assigned. I looked at various frameworks including Backbone.js, Ember.js and AngularJS. In the end my dabbling paid off and we settled on using AngularJS for the next project, mostly due to community size, amount of resources and initial learning curve.
In this article I will explain our initial setup of AngularJS.
* I’ve updated the versions of software where needed to match the current available versions. Look at the respective site for each tool to see the current version / instructions before installing.
NVM (Node Version Manager https://github.com/creationix/nvm) is a tool to manage multiple active Node.js versions. This is useful if any of our other tools, such as the test suite, change to require a different version of Node.js. It will make upgrading (or downgrading) a simple task.
To install I used wget and the bash installation script (instructions on the NVM github page)
1 |
> wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.24.0/install.sh | bash |
Node.js
Since NVM is installed, installing Node.js is a trivial matter.
1 2 |
> nvm install v0.10.37 > nvm alias default v0.10.37 |
The 0.10.x series was used because it is one of the two versions that the test suite selected supports (http://karma-runner.github.io/0.12/intro/installation.html).
NPM
NPM (Node Package Manger) is the default package manager that comes with Node.js. We’ve been using it to install and manage packages that are used for development and testing.
Packages installed using NPM can be done on a global level (all projects can use it) or locally to a given project. This can be controlled by using the -g flag. Multiple packages can also be installed at the same time by separating package names with a space.
1 |
> npm install -g bower grunt-cli karma-cli yo generator-angular |
One thing that is important to note: Any global packages installed for a given Node.js installation is for that Node.js version only. For each Node.js version installed using NVM, global packages will need to be re-installed.
Bower
We use Bower (http://bower.io) as a package manager for libraries included as part of the website itself. This also includes the library for mocking backend processes. We installed it globally to allow us its use in any project without having to install it for each project.
1 |
> npm install -g bower |
Grunt
Grunt (http://gruntjs.com) is a JavaScript task runner. Task runners help automate various tasks such as minifying, concatenating JavaScript files into a single file, automatically running JSHint and automatic running of test cases during development.
Grunt is generally installed on a per project basis, so we installed the command line interface globally to allow us to access the Grunt version installed for the project from the command line.
1 |
> npm install -g grunt-cli |
Yeoman
Yeoman (http://yeoman.io) is a web scaffolding tool. Yeoman by itself is a good tool to help get the initial project started, but the Yeoman workflow helps with development, building and managing the project. This workflow includes Yeoman, a build system and a package manager.
To use Yeoman, first it needs to be installed.
1 |
> npm install -g yo |
Next the generator that will be used needs to be installed. The generator can be chosen from the available existing ones, or you can create your own. We went with the AngularJS generator provided by the Yeoman team.
1 |
> npm install -g generator-angular |
Testing Libraries
One of the big advantages of using a frontend framework is the ability to do unit testing. Done right, this can reduce the amount of time needed for manually testing the user interface and result in increased time to create new functionality.
It was decided to use Karma runner and Jasmine assertion library for the unit tests and Protractor for E2E (end to end) tests. How the tests were written and how Protractor was used is a topic for another time though.
The Yeoman AngularJS generator has Karma included in the Bower configuration, so there was no need to explicitly add it. However, we did add the Karma command line interface so Karma could be executed from the command line.
1 |
> npm install -g karma-cli |
This is a global install so it can be accessed from any project. Karma and Jasmine on the other hand are installed locally to each project, allowing us to change versions on a project by project basis.
The Setup
Using Yeoman and generator-angular, we executed Yeoman from the command line.
1 |
> yo angular demo |
Following the prompts we selected to not use Sass, to use Bootstrap and left the modules as the default selections.
NPM install and Bower install are automatically run, causing the preset packages to be downloaded and installed into the local project.
Sometimes errors do come up during the install process. Unfortunately, these need to be handled on a case by case basis.
After the installation is completed and any errors that came up during the install fixed, we then installed tools needed for unit tests (saving them as a development packages to the package.json file used by NPM).
1 |
> npm install karma-phantomjs-launcher karma-jasmine grunt-karma --save-dev |
note: PhantomJS (http://phantomjs.org) is a headless WebKit that is used in place of a browser for testing. For full testing actual browsers should be used, but for simple unit tests this is enough.
A few lines in Gruntfile.js, located in the project root, were changed to allow us to load the site from other machines and to not automatically open a new browser window when the server is started.
1 2 3 4 5 6 7 8 9 10 |
// The actual Grunt server settings connect : { options : { port : 9000, hostname : '0.0.0.0', livereload : 35729 }, livereload : { options : { open: false, |
This is done by changing the hostname to ‘0.0.0.0’ and setting the open option to false inside livereload.
At this point we can run the frontend on top of node using the Grunt task provided by the generator and view it in the browser.
1 |
> grunt serve |
Mocking
An important part of using a framework like AngularJS is being able to develop both the front and backend simultaneously. This is possible because the frontend is no longer coupled with the backend; instead, the two communicate using JSON. The result is a delay in the development of the backend doesn't slow down the development of the frontend. The same goes for the reverse.
The problem with not having a backend, is populating lists with records, testing how an invalid input appears in the frontend after receiving a response from the backend etc. To solve this problem the records could be hardcoded as HTML or JavaScript somewhere, but that means it also has to be removed at some point before a production release. To deal with this we use mocking.
Mocking allows us to simulate a backend and can easily be configured to not include any of the mocking data into the production release. For this we are using the bower-angular-mocks library(https://github.com/angular/bower-angular-mocks). It is installed using Bower and saved for development only.
1 |
> bower install angular-mocks --save-dev |
Since this is being saved as a development dependency, it is not automatically added to the index.html file. To use it, we needed to add it manually:
1 |
<script src="bower_components/angular-mocks/angular-mocks.js"></script> |
The problem now is how to automatically remove this from the production build. This can be done by using a library to process the HTML and configure grunt to remove it during the production build.
The grunt-processhtml library (https://github.com/dciccale/grunt-processhtml) is used to manipulate the index.html file during a build. We used this to remove the angular-mocks.js and associated files during the production build process.
1 |
> npm install grunt-processhtml --save-dev |
Add a new setting for processhtml within the Gruntfile.js initConfig object.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
processhtml : { options : { commentMarker: 'process' }, dist : { files : [{ expand : true, cwd : '<%= yeoman.dist %>', src : ['*.html', 'views/{.*}*.html'], dest : '<%= yeoman.dist %>' }] } } |
Processhtml is configured to look for comments using the keyword ‘process’. If the build is dist (distribution) then it will move any required files to the production directories.
Next we add this to the Grunt task.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
grunt.registerTask('build', [ 'clean:dist', 'wiredep', 'useminPrepare', 'concurrent:dist', 'autoprefixer', 'concat', 'ngAnnotate', 'copy:dist', 'cdnify', 'cssmin', 'uglify', 'filerev', 'processhtml', // <== here! 'usemin', 'htmlmin' ]); |
Each time the build task is run, processhtml will also be run.
Next index.html had to be set up to automatically remove mock files during a production build.
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 |
<!- build.js(.) scripts/vendor.js --> <!-- bower.js --> <script src="bower_components/jquery/dist/jquery.js"></script> <script src="bower_components/angular/angular.js"></script> <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script> <script src="bower_components/angular-animate/angular-animate.js"></script> <script src="bower_components/angular-aria/angular-aria.js"></script> <script src="bower_components/angular-cookies/angular-cookies.js"></script> <script src="bower_components/angular-messages/angular-messages.js"></script> <script src="bower_components/angular-resource/angular-resource.js"></script> <script src="bower_components/angular-route/angular-route.js"></script> <script src="bower_components/angular-sanitize/angular-sanitize.js"></script> <script src="bower_components/angular-touch/angular-touch.js"></script> <!-- endbower --> <!-- endbuild --> <!-- process:remove:dist --> <script src="bower_components/angular-mocks/angular-mocks.js"></script> <!-- /process --> <!-- build:js({.tmp,app}) scripts/scripts.js --> <script src="scripts/app.js"></script> <script src="scripts/controllers/main.js"></script> <script src="scripts/controllers/about.js"></script> <!-- endbuild --> <!-- process:remove:dist --> <script src="devmock/app-mock.js"></script> <!-- /process --> |
There are a couple sets of HTML comments. Originally the ones starting with ‘build:js’ were built as part of the generator used. What they are doing is telling bower to compact all of the files listed between the opening and closing comments into a single file (eg. create the vendor.js file and place it into the scripts directory). This will allow the browser to cache the file so it doesn't need to download the file until it changes. That reduces HTTP requests and helps speed up the page when it first loads.
Similarly we’ve added two new comment blocks using the keyword ‘process’. In these blocks we are telling Grunt to remove the contents between the comments during a production build. You might have noticed that the second block has a file called ‘app-mock.js’. Keep reading, I’ll explain that a little further down.
The AngularJS application needs to be configured to use angular-mocks. Normally the AngularJS application is set in index.html in such a way that it is automatically bootstrapped when the page loads. To completely remove the mock files during production, I found it easier to manually bootstrap AngularJS.
First, remove the ng-app attribute from the body tag in index.html
1 2 |
</head> <body> |
Second, bootstrap the application inside the app.js file (if the mocking files are not present)
1 2 3 4 5 6 7 |
(function() { if(!angular.mock) { angular.element(document).ready(function() { angular.bootstrap(document, ['demoApp']); }); } })(); |
This tells AngularJS to bootstrap if angular.mock is not present.
Third, create a new file and place it somewhere that best suits your directory structure. In our case, we created a directory called ‘devmock’ inside app and a file called app-mock.js.
1 2 3 4 5 6 7 8 9 10 11 |
'use strict'; angular.module('demoAppDev', ['demoApp', 'ngMockE2E']).run( function($httpBackend) { }); if(angular.mock) { angular.element(document).ready(function() { angular.bootstrap(document, ['demoAppDev']); }); } |
In this file the application is being bootstrapped and demoApp included as a module.
At this point the mock setup is complete. There is only one thing left to do before we can load the server up and look at our web application. We need to tell the mock that calls to get HTML files are to be allowed. This is done by adding a call to passThrough inside the run block.
1 2 3 4 |
angular.module('demoAppDev', ['demoApp', 'ngMockE2E']).run( function($httpBackend) { $httpBackend.whenGET(/^views\/main\.html/).passThrough(); }); |
This tells angular-mocks that anytime a request for the views/main.html file is intercepted, it should just let it go without stopping it. The parameter is a regular expression, so it can be changed to match any URL. This comes in handy when you want to load a list. You can write the production code exactly as you would for a backend and intercept the API call here, then return the JSON list without connecting to an actual backend server.
1 2 3 4 5 6 7 |
angular.module('demoAppDev', ['demoApp', 'ngMockE2E']).run( function($httpBackend) { $httpBackend.whenGET(/^\/api\/foo\/bar\/list/).respond(function(method, url) { return [200, {data: ['foo', 'bar']}]; }); $httpBackend.whenGET(/^views\/main\.html/).passThrough(); }); |
Wrap-up
This is just a simple example of how we first configured AngularJS. It slowly changes to match requirements and as we learn better ways. There are a number of areas I glossed over without going into detail. For now, I’ll leave those points for you to look up if you’re interested.