Building an app based on angularjs, requirejs, jquery, bootstrap.

13 July 2015


Prerequisites

To start building the app you need to install:

  • Node.js
  • npm


Farm App

In this example we're going to create a sample app called "FarmApp", which should ressemble an imaginary farm.

project directory tree

app/js$ tree
.
├── animations.js
├── app.js
├── controllers.js
├── directives.js
├── filters.js
├── main.js
├── routes.js
├── services.js
├── test-main.js
└── views
    └── animal_list
        ├── animal_list.html
        ├── animalList.js
        └── animalListSpec.js
//...


package.json

It's necessary to build the infrastructure of the project first, so for that we can begin with the package.json file, which makes npm manage your node modules.

{
  "name": "farmApp",
  "version": "1.0.0",
  "description": "The great Farm App",
  "author": "The anonymous Developer <myemail@mydomain.com>",
  "readmeFilename": "README.md",
  "directories": {
    "test": "test"
  },
  "repository": {
    "type": "git"
  },
  "devDependencies": {
    "bower": "^1.3.1",
    "grunt": "^0.4.5",
    "grunt-bower-task": "^0.4.0",
    "grunt-cli": "^0.1.13",
    "grunt-contrib-clean": "",
    "grunt-contrib-copy": "",
    "grunt-contrib-less": "",
    "grunt-contrib-uglify": "",
    "grunt-karma": "^0.11.2",
    "jasmine-core": "^2.3.4",
    "karma": "^0.12.37",
    "karma-chrome-launcher": "^0.2.0",
    "karma-coffee-preprocessor": "*",
    "karma-firefox-launcher": "^0.1.6",
    "karma-html2js-preprocessor": "^0.1.0",
    "karma-jasmine": "^0.3.6",
    "karma-junit-reporter": "^0.2.2",
    "karma-ng-scenario": "^0.1.0",
    "karma-phantomjs-launcher": "^0.2.0",
    "karma-requirejs": "^0.2.2",
    "karma-script-launcher": "^0.1.0",
    "protractor": "^2.1.0",
    "shelljs": "^0.2.6"
  },
  "scripts": {
    "pretest": "npm install",
    "test": "karma start karma.conf.js",
    "test-single-run": "karma start karma.conf.js  --single-run",
    "preupdate-webdriver": "npm install",
    "update-webdriver": "webdriver-manager update",
    "preprotractor": "npm run update-webdriver",
    "protractor": "protractor e2e-tests/protractor.conf.js"
  }
}

bower.json

Another component of the necessary infrastructure of the project is the bower.json file, which enables Bower to manage your JS dependencies and libraries during your build.

