





















































In this article by Matthias Nehlsen, the co-author of the book, AngularJS UI Development, has explained about managing client-side dependencies with Bower. He also explains how to build an application, running Protractor from Grunt, and managing the source code with Git. He will also explain about building AngularUI Utils and integrating AngularUI-Utils into our project.
(For more resources related to this topic, see here.)
We download AngularJS and placed the angular.js file in our directory structure manually. This was not so bad for a single file, but this process will very soon become tedious and error-prone when dealing with multiple dependencies.
Luckily, there is a great tool for managing client-side dependencies, and it can be found at http://bower.io.
Bower allows us to record which dependencies we need for an application. Then, after downloading the application for the first time, we can simply run bower install and it will download all the libraries and assets specified in the configuration file for us.
First, we will need to install Bower (potentially with sudo):
# npm install -g bower
Now, let's try it out by running the following command:
# bower install angular
You will notice an error message when running the previous command on a machine that does not have Git installed.
Okay, this will download AngularJS and place the files in the app/bower_components/ folder. However, our remaining sources are in the src/ folder, so let's store the Bower files here as well. Create a file named .bowerrc in the project root, with these three lines in it:
{ "directory": "src/bower" }
This will tell Bower that we want the managed dependencies inside the src/bower/ folder. Now, remove the app/ folder and run the earlier bower install command one more time. You should see the AngularJS files in the src/bower/ folder now.
Now, we said we wanted to record the dependencies in a configuration file so that we can later run bower install after downloading/checking out the application.
Why can't we just store all the dependencies in our version control system? Of course, we can, but this would bloat the repository a lot. Instead, it is better to focus on the artifacts that we created ourselves and pull in the dependencies when we check out the application for the first time.
We will create the configuration file now. We could do this by hand or let Bower do it for us. Let's do the latter and then examine the results:
# bower init
This will start a dialogue that guides us through the initial Bower project setup process. Give the application a name, version number, and description as you desire. The main file stays empty for now. In the module type selection, just press Enter on the first option. Whenever you see something in square brackets, for example, [MIT], this is the default value that will be used when you simply press Enter. When we confirm that the currently installed dependencies should be set as dependencies, AngularJS will automatically appear as a project dependency in the configuration file if you have followed the previous instructions. Finally, let's set the package as private. There is no need to have it accidentally appear in the Bower registry. Once we are done, the bower.json file should look roughly as follows:
{ name: 'Hello World', version: '0.0.0', homepage: 'https://github.com/matthiasn/AngularUI-Code', authors: ['Matthias Nehlsen <mn@nehlsen-edv.de>'], description: 'Fancy starter application', license: 'MIT', private: true, ignore: [ '**/.*', 'node_modules', 'bower_components', 'src/bower', 'test', 'tests' ], dependencies: { angular: '~1.2.22' } }
Now that we have a dependency management in place, we can use AngularJS from here and delete the manually downloaded version inside the js/vendor/ folder. Also, edit index.html to use the file from the bower/ directory. Find the following line of code:
<script src="js/vendor/angular.js"></script>
Replace it with this:
<script src="bower/angular/angular.js"></script>
Now, we have a package manager for client-side dependencies in place. We will use more of this in the next step.
Preparing an application for production environments can be a tedious task. For example, it is highly recommended that you create a single file that contains all the JavaScript needed by the application instead of multiple small files, as this can dramatically reduce page load time, particularly on slow mobile connections with high latency. It is not feasible to do this by hand though, and it is definitely no fun.
This is where a build system really shines. We are going to use Grunt for this. A single command in the Terminal will run the tests and then put the necessary files (with a single, larger JavaScript file) for the application in the dist/ folder. Many more tasks can be automated with Grunt, such as minifying the JavaScript, automating tasks by watching folders, running JsHint (http://www.jshint.com), but we can only cover a fairly basic setup here.
Let's get started. We need to install Grunt first (possibly with sudo):
# npm install –g grunt-cli
Then, we create a file named package.json in the root of our application folder with the following content:
{ "name": "my-hello-world", "version": "0.1.0", "devDependencies": { "grunt": "~0.4.5", "grunt-contrib-concat": "~0.5.0", "grunt-contrib-copy": "~0.5.0", "grunt-targethtml": "~0.2.6", "grunt-karma": "~0.8.3", "karma-jasmine": "~0.1.5", "karma-firefox-launcher": "~0.1.3", "karma-chrome-launcher": "~0.1.4" } }
This file defines the npm modules that our application depends on. These will be installed automatically by running npm install inside the root of our application folder.
Next, we define which tasks Grunt should automate by creating Gruntfile.js, also in the root of our application folder, with the following content:
module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), concat: { options: { separator: ';' }, dist: { src: ['src/js/vendor/*.js','src/js/*.js'], dest: 'dist/js/<%= pkg.name %>.js' } }, copy: { main: { src: 'src/css/main.css', dest: 'dist/css/main.css', }, }, targethtml: { dist: { files: { 'dist/index.html': 'src/index.html' } } }, karma: { unit: { configFile: 'conf/karma.conf.js', singleRun: true } } }); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-targethtml'); grunt.loadNpmTasks('grunt-karma'); grunt.registerTask('dist', ['karma', 'concat', 'targethtml', 'copy']); };
This file contains different sections that are of interest. In the first part, the configuration object is created, which defines the options for the individual modules. For example, the concat section defines that a semicolon should be used as a separator when it merges all JavaScript files into a single file inside the dist/js folder, with the name of the application. It is important that angular.js come before the application code inside this file, which is guaranteed by the order inside the src array. Files in the vendor subfolder are processed first with this order.
The copy task configuration is straightforward; here, we only copy a single CSS file into the dist/css folder. We will do more interesting things with CSS later when talking about CSS frameworks.
The targethtml task processes the HTML so that it only loads the one concatenated JavaScript file. For this to work, we need to modify index.html, as follows:
<!DOCTYPE html> <head> <meta charset="utf-8"> <title>Angular UI Template</title> <link rel="stylesheet" href="css/main.css"> </head> <body data-ng-app="myApp"> <div data-ng-controller="helloWorldCtrl"> <div hello-world name="name"></div> </div> <!--(if target dev)><!--> <script src="js/vendor/angular.js"></script> <script src="js/app.js"></script> <script src="js/controllers.js"></script> <script src="js/directives.js"></script> <!--<!(endif)--> <!--(if target dist)><!--> <script src="js/my-hello-world.js"></script> <!--<!(endif)--> </body> </html>
This, together with the configuration, tells the targethtml task to only leave the section for the dist task inside the HTML file, effectively removing the section that will load individual files.
One might be tempted to think that it will not make much of a difference if one or multiple files need to be retrieved when the page is loaded. After all, the simple concatenation step does not reduce the overall size of what needs to be loaded.
However, particularly on mobile networks, it makes a huge difference because of the latency of the network. When it takes 300 ms to get a single response, it soon becomes noticeable whether one or ten files need to be loaded. This is still true even when you get the maximum speed in 3G networks. LTE significantly reduces latency, so the difference is not quite as noticeable. The improvements with LTE only occur in ideal conditions, so it is best not to count on them.
The karma section does nothing more than tell the karma task where to find the previously defined configuration file and that we want a single test run for now. Next, we tell Grunt to load the modules for which we have created the configuration, and then we define the dist task, consisting of all the previously described tasks.
All that remains to be done is to run grunt dist in the command line when we want to test and build the application. The complete AngularJS web application can then be found in the dist/ folder.
Let's also run Protractor from our grunt task. First, we need to install it as follows:
# npm install grunt-protractor-runner --save-dev
This will not only install the grunt-protractor-runner module, but also add it as a dependency to package.json so that when you, for example, check out your application from your version control system (covered next) on a new computer and you want to install all your project's dependencies, you can simply run:
# npm install
If you follow along using the companion source code instead of typing the source code yourself, you will need to run npm install again in the last step's folder.
Next, edit Gruntfile.js so that from the karma section, it looks as follows:
karma: { unit: { configFile: 'conf/karma.conf.js', singleRun: true } }, protractor: { e2e: { options: { configFile: 'conf/protractor.conf.js' } } } }); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-targethtml'); grunt.loadNpmTasks('grunt-karma'); grunt.loadNpmTasks('grunt-protractor-runner'); grunt.registerTask('dist', ['karma', 'protractor', 'concat', 'targethtml', 'copy']); };
Now, Protractor will also run every single time we call grunt dist to build our application. The build process will be stopped when either the karma or the protractor step reports an error, keeping us from ever finding code in the dist folder that fails tests.
Note that we will need to have both the webdriver-manager and http-server modules running in separate windows for the grunt dist task to work. As a little refresher, these were started as follows:
# webdriver-manager start # http-server –a localhost –p 8000
Both can also be managed by Grunt, but that makes the configuration more complex and it would also mean that the task runs longer because of startup times. These can also be part of a complex configuration that watches folders and runs and spawns all the required tasks automatically. Explore the Grunt documentation further for tailoring an environment specific to your exact needs.
Now that you know how the build system works in general, you may already want to explore more advanced features. The project's website is a good place to start (http://gruntjs.com).
You are probably using Git already. If not, you really should. Git is a distributed Version Control System (VCS). I cannot imagine working without it, neither in a team nor when working on a project myself. We don't have the space to cover Git for team development here in either case; this topic can easily fill an entire book. However, if you are working in a team that uses Git, you will probably know how to use it already. What we can do in is go through the basics for a single developer.
First, you need to install Git if you do not have it on your system yet.
OS X
On a Mac, again the easiest way to do this is using Homebrew (http://brew.sh). Run the following command in the Terminal after installing Homebrew:# brew install git
Windows
On Windows, the easiest way to install Git is to run the Windows installer from http://git-scm.com/downloads.
Linux (Ubuntu)
On Ubuntu, run the following command in the shell:# sudo apt-get install git
Let's initialize a fresh repository in the current directory:
git init
Then, we create a hidden file named .gitignore, for now with only the following content:
node_modules
This tells Git to ignore the hundreds of files and directories in the node_modules folder. These don't need to be stored in our version control system because the modules can be restored by running npm install in the root folder of the application instead, as all the dependencies are defined in the package.json file.
Next, we add all files in the current directory (and in all subdirectories):
git add
Next, we freeze the file system in its current state:
git commit –m "initial commit"
Now, we can edit any file, try things out, and accidentally break things without worry because we can always come back to anything that was committed into the VCS. This adds incredible peace of mind when you are playing around with the code.
When you issue the git status command, you will notice that we are on a branch called master. A project can have multiple branches at the same time; these are really useful when working on a new feature. We should always keep master as the latest stable version; additional features (or bug fixes) should always be worked upon in a separate branch. Let's create a new feature branch called additional-feature:
git branch additional-feature
Once it is created, we need to check out the branch:
git checkout additional-feature
Now, when the new code is ready to be committed, the process is the same as above:
git add .
git commit –m "additional feature added"
We should commit early and often, this habit will make it much easier to undo previous changes when things go wrong. Now, when everything is working in the new branch (all the tests pass), we can go back into the master branch:
git checkout master
Then, we can merge the changes back into the master branch:
git merge additional-feature
Being able to freely change between branches, for example, makes it very easy to go back to the master branch from whatever you are working on and do a quick bug fix (in a specialized branch, ideally) without having to think about what you just broke with the current changes in the new feature branch. Please don't forget to commit before you switch the branch though. You can merge the bug fix in this example back into the master, go back to the feature branch you were working on, and even pull those changes that were just done in the master branch into the branch you are working on. For this, when you are inside the feature branch, merge the changes:
git merge master
If these changes were in different areas of your files, this should run without conflicts. If they were in the same lines, you will need to manually resolve the conflicts.
Using Git is really useful to manage source code. However, it is in no way limited to code files (or text files, for that matter). Any file can be placed under the source control. For example, this book was written with the heavy usage of Git, for any file involved. This is extremely useful when you are trying to go back to the previous versions of any file.
With what we learned about package managing, let's build AngularUI-Utils ourselves before we start using it. We could just download the JavaScript file(s), but it will be more gratifying to do this ourselves. Learning how to use Grunt will also be very helpful in any larger project later on.
For this, first of all, either clone or fork the repository or just download the zip file from https://github.com/angular-ui/ui-utils.
For simplicity, I suggest that you download the zip file; you can find the link on the right-hand side of the GitHub project page. Once we have unpacked the zip file, we first need to install the dependencies. On your command line inside the project folder, run the following commands:
$ npm install $ bower install
This will install the necessary files needed for the build process. Let's first check whether all the tests are passing after the previous commands have run through:
$ karma start --browsers=Chrome test/karma.conf.js --single-run=true
Alternatively, you can also simply run:
$ grunt
The tests are part of the default task specified in gruntFile.js. Take a moment and familiarize yourself with the file by trying to find where the default task is specified. Note that one subtask is the karma:unit task. Try to locate this task further down in the file; it specifies which Karma configuration file to load.
If all the tests pass, as they should, we can then build the suite using the following command:
$ grunt build
This will run the following tasks specified in gruntFile.js:
I highly recommend that you spend some time reading and following through the gruntFile.js file and the tasks specified therein. It is not strictly necessary that you follow along in this article, as simply building the suite (or even downloading it from the project website) would suffice, but knowing more about the Grunt build system will always be helpful for our own projects.
Imagine this: you find an issue in some project and add it to the list of known issues on the GitHub project. Someone picks it up immediately and fixes it. Now, do you want to wait for someone (maybe an automated build system) to decide when it is a good time to publish a version that includes the said fix? I wouldn't; I'd much rather be able to build it myself. You never know when the next release will happen.
In this step, we will take the ui-utils.js file we built in the previous section and use it in a sample project. The UI-Utils suite consists of a large and growing number of individual components. We will not be able to cover all of them here, but the one's we do cover should give you a good idea about what is available.
Now, let's do the following edits:
{ "name": "fun-with-ui-utils", "version": "0.1.0", "devDependencies": { "grunt": "~0.4.1", "grunt-contrib-concat": "~0.3.0", "grunt-contrib-copy": "~0.4.1", "grunt-targethtml": "~0.2.6", "grunt-karma": "~0.6.2" } }
<body ng-app="myApp"> <div ng-controller="helloWorldCtrl"> <h1 hello-world name="name" id="greeting"></h1> </div> <!--(if target dev)><!--> <script src="bower/angular/angular.js"></script> <script src="js/vendor/ui-utils.js"></script> <script src="js/app.js"></script> <script src="js/controllers.js"></script> <script src="js/directives.js"></script> <!--<!(endif)--> <!--(if target dist)><!--> <script src="js/fun-with-ui-utils.js"></script> <!--<!(endif)--> </body>
Dist folder
You will notice that the my-hello-world.js file is still in the dist/js/ folder, despite not being used any longer. You can safely remove it. You could also remove the entire dist folder and run the my-hello-world.js file again:
$ grunt dist
This will recreate the folder with only the require files. Deleting the folder before recreating it could become a part of the dist task by adding a clean task that runs first. Check out gruntFile.js of the UI-Utils project if you want to know how this is done.
Grunt task concat
Note that all JavaScript files get concatenated into one file during the concat task that runs in our project during the grunt dist task. These files need to be in the correct order, as the browser will read the file from beginning to end and it will complain when, for example, something references the AngularJS namespace without that being loaded already. So, while it might be tempting to use wildcards as we did so far for simplicity, we shall name individual files in the correct order. This might seem tedious at first, but once you are in the habit of doing it for each file the moment you create or add it, it will only take a few seconds for each file and will keep you from scratching your head later. Let's fix this right away.
Find the source property of the concat task in Gruntfile.js of our project:src: ['src/js/vendor/*.js','src/js/*.js'],
Now, replace it with the following:src: ['src/bower/angular/angular.js', 'src/js/vendor/ui-utils.js', 'src/js/app.js', 'src/js/controllers.js', 'src/js/directives.js'],
We also need to edit the app module so that it loads UI-Utils. For this, edit app.js as follows:
'use strict';
angular.module('myApp', ['myApp.controllers', 'myApp.directives', 'ui.utils'])
With these changes in place, we are in a pretty good shape to try out the different components in the UI-Utils suite. We will use a single project for all the different components; this will save us time by not having to set up separate projects for every single one of them.
In this article, we have learned about managing client-side dependencies with Bower. We also learned about building an application, running Protractor from Grunt, and managing the source code with Git. Then, later we learned about building AngularUI Utils and integrating AngularUI-Utils into our project.