Custom Development

Getting started with Ionic (Part 2: Modularizing Ionic with Browserify)

Jorge Balderas

The first post in this series helps get you started with the Ionic sideMenu starter application. A common problem with AngularJS applications is how to modularize them into separate files and easily manage dependencies among components. Browserify is a popular way to modularize AngularJS. This post walks through the steps needed to modularize the sideMenu Ionic starter application using Browserify and "gulp."


Modularizing sideMenu starter app

If you inspect the sideMenu starter code you will notice that the application logic is contained within 2 files:

  • app.js - includes the app initialization as well as the application routing logic.
  • controllers.js - includes the AngularJS controllers.

To extend the application functionality, you will need to add additional logic to these 2 files which can quickly grow out of proportion depending on your application's complexity. There is nothing that prevents you from creating additional files for new functionality; however, it can become cumbersome for "importing" new js files, specially if you need to use them from multiple files. One way to solve this problem is to use Browserify to build self contained "atomic" modules that can be developed independently of the overall app.

Installing Dependencies

We will use the following packages:

  1. gulp - a build system for automating tasks. It is commonly used for minification and linting of Javascript code. 
  2. browserify - provides a way to "require" modules in the browser by bundling all of your Javascript dependencies.
  3. browserify-shim - makes files that are CommonJS incompatible, compatible so that they can be "required" by browserify. We will use this to be able to require angular and ionic.
  4. napa - allows using npm for installing packages without a package.json file. We will use it for importing AngularJS and Ionic as node modules so that they can be bundled with Browserify. 
  5. jshint - a code analysis tool for detecting errors and potential problems with Javascript code. The output is the closest thing to compiler errors you'll get from Javascript code.

We recommend installing gulp and napa globally (-g) so that it can be run from any path:

sudo npm install -g gulp
sudo npm install -g napa

Next install the dev dependencies by running the following from the "myApp" folder (created via steps in the "Part 1" post):

npm install -D browserify browserify-shim gulp-jshint vinyl-source-stream

This will add the following 4 devDependencies (highlighted in bold) to the package.json file:

"devDependencies": {
 "bower": "^1.3.3",
 "browserify": "^10.2.3",
 "browserify-shim": "^3.8.8",
 "gulp-jshint": "^1.11.0",
 "gulp-util": "^3.0.1",
 "shelljs": "^0.3.0",
 "vinyl-source-stream": "^1.1.0"
}

Manually modify package.json to add napa dependencies:

"scripts": {
 "install": "napa"
},
"napa": {
 "angular": "angular/bower-angular#v1.3.13",
 "ionic": "driftyco/ionic-bower#v1.0.0"
}

The scripts part is for running napa when npm install is executed. The second part adds angular and ionic as napa dependencies.

Next add browserify configuration to package.json:

"browser": {
 "ionic": "./node_modules/ionic/js/ionic.js",
 "angular": "./node_modules/angular/angular.js"
},
"browserify": {
 "transform": [
  "browserify-shim"
 ]
},
"browserify-shim": {
 "angular": "angular",
 "ionic": "ionic"
}

These 3 sections provide an alias to angular and ionic so that they can be "required" and resolved when building the bundle with browserify.

For verification purposes you may compare your modified package.json with this: https://github.com/yortch/modularIonicDemo1/blob/master/package.json

Finally execute npm install, which will install all of the dependencies in the package.json that have not been installed previously. This may take several minutes depending on your network connection. A "node_modules" will be created with a folder for each dependency.

Modularizing code

We will start by creating a modules folder under www/js. Within the module folder create the following folders that are part of the sideMenu app: browse, login, menu, playlists and search.

Move html files

Move the html files from the templates folder into their corresponding module folder. playlists.html and playlist.html can be moved within the playlists folder because they can be considered part of the same module as they have a master/detail relationship.

Break out controllers.js

controllers.js effectively contains controller code for 2 modules: login and playlists. You will create 2 controller files: "login-contoller.js" and "playlists-controller.js" and copy the corresponding code as shown in these 2 files:

https://github.com/yortch/modularIonicDemo1/blob/master/www/js/modules/login/login-controller.js

https://github.com/yortch/modularIonicDemo1/blob/master/www/js/modules/playlists/playlists-controller.js

The next step is to create the module files that allow exporting the module and angular dependencies (e.g. service, controller, etc.) so that they can be required from other modules. I named these files after the module folder, e.g. login.js, playlists.js

'use strict';

module.exports = angular.module('login', [])
   .controller('LoginController', require('./login-controller'));

Break out app.js into app.js, router.js and app-main.js

app.js contains the logic to initialize the application. This will be moved into app-main.js as follows:

https://github.com/yortch/modularIonicDemo1/blob/master/www/js/app-main.js

Next move the router logic into router.js:

https://github.com/yortch/modularIonicDemo1/blob/0eb1153ab007776a7d55ad02fc859add4cab0a35/www/js/router.js

Finally your app.js contains a set of "require" statements and modules exported:

'use strict';

require('angular');
require('ionic');

require('./modules/playlists/playlists');
require('./modules/login/login');
require('./modules/menu/menu');

module.exports = angular.module('starter', [
 'ionic',
 'menu',
 'login',
 'playlists'
])
.config(require('./router'))
.run(require('./app-main'));

Updates to index.html

The update to index.html is straightforward. Simply remove the script references to app.js and controller.js and replace them with the import of a bundle.js that will be generated with our gulp script.

<script src="dist/bundle.js"></script>

Gulp tasks

The starter script already includes a gulpfile.js file, we are going to add 2 new tasks to it. First we need to add require statements for browserify and jshint:

//Additional dependencies added for browserify and lint
var jshint = require('gulp-jshint');
var browserify = require('browserify');
var vinylSource = require('vinyl-source-stream');

Next we modify the default task and add 2 new tasks: jshint and browserify:

gulp.task('default', ['lint', 'browserify']);

gulp.task('lint', function() {
 gulp.src(['./www/js/**/*.js'])
  .pipe(jshint())
  .pipe(jshint.reporter('default'))
  .pipe(jshint.reporter('fail'));
});

gulp.task('browserify', function() {
 return browserify('./www/js/app.js', {debug: true})
  .bundle()
  .pipe(vinylSource('bundle.js'))
  .pipe(gulp.dest('./www/dist'));
});

Finally run gulp and if everything was done correctly you should see similar output to what is below:

$ gulp
[23:32:42] Using gulpfile ~/myApp/gulpfile.js
[23:32:42] Starting 'lint'...
[23:32:42] Finished 'lint' after 11 ms
[23:32:42] Starting 'browserify'...
[23:32:46] Finished 'browserify' after 3.7 s
[23:32:46] Starting 'default'...
[23:32:46] Finished 'default' after 11 μs 

More importantly, verify that a bundle.js was created in the www/dist folder

Run and verify

Finally rerun the application with ionic emulate to verify everything works as expected. You should see no changes with respect to the original application. The application code is equivalent to the original with the big difference being that your code is now modular and it will be a lot easier to add new functionality, which I will showcase on part 3 of this series.

 

 

Jorge Balderas
ABOUT THE AUTHOR

Distinguished Technical Consultant