{
  "name": "farmApp",
  "version": "1.0.0",
  "ignore": [
    ".jshintrc",
    "**/.txt",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "jquery": ">=2.1.3",
    "jquery-ui": ">=1.11.2",
    "angular": ">=1.4.0-rc.2",
    "angular-route": ">=1.4.0-rc.2",
    "angular-resource": ">=1.4.0-rc.2",
    "angular-cookies": ">=1.4.0-rc.2",
    "angular-animate": ">=1.4.0-rc.2",
    "angular-touch": ">=1.4.0-rc.2",
    "angular-scenario": ">=1.4.0-rc.2",
    "angular-mocks": ">=1.4.0-rc.2",
    "angular-sanitize": ">=1.4.0-rc.2",
    "angular-bootstrap" : ">=0.13.0",
    "bootstrap": ">=3.3.2",
    "requirejs": ">=2.1.15",
    "requirejs-text": ">=2.0.10",
    "html5-boilerplate": ">=5.2.0"
  } 

Gruntfile.js

now the gruntfile is the file that Grunt will use to generate your build, Grunt will generate your final JS build folder and it will do some lifecycle steps in the way like invoking tests, minifying and concatenating files for performance improvement, etc. Here you place all your required tasks prior to deployment.

module.exports = function (grunt) {
    var webappDir = './app/';
    var cssDir = webappDir + 'css/';
    var jsDir = webappDir + 'js/';
    var dstDir = webappDir + 'build/';
    var bowerDir = webappDir + 'lib/bower_components/';
    var dstCssDir = dstDir + 'css/';
    var dstJsDir = dstDir + 'js/';
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        clean: [ dstDir ],
        bower: {
            install: {
                options: {
                    targetDir: bowerDir,
                    install: true,
                    cleanTargetDir: false,
                    cleanBowerDir: false,
                    bowerOptions: {}
                }
            }
        },
        uglify: {
            dist: {
                options: {
                    compress: true,
                    report: 'min'
                },
                src: [
                    jsDir + '.js'
                ],
                dest: dstJsDir + 'all.js'
            }
        },
        less: {
            dist: {
                options: {
                    compress: true,
                    yuicompress: true,
                    report: 'min'
                },
                src: [
                    cssDir + '.less',
                    cssDir + '*.css'
                ],
                dest: dstCssDir + 'all.css'
            }
        },
        karma: {
              unit: {
                configFile: 'karma.conf.js'
              }
        }      
}); grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-bower-task'); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-karma'); grunt.registerTask('default', ['bower', 'uglify', karma']); };

index.html

Now we can start with the index.html our main entry point for the application:

<!DOCTYPE html>
<html lang="en" ng-app>
    <head>
        <meta charset="UTF-8">
        <title>Farm app v1.0</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">      
<link rel="stylesheet" href="lib/bower_components/html5-boilerplate/dist/css/normalize.css"> <link rel="stylesheet" href="lib/bower_components/html5-boilerplate/dist/css/main.css"> <link rel="stylesheet" href="lib/bower_components/bootstrap/dist/css/bootstrap.css"> <link rel="stylesheet" href="lib/bower_components/jquery-ui/themes/base/jquery-ui.min.css">
<link href="lib/bower_components/angular-datatables/dist/plugins/bootstrap/datatables.bootstrap.min.css" rel="stylesheet"> <script src="lib/bower_components/html5-boilerplate/dist/js/vendor/modernizr-2.8.3.min.js"></script> </head> <body ng-cloak ng-controller="MainCtrl"> <!--Start Header--> <div id="header" data-ng-include data-src="'partials/template/header.html'"></div> <!--End Header--> <!--Start Container--> <div id="main"> <div class="row">
<!--Start Content--> <div id="content"> <div ng-view></div> </div> <!--End Content--> </div> </div> <!--End Container-->

<!--Start Footer --> <div id="footer" data-ng-include data-src="'partials/template/footer.html'"></div> <!--End Header-->

<script data-main="js/main" src="lib/bower_components/requirejs/require.js"></script>

</body> </html>

here we are pointing to js/main.js file for requireJS.

main.js

now we need to configure requireJS as our main module dependency manager:

'use strict';

require.config({
baseUrl: 'js/', deps: ['jquery'], paths: { jquery: '../lib/bower_components/jquery/dist/jquery.min', angular: '../lib/bower_components/angular/angular.min', angularAnimate: '../lib/bower_components/angular-animate/angular-animate.min', angularCookies: '../lib/bower_components/angular-cookies/angular-cookies.min', angularResource: '../lib/bower_components/angular-resource/angular-resource.min', angularRoute: '../lib/bower_components/angular-route/angular-route.min', angularScenario: '../lib/bower_components/angular-scenario/angular-scenario', angularMocks: '../lib/bower_components/angular-mocks/angular-mocks', angularTouch: '../lib/bower_components/angular-touch/angular-touch',
angularSanitize: '../lib/bower_components/angular-sanitize/angular-sanitize.min', jqueryui: '../lib/bower_components/jquery-ui/jquery-ui.min', bootstrap: '../lib/bower_components/bootstrap/dist/js/bootstrap.min', angularBootstrap: '../lib/bower_components/angular-bootstrap/ui-bootstrap-tpls' },
shim: { 'angular': { 'deps': ['jquery'], 'exports': 'angular' }, 'angularResource': { 'deps': ['angular'], exports: 'angularResource' }, 'angularRoute':{ 'deps': ['angular'] }, 'angularAnimate': { 'deps': ['angular'] }, 'angularCookies':{ 'deps': ['angular'] },
'angularMocks': { deps:['angular'] }, 'angularTouch': { deps:['angular'] }, 'angularScenario': { deps:['angular'] }, 'angularSanitize': { deps:['angular'] }, 'angularBootstrap' : { deps: ['angular', 'bootstrap'] }, 'bootstrap' : {deps:['jquery']} },
priority: [ 'angular' ]

});

//https://docs.angularjs.org/guide/bootstrap window.name = 'NG_DEFER_BOOTSTRAP!';

require([ 'angular', 'app', 'routes' ], function(angular, app, routes) { 'use strict'; var $html = angular.element(document.getElementsByTagName('html')[0]); angular.element().ready(function() { angular.resumeBootstrap([app['name']]); }); });

app.js

now in app.js we register our AngularJS app module:

define([
    'angular',
    'filters',
    'services',
    'directives',
    'views/animal_list/animalList',
    'views/animal_edit/animalEdit',
    'animations',
    'angularRoute',
    'angularCookies',
    'angularAnimate',
    'angularTouch',
    'angularSanitize',
    'angularBootstrap'
    ], function (angular) {
        'use strict';      
return angular.module('farmapp', [ 'ngRoute', 'ngCookies', 'ngAnimate', 'ngTouch', 'farmapp.services', 'farmapp.directives', 'farmapp.filters', 'farmapp.controllers', 'farmapp.animalList', 'farmapp.animalEdit', 'farmapp.animations', 'ngSanitize', 'ui.bootstrap' ]);

});

Here we define our services, filters, directives and controllers.

services.js

Here we build our services that will interact with a backend applicaton server:

define(['angular', 'angularResource'], function (angular) {
    'use strict';  
/ Services / var services = angular.module('farmapp.services', ['ngResource']); services.factory('AnimalService', ['$resource', function($resource){
return $resource('/farmWebApp/rest/animal/:id', { id: "@id" }, { 'getByName': {method: 'GET', params: {name: '@name'}, url: '/farmWebApp/rest/animal/:name'} } ); }]);
return services; });

The services class lists all your services, in this case for sake of brevity I listed only 1 service, but a good practice would be to separate individual services in separate files, as the project can grow huge and lots of lines turns to be hard to maintain.

Controllers

once again remember how our controllers are layered in a way such that their corresponding tests and html view are bundled together in the same folder:

app/js$ tree
.
└── views
    └── animal_list
        ├── animal_list.html
        ├── animalList.js
        └── animalListSpec.js
//...


animalList.js

Here we build our first controller, Animal List, which will be responsible for fetching a list of animals from the service and displaying them nicely in a table.

define(['angular', 'services'], function (angular) {
    'use strict';  
/ Controllers / var animalList = angular.module('farmapp.animalList', ['farmapp.services']);
animalList.controller('AnimalListCtrl', ['$scope', '$filter', 'AnimalService', function($scope, $filter, AnimalService) { // callback for ng-click 'deleteAnimal': $scope.deleteAnimal = function (aId) { AnimalService.remove({ id: aId }); $scope.loadAnimalListTable(); };
$scope.loadAnimalListTable = function() { AnimalService.query(function(data) { $scope.animals = data; }); }; $scope.loadAnimalListTable(); }]);
return animalList; });

animal_list.html

Here we build our first view, Animal List which displays an animal list in an HTML table.

<div class="row">
<a href="#/animal-create" class="btn" role="button" >create animal</a>
</div>
<div class="row">
    <div class="box">
        <div class="box-content">
                <table>
                    <thead>
                        <tr>
                            <th>name</th>
                            <th>class</th>
                            <th>birth</th>
                            <th>gender</th>
                            <th>location</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr ng-repeat="animal in animals">
                            <td><a href="#/animal/{{animal.name}}"> {{animal.name}}</a></td>
                            <td>{{animal.class}}</td>
                            <td>{{animal.birth}}</td>
                            <td>{{animal.location}}</td>
                        </tr>
                    </tbody>
                    <tfoot>
                        <tr>
                            <th>name</th>
                            <th>class</th>
                            <th>birth</th>
                            <th>gender</th>
                            <th>location</th>
                        </tr>
                    </tfoot>
                </table>       
</div> </div> </div>


Next steps: Unit Testing the app!

How to test an angularJS and requireJS app using Karma


comments powered by Disqus