This website uses cookies to allow us to see how the site is used. If you continue to use this site we will assume that you are happy with this.Click to hide
Menu
CoDE - NCR Edinburgh
CoDE - NCR Edinburgh
  • Home
  • People
  • Events
  • Social
  • Products
  • Jobs
  • Edinburgh
  • Blog
  • Contact
 home > blog > angular tools training

Angular Toolchain on Windows

These notes were prepared to help colleagues get set up for Angular development. They may be helpful for others. Subjects covered include:

Node, NPM, Yeoman, generator-angular, Grunt, grunt-connect-proxy, Usemin, Bower, Compass, Bootstrap, Sass / SCSS, Karma, Jasmine, karma-ng-html2js-preprocessor, jasmine-given, jasmine-stealth, CoffeeScript, JSHint, Editorconfig, Chrome dev tools & Sublime Text 3 plugins.

Installation

  1. Git (download) used not just for Git but also for Git Bash which gives a bash shell rather than a Windows DOS prompt.
  2. Sublime Text 3 (download) used with various plugins.
  3. Node.js (download) needed for Yeoman and Grunt (use 0.10.26 as .28 and .27 cause problems)
  4. Ruby (download) needed for compass
  5. Compass install
     gem update --system
     gem install compass
  6. Yeoman (Git Bash):
     npm install -g yo
    this will install Grunt for builds and Bower for client side dependencies.
  7. generator-angular (Git Bash):

     npm install -g generator-angular
  8. Karma

     npm install --save-dev karma-jasmine karma-chrome-launcher
     npm install -g karma-cli

NOTE: There are some videos included in this tutorial. The steps followed in the videos may differ from those in the text. So follow the steps outline in the text and use the videos for reference if you get stuck. You will need to view most of the videos full screen.

Getting started

Generate the project:

mkdir myapp
cd myapp
# Answer yes to all questions Yeoman asks
yo angular myapp

What just happened:

  • A basic project structure was created
  • A package.json file was created for npm and the npm install command was invoked
  • A bower.json file created and the bower install command was invoked

To start a test server:

grunt serve

You should see this:


File system overview

What did Yeoman install?


Node and npm

Node.js runs JavaScript on the command line rather than in the browser. Among other things, it can be used to:

  • Copy, modify, move and delete files
  • Run a web server to serve local files to a browser

Npm is node's package manager. It allows one package to depend on other packages declared in a package.json file. Consider this command:

npm install

The command above installs all the dependencies of a package (and all the dependencies of each dependency). This is a local install – local in this sense means local to the project. Running npm install results in a node_modules folder being created. Each folder in node_modules contains a package with may itself contain a node_modules folder.

Global installs

Some node packages are intended to be used on the command line (e.g. Yeoman) so they need to be installed globally. We do this with the -g flag:

`npm install -g yo`

Think globally, act locally

Sometimes we want the convenience of installing a command globally, but the flexibility to have a different version of the package for each project. We use CLI wrapper packages to accomplish this.

Let's consider the example of the Karma test runner. A normal (global) install would look like this:

npm install -g karma

Instead, we install karma-cli globally:

npm install -g karma-cli

karma-cli is just a wrapper command that looks for a locally installed version of Karma in the node_modules folder of our project. Running karma start outside our project or before doing an npm install will result in the following warning:

Cannot find local Karma!
  Please install Karma by `npm install karma --save-dev`.
  If you wanna use a global instance, please set NODE_PATH env variable.

Note: when we install Yeoman, it installs grunt in this way using the grunt-cli wrapper.

Adding a dependency

Because yeoman normally only uses node for build steps, all our dependencies will be dev dependencies. If we want to install a new local dev dependency (e.g. karma-jasmine) and save it to our package.json:

npm install --save-dev karma-jasmine

Note: If you are cloning a project created with Yeoman, you will need to manually install the dependencies:

npm install

You may need to run this occasionally after running git pull if members of your team add new dependencies.

Also see: egghead.io NodeJS videos


Grunt

Dependencies related to the build – in other words, related to grunt, are found in package.json:

