Introduction
At the end of this tutorial, you should be able to set up a generic JS project using Angular and Grunt as a task runner. The project should be able to unit test, generate test coverage report, and minify the source codes with minimal configuration. This guide can help developers who are unfamiliar with JS frameworks and tools to start learning.
However, it won’t explain all possible use cases and options for a tool. Nor does it attempt to provide a full tutorial of AngularJS, Grunt, or testing frameworks. These are too big of a topic to include in this guide.
What you'll need to get started
- Working knowledge of the command prompt/shell on your platform.
- Working knowledge of JS language and JSON.
- Admin privileges to install software and create directories.
Setting up your Angular project
- Install nodejs.
- This will allow you to use a tool called npm, a package manager for JS tools similar to apt-get in linux.
- Go to command prompt and type "npm" to verify correct installation.
- You may get an error like "Error: ENOENT, stat ‘C:\Users\DoeJ\AppData\Roaming\npm’” if running on Windows OS.
- Simply create the folder npm under the path.
- This folder is where your npm tools will be installed.
- You may get an error like "Error: ENOENT, stat ‘C:\Users\DoeJ\AppData\Roaming\npm’” if running on Windows OS.
- Install node package bower:
- Execute the command, “npm install -g bower”
- The npm install command is used to install a package
- The -g option installs the package globally, meaning the package you install will be available under all directories. Default command will only make the package available in the directory you are in.
- The bower argument is the name of the package you want to install. Full list of packages are available at npm site.
- bower is a JS tool which can download JS libraries and resolve dependencies from github.
- Install git and make sure git executable is in your PATH variable.
- Execute the command, “npm install -g bower”
- Create a directory for your project called myangular and navigate to it.
- Run command “npm init” while in your project directory. Follow the prompts on the screen. For all prompts given, press enter to set to default.
- This command generates a package.json file, which specifies the project information and development-only dependencies. It’s comparable to pom.xml in maven.
- Install the following dev dependencies using the command "npm install --save-dev
". -
:: grunt is a task runner for js, comparable to ant script npm install --save-dev grunt :: grunt-cli is the actual command line interface for running Gruntfiles npm install --save-dev grunt-cli :: jshint is a syntax and formatting checker for js. npm install --save-dev grunt-contrib-jshint :: karma is a unit testing helper which spawns browsers for you npm install --save-dev grunt-karma :: jasmine is the unit testing framework which we will be using to write unit tests npm install --save-dev karma-jasmine :: PhantomJS is a headless browser that is used to run your unit tests. :: Other browsers are available, but they must be installed in your environment. :: //karma-runner.github.io/0.10/config/browsers.html npm install --save-dev karma-phantomjs-launcher :: karma has several "reporters" to output the test results. Mocha shows results to your console. npm install --save-dev karma-mocha-reporter :: karma plugin to generate JUnit reports npm install --save-dev karma-junit-reporter :: code coverage plugin for karma, uses istanbul behind the scenes https://github.com/gotwarlost/istanbul npm install --save-dev karma-coverage :: LESS css pre-processor npm install --save-dev grunt-contrib-less :: css minification npm install --save-dev grunt-contrib-cssmin :: Puts your template *.html files into $templateCache for minification :: Otherwise Angular will have to make ajax requests for all templates npm install --save-dev grunt-angular-templates :: Automagically converts your angular dep injection into a format that can be minified :: ex. app.controller('MyCtrl', function($scope, $http) {}) becomes app.controller('MyCtrl', ['$scope', '$http', function($scope, $http) {}]) :: See https://docs.angularjs.org/tutorial/step_05 under "A Note on Minification" :: Also concatenates .js files npm install --save-dev grunt-ng-annotate :: Obfuscates and minifies JS files npm install --save-dev grunt-contrib-uglify :: Optional, but very useful tool to load tasks in your Gruntfile.js will visit this later in the guide npm install --save-dev load-grunt-tasks
- The --save-dev option will update your package.json file to add these dev dependencies for future usage. So, you won't have to run these commands again.
- If you want to install these dependencies again, just run npm install (without any options or package name) at your project root. It will download and install all packages referred to in package.json.
-
- Install the bower dependencies which we will be using for this tutorial.
- Run these commands:
bower init
bower install --save angular
bower install --save angular-route
bower install --save angular-mocks
{
"proxy" : "//proxye1.finra.org:8080",
"https_proxy" : "//proxye1.finra.org:8080"
}
- Run these commands:
- Create a Gruntfile.js.
-
module.exports = function(grunt) { /* Loads all Grunt task plugins. You must load a task to use it here. Normally, you would have to write something like: grunt.loadNpmTask('jshint'); grunt.loadNpmTask('karma'); grunt.loadNpmTask('ngTemplates'); ...for each new tasks you installed and want to use in this Gruntfile. But "npm install -g load-grunt-tasks" allows you to write this single line to load all available tasks automatically. */ require('load-grunt-tasks')(grunt); /* Here goes the configuration for each task. General format goes like this: {
: { : { } } } In comparison to maven: is like maven plugin name is like maven plugin's goal If is 'main', this is your default target which runs when no target parameter is given in the .registerTask() (see below) All file paths in this configuration are relative to the location of Gruntfile.js Most file paths may be specified with a glob pattern to load multiple files */ grunt.initConfig({ jshint : { main : { files : { /* List files you want jshint to check. You want to list here your configurations, source, and test js files, but do not include your library files. */ src : [ '*.js', 'src/**/*.js', 'test/**/*.js' ] } } }, karma : { // karma test task main : { // default target /* There are MANY configurations available for karma. We're only scratching the surface here. See the full documentation here https://github.com/karma-runner/grunt-karma //karma-runner.github.io/0.8/config/configuration-file.html */ options : { // we are using jasmine for our testing, you can add others in this array frameworks : ['jasmine'], // we are only going to use PhantomJS as our test browser, // you can add more here browsers : ['PhantomJS'], /* Specify the files you want to load for your tests. You should include libraries and sources in the order that a browser should normally load them. You should include the test files. */ files : [ 'bower_components/angular/angular.js', 'bower_components/angular-route/angular-route.js', // note we include this library, it is required to mock backend for Angular 'bower_components/angular-mocks/angular-mocks.js', 'src/**/*.js', 'test/**/*.js' ], /* how to display the results mocha for console output, junit for junit style xml output coverage for code coverage results */ reporters : ['mocha', 'junit', 'coverage'], // preprocessors are plugins to process files before running tests // this case, we are preprocessing source files with 'coverage' // this will instrument our source code preprocessors : { 'src/**/*.js' : ['coverage'] }, // this will run karma and stop it once the test are done singleRun : true, // specify options for junit reporting junitReporter : { // junit results will be in this file outputFile : 'test-results.xml' }, // options for code coverage report coverageReporter : { /* You can add multiple reporters and options for each See full list and options //karma-runner.github.io/0.8/config/coverage.html https://github.com/karma-runner/karma-coverage */ reporters : [ {type : 'html'}, // html output {type : 'cobertura'} // and a cobertura output // cobertura is a code coverage report that is consumable by jenkins ] } } } }, less : { // less task main : { // default target files : { // take app.less, process it into a css, and save it into temp/ 'temp/app.min.css' : ['src/app.less'] } } }, cssmin : { // css minifier main : { // default target files : { // take css in temp, minify it, and save it into dist 'dist/app.min.css' : ['temp/app.min.css'] } } }, ngtemplates : { // angular templates compiler main : { // default task options : { module : 'myangular', // name of your angular app module htmlmin : { // minify the html contents // see https://github.com/kangax/html-minifier#options-quick-reference for more options removeComments : true, collapseWhitespace : true, collapseBooleanAttributes : true, removeAttributeQuotes : true, removeRedundantAttributes : true } }, // take all template .html files in source src : ['src/templates/*.html'], // save compiled templates in temp dest : 'temp/templates.js' } }, ngAnnotate : { // angular annotation and concatenator main : { // default target files : { /* Prepare angular files so they can be minified, and concatenate all the js files into a single file. Save the concatenated file into temp. You should include your libraries used in production, You should include your source js files You should NOT include your test files You should NOT include your dev only libraries */ 'temp/app.min.js' : [ 'bower_components/angular/angular.js', 'bower_components/angular-route/angular-route.js', 'src/**/*.js', 'temp/templates.js' // this is the compiled templates.js ] } } }, uglify : { // minifies your js files main : { // default task // again, many other options available options : { sourceMap : true, // we will generate a source map for uglified js sourceMapName : 'dist/app.min.map' // into this dir }, files : { // files to uglify and destination // we only have one file to uglify 'dist/app.min.js' : ['temp/app.min.js'] } } } }); /* Here we can register a task to sub-tasks to run. vList of sub-tasks grunt.registerTask('test', ['jshint', 'karma']); ^Name of your task Sub-tasks can be specified to run with a target, such as: karma:test_module_1 where 'karma' is your task and 'test_module_1' is your target */ /* Plan for testing: 1. Check for syntax and formatting errors in all files 2. Run karma tests */ grunt.registerTask('test', ['jshint', 'karma']); /* Plan for building: 1. Check for syntax and formatting errors in all files CSS processing: 2. Compile .less into a .css file 3. Minify the .css file JS processing: 4. Compile all .html angular template files into a single .js file 5. Prepare .js files for minification and concatenate all .js files into a single file 6. Obfuscate the concatenated .js file */ grunt.registerTask('build', ['jshint', 'less', 'cssmin', 'ngtemplates', 'ngAnnotate', 'uglify']); };
-
- Add some source files.
- src/app.js
angular.module('myangular', ['ngRoute']).config(function($routeProvider) { $routeProvider.when('/login', { templateUrl : 'src/templates/login.html', controller : 'LoginController' }); });
- src/templates/login.html
<div> <h1>Login</h1> <form ng-submit='login()'> <div class='field'> <label class='required'>User Name</label> <input type='text' ng-model='user.user_id'> <span class='validation'>{{validations.user_id}}</span> </div> <div class='field'> <label class='required'>Password</label> <input type='password' ng-model='user.password'> <span class='validation'>{{validations.password}}</span> </div> <p class='validation'>{{validations.message}}</p> <div class='field'> <button type='submit'>Login</button> <a href="#/register">Register</a> </div> </form> </div>
- src/controllers/LoginController.js
angular.module('myangular').controller('LoginController', function($scope, $window) { $scope.user = { user_id : null, password : null }; $scope.validations = {}; $scope.login = function() { if(validate()) { $window.alert('Logging in user ' + $scope.user.user_id + ' with password ' + $scope.user.password); } }; function validate() { var valid = true; $scope.validations = {}; if(!$scope.user.user_id) { $scope.validations.user_id = 'User name is required'; valid = false; } if(!$scope.user.password) { $scope.validations.password = 'Password is required'; valid = false; } return valid; } });
- src/app.less
html { font: 10pt arial; } label:after { content: ':'; } label.required:before { content: '*'; color: red; } form div.field { margin-bottom: 1em; } form div.field>* { display: inline-block; } .validation { color: red; }
- src/app.js
- Now add a test.
- test/app.js
describe('app', function() { it('should work', function() { expect(true).toEqual(true); }); });
- test/app.js
- Modify your package.json.
- modify under Scripts.
"scripts": { "test": "grunt test", "build": "grunt build" },
- modify under Scripts.
- Run command "npm run test" to run tests.
- A test-results.xml file should be created under your project root, this is your Junit report.
- A coverage/ directory should be created under your project root.
- A list of directories with browsers that the tests were run under should be here.
- Navigate to a browser directory and open the 'index.html' to see the code coverage results for that browser.
- A cobertura-coverage.xml should've been created, this is the Jenkins consumable result.
- Run command "npm run build" to compile sources to be ready for distribution
- The dist directory contains your production distributable js and css files.
- The temp directory contains the staging files during the build process, such as ngtemplates and less tasks.
Conclusion
Following these steps, you’ve now created an Angular project which automates validation, testing, and packaging using Grunt. Although the steps detailed here may seem long and complex, this is a one-time setup and would require only minor changes once implemented. Jumping into the npm/grunt ecosystem can be a challenging task for beginners. Hopefully this guide will help you get over the learning curve of setting up an Angular project.
A working version of this guide can be found in my github repository. Interested in learning more? Check out this link for more on npm modules and go here for Grunt’s getting started page.