AngularJS MVC Tutorial – Part 2 – Bower, SASS and Grunt
This example is the part 2 of my MVC tutorial with Angular. I strongly suggest you read the part 1 if you have not as it shows how to do the MVC part of the project.
Table Of Contents
1. Introduction
This part will show you how to integrate Bower for dependency management with Grunt for bundling.
2. Bower integration
2.1. Installation
Bower is a package manager. Just like NPM would do, it allows to manage the dependencies of your projects with a configuration file called bower.json
. To install Bower, you will need to run the following npm command:
npm install -g bower
This will install Bower globally in your environment, not your project:
C:\Users\MyUser\AppData\Roaming\npm\node_modules\bower
By doing so, the Bower client will be available in the command line.
2.2. Initializing a project
The first thing you need to make your project Bower-ready is a bower.json
file. This file contains metadata about your application like licence information, authors, application name, etc., but also contains the dependencies of your project.
In order to create the bower.json
file, you can search on the Internet to find what is needed in it. It simply is a JSON object with predefined properties. Here, I will show you how to use the interactive command line to create the JSON file. You need to go in your project folder, in a command prompt and type:
bower init
This command will launch Bower’s module creation wizard which will ask you a couple of questions.
Depending on the command line tool you use, you might get an error saying Register requires an interactive shell. In this case, you will need to run
bower init --config.interactive
.Once you were guided through the initialization steps, you will get a bower.json
file in the folder where you ran the bower init
command.
bower.json
{ "name": "AngularTutorial", "authors": [ "Sylvain " ], "description": "Angular MVC architecture tutorial for WebCodeGeeks.com", "main": "server.js", "moduleType": [], "keywords": [ "angular", "tutorial", "webcodegeeks" ], "license": "MIT", "homepage": "www.webcodegeeks.com", "private": true }
Now that we are set up with Bower, we can start to add our project’s dependencies. For this example, I used Angular for the MVC part, Bootstrap as the CSS framework and JQuery SVG to draw crosses and circles, so we can add the dependencies using Bower’s command line, simply by typing:
bower install angular --save bower install bootstrap --save bower install jquery-svg --save
Here, the --save
attribute tells Bower to include our dependencies in the bower.json
file. It is a common practice to add the bower_components
to your .gitignore
or other versioning tool ignore files, so by adding the dependencies to the bower.json
file will only require other developers to run:
bower install
Which will retrieve the dependencies automatically.
3. SASS
SASS is an extension to CSS. In my opinion, I don’t see why developers would not pick SASS over CSS as it brings to CSS what was missing before. As this is not a SASS tutorial, I will only show you how you can integrate SASS with Grunt.
In the example attached to this post (see below), you will find in the src
folder another one called scss
. In there, a file named tictactoe.scss
should be found.
tictactoe.scss
$darkBorder: 2px solid lightgray; body { padding-top: 75px; } .cell { border: $darkBorder; } .grid { border: $darkBorder; } .social-logo { width: 10%; height: 10%; }
This SASS file will get compiled into CSS by Grunt.
4. Grunt
Grunt is a tool that allows the developers to automate recurring tasks. It is commonly used to compile (minify, uglify, merge) your project’s JavaScript files. Here, I will show you how I used it to run my unit tests, compile the SASS files into CSS, minify the compiled CSS file, uglify, minify and copy the JS files into one project file, bundle (and clean) the application in a deployment-ready folder and copy Bower’s dependencies in the bundled application.
4.1. Setup
The first thing you need to install is obviously Grunt. In order to have it ready on your project, you need 3 things. First, the grunt-cli
command line tool for Grunt, then Grunt dependencies into your project, and a Grunt file to be executed.
To install Grunt, simply open a command line and type: npm install -g grunt-cli
.
This will make Grunt runnable form any folder using the command line. Then, you need to add the Grunt dependencies in your package.json
file, in your project. Locate the file from the command line and type:
npm install grunt --save
The last thing you need is a Gruntfile.js
file into your project. I personally put it beside the package.json
and bower.json
files. In this file, include the following code:
Gruntfile.js
module.exports = function (grunt) { grunt.initConfig({ }); grunt.registerTask('default', []); }
The first this I want to bring your attention to is the function parameter grunt
. This gives us the API to tell Grunt what to do. The second thing is the call to initConfig()
which will hold the configuration for the different plugins that we will use. Finally, registerTask()
tells Grunt which plugin to run in which order.
Using the command line, navigate to the Gruntfile.js
and type grunt
. It should execute successfully.
C:\Users\MyUser\Desktop\AngularTutorial>grunt Done, without errors.
4.2. grunt-contrib-clean
This Grunt plugin allows to recursively delete a folder. I personally use it to delete the folder in which the application is bundled. First, you need to install it using npm:
npm install grunt-contrib-clean --save
Then, in the Gruntfile.js
, you need to load the plugin by calling the loadNpmTasks()
method:
grunt.loadNpmTasks('grunt-contrib-clean');
Put this little piece of code between the call to initConfig()
and registerTask()
. Now that it will be loaded, it needs to be executed. Still in the Grunt file, locate the call to registerTask()
and add clean
in the array (second parameter). This array should contain the plugins in the appropriated order. It should now look like:
grunt.registerTask('default', ['clean']);
The last thing we need to do is to define the configuration. This plugin has one of the simplest configuration, all you need is to add the following attribute to the object passed to initConfig()
:
clean: ["lib/tictactoe/build"]
Now, if you create a build
folder, you should see that it will get cleaned if you run Grunt. For reference, your Gruntfile.js
should look like:
Gruntfile.js
module.exports = function(grunt) { grunt.initConfig({ clean: ["lib/tictactoe/build"] }); grunt.registerTask('default', ['clean']); }
You can test by executing grunt
from the command line:
C:\Users\MyUser\Desktop\AngularTutorial>grunt Running "clean:0" (clean) task >> 1 path cleaned. Done, without errors. C:\Users\MyUser\Desktop\AngularTutorial>
4.3. grunt-contrib-sass
This plugin allows Grunt to execute SASS on specific paths to SCSS files. The resulting files are defined by the configuration object. As prerequisites, you need to install Ruby and then use gem
to install sass
(see instructions). When those requirements are met, install the Grunt plugin by executing:
npm install grunt-contrib-sass --save
Then, load it in the Gruntfile.js
and set it in the executed plugins:
Gruntfile.js (partial)
grunt.loadNpmTasks('grunt-contrib-sass'); grunt.registerTask('default', ['clean', 'sass']);
Then you need to configure it. The simplest way, in my opinion, is to add all files manually. If you get to a point where you have too many files, you can use another way of configuring the sass
object by referring to their documentation.
Gruntfile.js (partial)
sass: { dist: { files: { 'lib/tictactoe/src/css/tictactoe.css': 'lib/tictactoe/src/scss/tictactoe.scss' } } }
Add this attribute to the object passed to the initConfig()
method in the Gruntfile.js
. The attribute dist
is a target name. Then, you specify a files
attribute to the target
object which will contain the list of paths to SCSS files and their compilation destinations. Those paths are defined as attributes, where the attribute name is the destination file and the value is the source file.
Your Gruntfile.js
should now look like:
Gruntfile.js
module.exports = function(grunt) { grunt.initConfig({ clean: ["lib/tictactoe/build"], sass: { dist: { files: { 'lib/tictactoe/src/css/tictactoe.css': 'lib/tictactoe/src/scss/tictactoe.scss' } } } }); grunt.loadNpmTasks('grunt-contrib-sass'); grunt.loadNpmTasks('grunt-contrib-clean'); grunt.registerTask('default', ['clean', 'sass']); }
By executing this file with Grunt’s command line client, you should get something like:
C:\Users\closy2\Desktop\AngularTutorial>grunt Running "clean:0" (clean) task >> 0 paths cleaned. Running "sass:dist" (sass) task Done, without errors. C:\Users\closy2\Desktop\AngularTutorial>
4.4. grunt-contrib-cssmin
This plugin is used to minify (i.e.: remove all non-necessary characters) CSS files. Here, I also use it to move the compiled CSS file into the bundle folder. Install it by running:
npm install grunt-contrib-cssmin --save
Then, load the plugin and defined it as runnable:
Gruntfile.js (partial)
grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.registerTask('default', ['clean', 'sass', 'cssmin']);
Regarding the configuration, the format is exactly the same as for the SASS plugin:
Gruntfile.js (partial)
cssmin: { dist: { files: { 'lib/tictactoe/build/css/tictactoe.min.css': 'lib/tictactoe/src/css/tictactoe.css' } } }
Your grunt file should now look like:
Gruntfile.js
module.exports = function(grunt) { grunt.initConfig({ clean: ["lib/tictactoe/build"], sass: { dist: { files: { 'lib/tictactoe/src/css/tictactoe.css': 'lib/tictactoe/src/scss/tictactoe.scss' } } }, cssmin: { dist: { files: { 'lib/tictactoe/build/css/tictactoe.min.css': 'lib/tictactoe/src/css/tictactoe.css' } } } }); grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-sass'); grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.registerTask('default', ['clean', 'sass', 'cssmin']); }
And the execution should create a tictactoe.min.css
in the build
folder.
C:\Users\MyUser\Desktop\AngularTutorial>grunt Running "clean:0" (clean) task >> 1 path cleaned. Running "sass:dist" (sass) task Running "cssmin:dist" (cssmin) task >> 1 file created. 218 B → 93 B Done, without errors. C:\Users\MyUser\Desktop\AngularTutorial>
4.5. grunt-contrib-qunit
Another useful thing with Grunt is that it can run our tests suite and stop the build if a test fails. As part of this example, I used QUnit to make my unit tests. Install the Grunt plugin for QUnit by running:
npm install grunt-contrib-qunit --save
Then, like any other plugin, we need to load it and add it to the default
task like so:
Gruntfile.js (partial)
grunt.loadNpmTasks('grunt-contrib-qunit'); grunt.registerTask('default', ['clean', 'sass', 'cssmin', 'qunit']);
Regarding the configuration of the plugin, it is pretty simple to do. All you need is to define a target, as usual, and an array containing the list of the html files containing the QUnit code, like so:
Gruntfile.js (partial)
qunit: { dist: ['lib/tictactoe/test/*.html'] }
As you can see, it accepts the wildcard character *
, meaning that all html files will be executed.
Your Gruntfile.js
should now look like this:
Gruntfile.js
module.exports = function(grunt) { grunt.initConfig({ clean: ["lib/tictactoe/build"], sass: { dist: { files: { 'lib/tictactoe/src/css/tictactoe.css': 'lib/tictactoe/src/scss/tictactoe.scss' } } }, cssmin: { dist: { files: { 'lib/tictactoe/build/css/tictactoe.min.css': 'lib/tictactoe/src/css/tictactoe.css' } } }, qunit: { dist: ['lib/tictactoe/test/*.html'] } }); grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-sass'); grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.loadNpmTasks('grunt-contrib-qunit'); grunt.registerTask('default', ['clean', 'sass', 'cssmin', 'qunit']); }
When you execute the Grunt file, you will see that the tests suite was executed and that all tests passed:
C:\Users\MyUser\Desktop\AngularTutorial>grunt Running "clean:0" (clean) task >> 1 path cleaned. Running "sass:dist" (sass) task Running "cssmin:dist" (cssmin) task >> 1 file created. 218 B → 93 B Running "qunit:dist" (qunit) task Testing lib/tictactoe/test/tests.html .........................OK >> 25 assertions passed (124ms) Done, without errors. C:\Users\MyUser\Desktop\AngularTutorial>
If you use any other test frameworks, you should be able to find a plugin for you. There is one for Jasmine (grunt-contrib-jasmine), Mocha (grunt-mocha-test) and Protractor (grunt-protractor-runner).
4.6. grunt-contrib-copy
The copy plugin of Grunt does what it says, it copies files from one folder to another. There is a bunch of usage options, but I will show you a couple. First, you install the plugin using:
npm install grunt-contrib-copy --save
Again, you load this plugin using the same method as above:
Gruntfile.js (partial)
grunt.loadNpmTasks('grunt-contrib-copy'); grunt.registerTask('default', ['clean', 'sass', 'cssmin', 'qunit', 'copy']);
Then, for the configuration part, you will need to tell which files to move. In this example, I copy from the src
folder to the build
folder all the static files like views, Bower’s dependencies, images, etc..
So first, in the configuration object passed to Grunt, you need to add an attribute for copy
and define a target, then the list of files to copy are passed as an array of objects:
Gruntfile.js (partial)
copy: { dist: { files: [ { expand: false, filter: 'isFile', src: ['bower_dependencies/angular/angular.min.js'], dest: 'lib/tictactoe/build/js/angular.min.js' }, { expand: false, filter: 'isFile', src: ['bower_dependencies/bootstrap/dist/css/bootstrap.min.css'], dest: 'lib/tictactoe/build/css/bootstrap.min.css' }, { expand: false, filter: 'isFile', src: ['bower_dependencies/jquery-svg/jquery.svg.min.js'], dest: 'lib/tictactoe/build/js/jquery.svg.min.js' }, { expand: false, filter: 'isFile', src: ['bower_dependencies/jquery-svg/jquery.svg.css'], dest: 'lib/tictactoe/build/css/jquery.svg.css' }, { expand: false, filter: 'isFile', src: ['bower_dependencies/jquery/dist/jquery.min.js'], dest: 'lib/tictactoe/build/js/jquery.min.js' }, { expand: true, filter: 'isFile', cwd: 'lib/tictactoe/src/views/', src: ['**/*.html'], dest: 'lib/tictactoe/build' }, { expand: true, filter: 'isFile', cwd: 'lib/tictactoe/src/img/', src: ['*'], dest: 'lib/tictactoe/build/img/' } ] } }
Note that you can use the same syntax as the other plugins for file, that is a JSON object were the property name is the destination folder and the value is the source folder. I also want to bring you attention to lines 29-34 and 35-40. The first object has the expand
attribute set to true and the src
has wildcards characters **/*.html
meaning that all files and folders present in cwd
(current working directory) will get copied in the destination folder recursively and by keeping the folder structure.
The other object has expand
set to false and the cwd
attribute has only the wildcard character *
which indicates it will only copy the first level of files.
Your Grunt file should now look like:
Gruntfile.js
module.exports = function(grunt) { grunt.initConfig({ clean: ["lib/tictactoe/build"], sass: { dist: { files: { 'lib/tictactoe/src/css/tictactoe.css': 'lib/tictactoe/src/scss/tictactoe.scss' } } }, cssmin: { dist: { files: { 'lib/tictactoe/build/css/tictactoe.min.css': 'lib/tictactoe/src/css/tictactoe.css' } } }, qunit: { dist: ['lib/tictactoe/test/*.html'] }, copy: { dist: { files: [ { expand: false, filter: 'isFile', src: ['bower_dependencies/angular/angular.min.js'], dest: 'lib/tictactoe/build/js/angular.min.js' }, { expand: false, filter: 'isFile', src: ['bower_dependencies/bootstrap/dist/css/bootstrap.min.css'], dest: 'lib/tictactoe/build/css/bootstrap.min.css' }, { expand: false, filter: 'isFile', src: ['bower_dependencies/jquery-svg/jquery.svg.min.js'], dest: 'lib/tictactoe/build/js/jquery.svg.min.js' }, { expand: false, filter: 'isFile', src: ['bower_dependencies/jquery-svg/jquery.svg.css'], dest: 'lib/tictactoe/build/css/jquery.svg.css' }, { expand: false, filter: 'isFile', src: ['bower_dependencies/jquery/dist/jquery.min.js'], dest: 'lib/tictactoe/build/js/jquery.min.js' }, { expand: true, filter: 'isFile', cwd: 'lib/tictactoe/src/views/', src: ['**/*.html'], dest: 'lib/tictactoe/build' }, { expand: true, filter: 'isFile', cwd: 'lib/tictactoe/src/img/', src: ['*'], dest: 'lib/tictactoe/build/img/' } ] } } }); grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-sass'); grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.loadNpmTasks('grunt-contrib-qunit'); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.registerTask('default', ['clean', 'sass', 'cssmin', 'qunit', 'copy']); }
If you then execute it with grunt
, you should see that your files gets copied in the build
folder.
C:\Users\MyUser\Desktop\AngularTutorial>grunt Running "clean:0" (clean) task >> 1 path cleaned. Running "sass:dist" (sass) task Running "cssmin:dist" (cssmin) task >> 1 file created. 218 B → 93 B Running "qunit:dist" (qunit) task Testing lib/tictactoe/test/tests.html .........................OK >> 25 assertions passed (250ms) Running "copy:dist" (copy) task Copied 11 files Done, without errors. C:\Users\MyUser\Desktop\AngularTutorial>
For Bower components, there is a plugin specially built for this called grunt-bowercopy.
4.7. grunt-contrib-uglify
The last but not the least plugin I want to show you is responsible for the minification of your JavaScript source files. It will merge the files into one single .js
file, minify it (remove all whitespaces, change variable names and functions to one letter, etc.) and copy the resulting file where it’s needed.
First, install the plugin using:
npm install grunt-contrib-uglify --save
Then you load it in the Grunt file (you know how now, don’t you!):
Gruntfile.js (partial)
grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.registerTask('default', ['clean', 'sass', 'cssmin', 'qunit', 'copy', 'uglify']);
For the configuration of the plugin, all you need to specify is a target and list of files is built where the attribute name is the destination path and the value is an array of paths in the include order:
Gruntfile.js (partial)
uglify: { dist: { files: { 'lib/tictactoe/build/js/tictactoe.min.js': [ 'lib/tictactoe/src/tictactoe.js', 'lib/tictactoe/src/modules/TicTacToe.js', 'lib/tictactoe/src/services/DrawingService.js', 'lib/tictactoe/src/services/TicTacToeService.js', 'lib/tictactoe/src/controllers/TicTacToeController.js', 'lib/tictactoe/src/directives/GridDirective.js' ] } } }
For the last time, this should be your Gruntfile.js
:
Gruntfile.js
module.exports = function(grunt) { grunt.initConfig({ clean: ["lib/tictactoe/build"], sass: { dist: { files: { 'lib/tictactoe/src/css/tictactoe.css': 'lib/tictactoe/src/scss/tictactoe.scss' } } }, cssmin: { dist: { files: { 'lib/tictactoe/build/css/tictactoe.min.css': 'lib/tictactoe/src/css/tictactoe.css' } } }, qunit: { dist: ['lib/tictactoe/test/*.html'] }, copy: { dist: { files: [ { expand: false, filter: 'isFile', src: ['bower_dependencies/angular/angular.min.js'], dest: 'lib/tictactoe/build/js/angular.min.js' }, { expand: false, filter: 'isFile', src: ['bower_dependencies/bootstrap/dist/css/bootstrap.min.css'], dest: 'lib/tictactoe/build/css/bootstrap.min.css' }, { expand: false, filter: 'isFile', src: ['bower_dependencies/jquery-svg/jquery.svg.min.js'], dest: 'lib/tictactoe/build/js/jquery.svg.min.js' }, { expand: false, filter: 'isFile', src: ['bower_dependencies/jquery-svg/jquery.svg.css'], dest: 'lib/tictactoe/build/css/jquery.svg.css' }, { expand: false, filter: 'isFile', src: ['bower_dependencies/jquery/dist/jquery.min.js'], dest: 'lib/tictactoe/build/js/jquery.min.js' }, { expand: true, filter: 'isFile', cwd: 'lib/tictactoe/src/views/', src: ['**/*.html'], dest: 'lib/tictactoe/build' }, { expand: true, filter: 'isFile', cwd: 'lib/tictactoe/src/img/', src: ['*'], dest: 'lib/tictactoe/build/img/' } ] } }, uglify: { dist: { files: { 'lib/tictactoe/build/js/tictactoe.min.js': [ 'lib/tictactoe/src/tictactoe.js', 'lib/tictactoe/src/modules/TicTacToe.js', 'lib/tictactoe/src/services/DrawingService.js', 'lib/tictactoe/src/services/TicTacToeService.js', 'lib/tictactoe/src/controllers/TicTacToeController.js', 'lib/tictactoe/src/directives/GridDirective.js' ] } } } }); grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-sass'); grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.loadNpmTasks('grunt-contrib-qunit'); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.registerTask('default', ['clean', 'sass', 'cssmin', 'qunit', 'copy', 'uglify']); }
And the execution result should be something like:
C:\Users\MyUser\Desktop\AngularTutorial>grunt Running "clean:0" (clean) task >> 1 path cleaned. Running "sass:dist" (sass) task Running "cssmin:dist" (cssmin) task >> 1 file created. 218 B → 93 B Running "qunit:dist" (qunit) task Testing lib/tictactoe/test/tests.html .........................OK >> 25 assertions passed (250ms) Running "copy:dist" (copy) task Copied 11 files Running "uglify:dist" (uglify) task File lib/tictactoe/build/js/tictactoe.min.js created: 8 kB → 3.02 kB >> 1 file created. Done, without errors. C:\Users\MyUser\Desktop\AngularTutorial>
5. Conclusion
In this example, I showed you how to integrate Grunt with Bower to copy the dependencies in the bundle folder, I also showed you a couple of useful Grunt plugins, and I quickly showed you how to make Grunt build your SASS files.
In the next part, I will show you how to connect this to a Node.js back end and how we can make this game work in multi-player.
6. Download
This was the second part of AngularJS MVC Tutorial, about Bower, SASS and Grunt.
You can download the full source code of this example here: TicTacToe