{
  "name": "moo2",
  "version": "0.0.0",
  "dependencies": {},
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-autoprefixer": "~0.4.0",
    "grunt-bower-install": "~1.0.0",
    "grunt-concurrent": "~0.5.0",
    "grunt-contrib-clean": "~0.5.0",
    "grunt-contrib-compass": "~0.7.2",
    "grunt-contrib-concat": "~0.3.0",
    "grunt-contrib-connect": "~0.5.0",
    "grunt-contrib-copy": "~0.4.1",
    "grunt-contrib-cssmin": "~0.7.0",
    "grunt-contrib-htmlmin": "~0.1.3",
    "grunt-contrib-imagemin": "~0.3.0",
    "grunt-contrib-jshint": "~0.7.1",
    "grunt-contrib-uglify": "~0.2.0",
    "grunt-contrib-watch": "~0.5.2",
    "grunt-google-cdn": "~0.2.0",
    "grunt-newer": "~0.6.1",
    "grunt-ngmin": "~0.0.2",
    "grunt-rev": "~0.1.0",
    "grunt-svgmin": "~0.2.0",
    "grunt-usemin": "~2.0.0",
    "jshint-stylish": "~0.1.3",
    "load-grunt-tasks": "~0.4.0",
    "time-grunt": "~0.2.1",
    "karma-ng-scenario": "^0.1.0",
    "grunt-karma": "^0.8.3",
    "karma": "^0.12.14",
    "karma-ng-html2js-preprocessor": "^0.1.0",
    "karma-chrome-launcher": "^0.1.3",
    "grunt-jasmine": "^0.1.0",
    "karma-jasmine": "^0.1.5",
    "grunt-contrib-jade": "^0.11.0"
  },
  "engines": {
    "node": ">=0.10.0"
  },
  "scripts": {
    "test": "grunt test"
  }
}

If we were to look inside the Gruntfile.js, we would find a correlation between the tasks configured there and the packages listed in package.json.

At the bottom of Gruntfile.js, we see some tasks registered:

  • serve
  • test
  • build
  • default

Let's try test:

grunt test
# Executed 1 of 1 SUCCESS

This runs the unit tests. If you get an error, check that you have already run this command:

npm install --save-dev karma-jasmine karma-chrome-launcher

Now, let's try build:

grunt build

Now have a look in your dist folder.

What happened?

  • Scripts included through bower were concatenated and minified into vendor.js
  • Scripts from our app were concatenated and minified into scripts.js
  • Each minified file has a unique ref as part of its name to help cache busting
  • App CSS and Bootstrap CSS files were minified into main.css
  • The HTML inside the index.html file was minified

Now let's test that in the browser:

grunt serve:dist

This time, the server serves our app from the dist directory rather than app and .tmp (.tmp is used for output of preprocessed scripts and css).

To remove the dist directory, run this command:

grunt clean

Also see: egghead.io Grunt videos

Adding a grunt task – Create a proxy for REST API

If our RESTful API and web app will reside on the same domain, this simplifies our app, because we do not need to make cross origin requests. However, in development we may want to run our API server on a different host or port. If we use a proxy we can make it appear to the browser that the REST end point and app are served from the same domain.

Grunt connect proxy is very handy for accessing REST APIs during development without having to worry about CORS.

Let's install it:

npm install grunt-connect-proxy --save-dev

Look at package.json and a property has been added:

"grunt-connect-proxy": "^0.1.10"

Edit the connect: property in Gruntfile.js adding proxies: in place of livereload::

proxies: [
  {
    context: '/people',
    host: 'ncredinburgh.com',
    port: 80,
    https: false,
    changeOrigin: false
  }
],
livereload: {
  options: {
    open: true,
    base: [
      '.tmp',
      '<%= yeoman.app %>'
    ],
    middleware:function (connect, options) {
      if (!Array.isArray(options.base)) {
          options.base = [options.base];
      }
      // Setup the proxy
      var middlewares = [require('grunt-connect-proxy/lib/utils').proxyRequest];
      // Serve static files.
      options.base.forEach(function(base) {
        middlewares.push(connect.static(base));
      });
      // Make directory browse-able.
      var directory = options.directory || options.base[options.base.length - 1];
      middlewares.push(connect.directory(directory));
      return middlewares;
    }
  }
},

Edit the serve task by adding configureProxies:

grunt.task.run([
  'clean:server',
  'bowerInstall',
  'concurrent:server',
  'configureProxies',   //add here
  'autoprefixer',
  'connect:livereload',
  'watch'
]);

Then start up a server:

grunt serve

If you visit http://localhost:9000/people you should see the contents of https://ncredinburgh.com/people. Note that CSS and other styling will not load, so it will look a little ugly.

Tip: Change the connect.options.hostname: setting from localhost to 0.0.0.0 this will enable you to view your application using your computer's IP on any other computer on the network (very handy for testing on mobile).

Bower

