Angular.js

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.
 
 
 
 
 
 
 
 
 

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.

Tip
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>
Tip
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>
Tip
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.

Download
You can download the full source code of this example here: TicTacToe

Sylvain Cloutier

Sylvain has been programming in Java for the past 5 years, mainly in the aerospace industry, as a lead developer of complex web based aircraft wiring systems. He is also one of the organizers of the Java User Group of Quebec City and currently works as a Java developer consultant at CGI.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button