Beginner's Guide to Setting up A Generic Angular Project Using Grunt

Author: Max Seo


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

  1. Install nodejs.
    1. This will allow you to use a tool called npm, a package manager for JS tools similar to apt-get in linux.
  2. Go to command prompt and type "npm" to verify correct installation.
    1. You may get an error like "Error: ENOENT, stat ‘C:\Users\DoeJ\AppData\Roaming\npm’” if running on Windows OS.
      1. Simply create the folder npm under the path.
      2. This folder is where your npm tools will be installed.
  3. Install node package bower:
    1. Execute the command, “npm install -g bower”
      1. The npm install command is used to install a package
      2. 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.
      3. The bower argument is the name of the package you want to install. Full list of packages are available at npm site.
        1. bower is a JS tool which can download JS libraries and resolve dependencies from github.
    2. Install git and make sure git executable is in your PATH variable.
  4. Create a directory for your project called myangular and navigate to it.
  5. 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.
    1. This command generates a package.json file, which specifies the project information and development-only dependencies. It’s comparable to pom.xml in maven.
  6. Install the following dev dependencies using the command "npm install --save-dev ".

    1. :: 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.
      :: http://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

    2. 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.
    3. 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.
  7. Install the bower dependencies which we will be using for this tutorial.
    1. Run these commands:

      bower init
      bower install --save angular
      bower install --save angular-route
      bower install --save angular-mocks
      {
      "proxy" : "http://proxye1.finra.org:8080",
      "https_proxy" : "http://proxye1.finra.org:8080"
      }
  8. Create a Gruntfile.js.
    1. 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
      http://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
      http://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']);

      };
  9. Add some source files.
    1. src/app.js

      angular.module('myangular', ['ngRoute']).config(function($routeProvider) {


      $routeProvider.when('/login', {
      templateUrl : 'src/templates/login.html',
      controller : 'LoginController'
      });

      });
    2. 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>
    3. 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;
      }
      });
    4. 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;
      }

  10. Now add a test.
    1. test/app.js describe('app', function() {
      it('should work', function() {
      expect(true).toEqual(true);
      });
      });
  11. Modify your package.json.
    1. modify under Scripts.

      "scripts": {
      "test": "grunt test",
      "build": "grunt build"
      },

  12. Run command "npm run test" to run tests.
    1. A test-results.xml file should be created under your project root, this is your Junit report.
    2. A coverage/ directory should be created under your project root.
      1. A list of directories with browsers that the tests were run under should be here.
      2. Navigate to a browser directory and open the 'index.html' to see the code coverage results for that browser.
      3. A cobertura-coverage.xml should've been created, this is the Jenkins consumable result.
  13. Run command "npm run build" to compile sources to be ready for distribution
    1. The dist directory contains your production distributable js and css files.
    2. 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.