These are browser dependencies (JQuery, Angular etc).

They are specified by the bower.json file:

{
  "name": "moo2",
  "version": "0.0.0",
  "dependencies": {
    "angular": "1.2.15",
    "json3": "~3.2.6",
    "es5-shim": "~2.1.0",
    "jquery": "~1.11.0",
    "bootstrap-sass-official": "~3.1.0",
    "angular-resource": "1.2.15",
    "angular-cookies": "1.2.15",
    "angular-sanitize": "1.2.15",
    "angular-route": "1.2.15"
  },
  "devDependencies": {
    "angular-mocks": "1.2.15",
    "angular-scenario": "1.2.15"
  }
}

To add a new dependency (e.g moment.js):

bower install momentjs --save

Then add script tag into into index.html:

<script src="bower_components/momentjs/moment.js"></script>

Note: If your app has JS errors after running git pull to retrieve others' changes, it is possible that another member of your team has added a bower dependency. To fix this:

bower install

Note: generator-angular's grunt tasks will attempt to keep the script tags in index.html up to date with the bower dependencies.

Also see: egghead.io Bower video

Sass, Compass and Bootstrap

What are Sass, SCSS, Compass and Bootstrap? Click the links above to find out.

Sublime Text 3

Sublime Text 3 is an extensible text editor that's ideal for Angular development. We recommend installing the Package Control plugin to make installing third-party extensions easier.

Note that Package Control is invoked with the Crtl+' not Ctrl+` as stated_

Use Crtl+Shift+P and type install. Hit enter when you see: Package Control Install. Use Package Control to install the following:

  • Editor Config
  • JSHint Gutter
  • Emmet

Editor config

Editor config is a module available for most text editors and IDEs it allows you to set basic formatting rules (tabs, line endings etc) in an editor agnostic way. The remove trailing white space is very handy if JSHint is set to be white space sensitive.

The settings are controlled by the .editorconfig file.

JSHint

JSHint will assist with code quality alerting you to common errors in the editor margin.

Its settings are controlled by the .jshintrc file. They can be overridden with comments in JavaScript.

To run jshint on the command line:

grunt jshint

Generator-angular

Try each of the following commands and answer the following questions:

  • What happens to test/spec?
  • Which command changes app.js and views/?
  • How is index.html changed?
yo angular:controller 'poddington-pea-ctrl'
yo angular:route clanger
yo angular:factory flump
yo angular:directive 'flower-pot-man'

The egghead.io site is a great place to learn more about Angular controllers, directives, factories and more.

We suggest that you start with the beinding tutorial, then keep clicking the next button to get to subsequent lessons.

Worth reading:

  • Angular Constants, Values, Factories, Services, Providers and Decorators, Oh My!
  • Angular docs on providers

Unit testing with Karma and Jasmine

Karma is a test runner and Jasmine is a BDD test framework.

In karma.config.js set:

autoWatch: true, //change to true
singleRun: false

Because karma.config has singleRun set to false, Karma will not exit but rather continue to watch all the files listed in the files property and run all the tests when any of them are modified. To run tests, run the following command:

karma start
# this is short hand for:
# karma start karma.config.js

If you get an error saying command karma not found, run this command to resolve the issue:

npm install -g karma-cli

Node CLI packages are wrappers that look for a local version of a command in a projects node_modules folder. This allows you to use a different version of the command in each project but still access it from the command line.

You may be wondering what the Debug button does. If you press it, you will be taken to a blank page (not very helpful). However, if you open the web inspector (Ctrl + Shift + i) while viewing the blank page, you will see the console.log output.

In a test you can add a value, object or function to window and inspect it after the tests have run. You can also add the command debugger in a test. This will allow you to step through code in the Chrome debugger (you will need to refresh the debugger window with the inspector open).

Making templates available in tests

Unit tests do not allow HTTP requests. Often, we want to use a template URL in directives to avoid writing HTML as concatenated strings. If we do this, we need to pre-cache the templates in the template cache.

karma.conf:

