9 February 2016
How to Start Developing Mobile Apps With AngularJS and Ionic Framework
We’ve been developing mobile applications at Gaslight for years. As is to be expected, our methods have matured in pace with advancements within the domain. We’ve been fans of Cordova for some time. Bus Detective’s mobile app is one example but it’s essentially just a Cordova wrapper of BusDetective.com.
Recently we were approached by a client who is seeking to add off-line data collection to their existing Web platform. The challenge we faced was adding a detailed and very specific workflow to their platform that would needlessly complicate the Web interface. From previous work with this client, we decided it would be beneficial to decompose the existing platform into a number of microservices if we were given the opportunity. That opportunity just arrived.
Ionic Framework
The process we’re modeling for the client requires users to perform off-line data collection in a factory environment. Mobile was the clear choice. After an extensive search for tools that would enable us to leverage existing knowledge and tools while targeting Android and iOS platforms we chose Ionic, a framework for creating mobile apps using HTML, CSS (Sass) and JavaScript (Angular).
Ionic’s “Getting Started” docs are thorough but beware that Node 5 is not yet supported. Having nvm installed is helpful if you’re doing bleeding edge Node development. The TL;DR is run these commands to create a blank Ionic app.
npm install -g cordova ionic
ionic start myApp blank
This results in a bare bones app with an intuitive project file structure.
myApp
├── bower.json
├── config.xml
├── gulpfile.js
├── hooks
├── ionic.project
├── package.json
├── platforms
├── plugins
├── scss
│ └── ionic.app.scss
└── www
├── css
├── img
├── index.html
├── js
│ ├── app.js
│ ├── controllers.js
│ └── services.js
├── lib
└── templates
├── chat-detail.html
├── tab-account.html
├── tab-chats.html
├── tab-dash.html
└── tabs.html
The tree above omits the underlying framework code but even novice Angular
developers should be able to make sense of the file structure and begin hacking
in features. However in doing so, the first problem to arise would be that the
app.js
, controllers.js
and services.js
files would become unwieldy pretty
fast. Immediately, we felt the need to organize our code into concise Angular
modules.
And what about testing? Certainly you’ll want decent test coverage for your app. (I know your client will!) How will you organize that code?
Finally, ionic serve
serves the application from the www
directory, which
made the idea of keeping that directory as clean and tidy as possible very
appealing. We found GulpJS helped us achieve that goal,
ensuring none of the support code made it into the app and gave us flexibility
in managing development environments.
ES6 Modules and Babel
It would probably be easy enough to use RequireJS to help with code organization, but we’ve already been using ES6 modules effectively on other projects. Without finicky browsers to worry about, it seemed like a slam dunk to continue that practice for this project.
We’ve been using Babel for ES6 to JavaScript compilation
on the aforementioned projects and found it simple to integrate with the
gulpfile.js
task (below) for compiling our
Jasmine specs, which are then run via
Karma.
var babel = require("gulp-babel");
var browserify = require('browserify');
var vinylSourceStream = require('vinyl-source-stream');
var vinylBuffer = require('vinyl-buffer');
...
gulp.task("specs", function () {
var sources = browserify({
entries: './specs/specs.js',
debug: true
}).transform(babelify.configure({ presets: ['es2015'] }));
return sources.bundle()
.pipe(vinylSourceStream('specs.js'))
.pipe(vinylBuffer())
.pipe(gulp.dest('./specs/dist'));
});
ES6 modules helped massively with code organization. Here’s the simplified
app.js
file in ES6:
import { default as controllersModuleName } from './controllers/app.controllers';
import { default as servicesModuleName } from './services/app.services';
import { default as routesModuleName } from './routes';
import { default as constantsModuleName } from './constants';
var moduleName = 'app';
var app = angular.module(moduleName, ['ionic', constantsModuleName, controllersModuleName, routesModuleName, servicesModuleName])
.run(function($ionicPlatform) {
$ionicPlatform.ready(function() {
// Hide the accessory bar by default (remove this to show the accessory
// bar above the keyboard for form inputs)
if(window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if(window.StatusBar) {
// org.apache.cordova.statusbar required
StatusBar.styleDefault();
}
});
});
export default moduleName;
Note that the controllers, services, routes and even environment constants are
imported from appropriate locations within the app
directory. We decided
upon a app/<module type>/app.<module type>.js
naming convention for the base
files, which import all modules of that type. For example, here’s our
app/controllers/app.controllers.js
file:
import HomeController from './home.controller';
import WorkOrdersController from './work_orders.controller';
import PerformInspectionController from './perform_inspection.controller';
import StyleguideController from './styleguide.controller';
var moduleName = 'app.controllers';
angular.module(moduleName, [])
.controller('HomeController', HomeController)
.controller('WorkOrdersController', WorkOrdersController)
.controller('PerformInspectionController', PerformInspectionController)
.controller('StyleGuideController', StyleguideController)
export default moduleName;
We followed this pattern for all the app code and features specs but more on the subject of testing in a future post. The end result looks a bit tidier and provides a nice contextual separation between the app code, test specs and built/compiled code.
myApp
├── app
│ ├── controllers
│ ├── scss
│ ├── services
│ ├── templates
│ ├── app.js
│ ├── constants.js
│ └── routes.js
├── config # Environment configs, e.g., development, staging, etc.
├── hooks
├── node_modules
├── platforms # Native code in here still once the app has been built.
├── plugins
├── specs
├── www # Only compiled and minified assets in here!
│ ├── css
│ ├── img
│ ├── js
│ └── lib
├── config.xml
├── gulpfile.js
├── ionic.project
├── karma-conf.js
├── mock-api.js # ExpressJS mock HTTP service for testing.
├── package.json
├── protractor-conf.js
└── run-features.sh # Script that runs the feature specs.
After some initial tinkering, we’re now pleased with how we have our code organized. We’ve fallen into a pattern of knowing where to place things and how to include them in the Angular app.
Gotchas!
Here are a couple of nagging issues we encountered. Nothing major.
- After pulling a commit in which
package.json
was changed, remembering to run:npm install
- Managing environments is troublesome and we haven’t ironed everything out
just yet, but roughly if we want to target any environment other than
staging we have to run:
gulp --env production ionic build android
Next Steps
In future posts, we will detail testing Ionic-based apps, including feature testing, as well as managing environments from development to production, plus solutions we’ve devised to any other sticking points we encounter along the way. If you have experience developing mobile applications with Angular or Ionic we’d appreciate any feedback you’d care to offer in the comments below.