preprocessors: {
  'app/views/**/*.html': ['ng-html2js'],
  'test/views/**/*.html': ['ng-html2js'],
},
ngHtml2JsPreprocessor: {
  prependPrefix: 'served/',
  cacheIdFromPath: function(filepath) {
    return filepath.replace(/app/|.tmp//,'');
  },
  // or define a custom transform function
  moduleName: 'templates'
},

Install karma-ng-html2js-preprocessor:

npm install karma-ng-html2js-preprocessor --save-dev

Prime the cache at by adding templates module to each spec file:

beforeEach(module('otherModules','templates'));

Testing power-ups
(CoffeeScript, jasmine-given & jasmine-stealth)

Jasmine is great, but it can be a bit verbose (you can learn Jasmine syntax here). Test Double, the authors of jasmine-given, recommend writing tests in CoffeeScript. To do this, edit the karma.conf file:

preprocessors: {
  '/**/*.coffee': ['coffee'], //add this line
  //...
}
files: [
  // ...
  'test/spec/**/*.js',
  'test/spec/**/*.coffee', //add this line
  // ...
]

Then run the following command:

npm install -save-dev karma-coffee-preprocessor

jasmine-given allows us to write tests in the GIVEN, WHEN, THEN format. It does not require CoffeeScript, but CoffeeScript makes it even more readable. jasmine-stealth adds a few helper functions which will help keep our test readable. To install we will use bower:

bower install jasmine-given jasmine-stealth --save-dev

Edit the karma.conf file:

 files: [
  'app/bower_components/jasmine-given/dist/jasmine-given.js', //add
  'app/bower_components/jasmine-stealth/dist/jasmine-stealth.js', //add
  'app/bower_components/angular/angular.js',
  //...,
],
}

Now, if we create the following spec it should run:

# test/spec/foo.coffee
describe "assigning stuff to this", ->
  Given -> @number = 24
  Given -> @number++
  When -> @number *= 2
  Then -> @number == 50
  # or
  Then -> expect(@number).toBe(50)

Below is an example of a directive unit test using jasmine-given:

To see this example as JavaScript copy and paste into Try CoffeeScript (very helpful for learning CoffeeScript).

describe "Directive: twinDollarInput", ->
  Given -> module('emcdApp', 'templates')
  Given -> @markup = """
  <twin-dollar-input
    ng-model="pence"
    maxlength="{{max}}"
    decimal-label="Amount in cents"
    symbol="£"
  >
  </twin-dollar-input>
  """
  Given ->
    inject ($rootScope, $compile) =>
      @scope = $rootScope.$new()
      @compile = $compile
  describe 'input should display pence', ->
    Given -> @scope.pence = '100'
    Given -> @scope.max = '11'
    Given -> @subject = angular.element(@markup)
    Given -> @compile(@subject)(@scope)
    Given -> @scope.$digest()
    Given -> @input = @subject.find('input')
    Given -> @formatAmountEl = @subject.find('.simple-pattern--format-amount')
    describe 'input should display cents', ->
      Then -> @input.val() == '100'
    describe 'left should show amount in dollars', ->
      Then -> @formatAmountEl.text() == '$1.00'
    describe 'updating cents input should update dollars', ->
      When -> @input.val(400)
      When -> @input.triggerHandler('change')
      Then -> @formatAmountEl.text() == '$4.00'
    describe 'dollar amount shoud split thousands by commas', ->
      When -> @input.val(500000000)
      When -> @input.triggerHandler('change')
      Then -> @formatAmountEl.text() == '$5,000,000.00'
    describe 'should trunate input to value to maxlength', ->
      When -> @input.val(100000000000000000000)
      When -> @input.triggerHandler('change')
      Then -> @formatAmountEl.text() == '$100,000,000.00'
      And -> @input.val().length == 11
    describe 'change maxlength should trunate value to maxlength', ->
      When -> @input.val(100000000000000000000)
      When -> @input.triggerHandler('change')
      When -> @scope.max = 3
      When -> @scope.$digest()
      Then -> @formatAmountEl.text() == '$1.00'
      And -> @input.val() == '100'
    describe 'change model should update view', ->
      When -> @scope.pence = '10000'
      When -> @scope.$digest()
      Then -> @formatAmountEl.text() == '$100.00'
      And -> @input.val() == '10000'

Alternatives

Currently usedWorth investigating
yeomanlineman
generator-angularlineman-angular
(gives: testem, jasmine-given, jasmine-stealth)
jasminejasmine && jasmine-given && jasmine-stealth
mocha
gruntbroccoli || gulp || make
karmatestem
Sublime Text 3WebStorm || Atom
Bowerbrowserify
Compasslibsass
sassstylus || less
be aware of BEM
BootstrapFoundation 5

Phil Holden
Tags: angular
July 2nd 2014

  • Top
  • Home
  • People
  • Events
  • Social
  • Products
  • Jobs
  • Edinburgh
  • Blog
  • Contact
  • NCR Global
  • Home
  • NCR Global