Getting Started with Mobile App Development with PhoneGap + Yeoman + AngularJS + Ionic

When I was trying to find information about how to get started with mobile app development with PhoneGap and Angular, I had difficulties on finding a good step by step tutorial. This is the tutorial I wish I had:

You will learn how to:

  • Set up the development environment in OS X for development for iOS and Android.
  • Set up PhoneGap and necessary emulators
  • Integrate with an AngularJS application with Yeoman scaffolding and Grunt automation.
  • Style with Ionic, a lightweight framework for mobile development that integrates very well with AngularJS.


Installing PhoneGap and emulators


Let’s start by installing PhoneGap from npm.

sudo npm install -g phonegap

To develop for iOS you just need Xcode installed (download from the App Store). If you didn’t have the command line tools you will also need them (xcode-select --install)

To develop for Android, you need to install Android SDK. It depends on Ant, so let’s install that first:

brew install ant

brew install android-sdk

You might need to add export ANDROID_HOME=/usr/local/opt/android-sdk to your ~/.bash_profile and run source ~/.bash_profile.

Now we can open the SDK with this command:


By default it will have selected the most necessary packages, so just go ahead and click the Install 12 packages… button. When finished installing it should look like this:

Screen Shot 2014-04-13 at 22.11.15

Now we have the necessary tools to create our first Android Virtual Device (AVD). I’m going to create the first one from command line, and later on I’ll create a second one with the help of the android app.

To create from command line, we need to know the target (Android version), and the available ABIs (hardware/CPU architecture). To do that run:

android list targets
Available Android targets:
id: 1 or "android-19"
Name: Android 4.4.2
Type: Platform
API level: 19
Revision: 3
Skins: HVGA, QVGA, WQVGA400, WQVGA432, WSVGA, WVGA800 (default), WVGA854, WXGA720, WXGA800, WXGA800-7in, AndroidWearRound, AndroidWearSquare
Tag/ABIs : android-wear/armeabi-v7a, default/armeabi-v7a, default/x86

You will get a list of targets. Find the one with the latest Android version. Your target will be the id. Have a look to the related ABIs. Apparently most phones out there use the ARM architecture, so choose default/armeabi-v7a. Now you can run:

android create avd --name MyAndroid-4.4.2 --target 1 --abi default/armeabi-v7a

Now we are ready to create our PhoneGap app:

phonegap create my-app

Let’s run it in the Android emulator that we just created:

cd my-app
phonegap run android

Screen Shot 2014-04-14 at 12.22.45

If you see the DEVICE IS READY text, means that everything is working so far.

Now let’s do the same for iOS. We need to install this two npms:

sudo npm install -g ios-deploy

sudo npm install -g ios-sim

And now let’s run the app in the emulator:

phonegap run ios

Screen Shot 2014-04-14 at 12.24.55

Testing in different devices

You probably want to test your app in different devices. To do that for iOS, you just need to go to the iOS Simulator > Hardware > Device and select your device.

If you need to test in different iOS versions, you need to download them from Xcode > Preferences > Downloads, and you will see the option when selecting your device.

For Android, you need to create new AVDs. Let’s do it this time from the android interface, which makes easier to select options:

android avd

Screen Shot 2014-04-13 at 20.52.21

Click New and fill up the details. Example:

Screen Shot 2014-04-13 at 20.55.40

Click OK.

Now if we want to use this AVD, we need to have it open.

Back in the list, elect the desired device and click Start.

You can close the avd window, but leave open the emulator. When you run

phonegap run android

The app should be deployed to the opened emulator

Screen Shot 2014-04-14 at 12.19.10

Testing in real devices

At some point you will also want to test your app in a real device.

To install your app in an iOS device, you need to be member of the Apple’s iOS Developer Program, which costs $99/year, so for now we will restrict our real life testing in an Android. To do that you just need to connect your device via USB, and enable USB debugging under Options > Developer Options. On Android 4.2 and newer, Developer options is hidden by default. To make it available, go to Settings > About phone and tap Build number seven times. Return to the previous screen to find Developer options. Then make sure you don’t have open any emulator, and run phonegap run android. The app should install and open in your device.

Creating the Angular app

Now that we have PhoneGap working with emulators, let’s create our AngularJS app. We will use Yeoman to help with the scaffolding, Grunt to help with automated task, and Bower to take care of front-end packages.

sudo npm install -g yo bower grunt

To scaffold AngularJS apps, Yeoman has a generator that we need to install too

sudo npm install -g generator-angular

Now in the current directory (where PhoneGap is) we can simply run:

yo angular
Would you like to use Sass (with Compass)? Yes
Would you like to include Bootstrap? No
Which modules would you like to include? // Deselect all of them.

We can install the modules when we need them with bower install angular-<module>.
Now we have the source files of our angular app inside the folder app.

We can open that app in a browser with grunt:

grunt serve

And a new browser window should open with a content like this:

Screen Shot 2014-04-14 at 12.52.19

It looks like is without styles, but that’s normal because we didn’t include Bootstrap, and Yeoman relies on it for this welcome screen.

Now let’s try that in our emulators. Grunt will build the app by default in a dist folder, but we need it to be built in the www folder from Phonegap. For that we need to change the Gruntfile.js: replace the line

dist: 'dist'


dist: 'www'

And to avoid this folder to be pushed in github, open .gitignore and replace dist with www.

We also need to protect the config.xml file inside the www folder, or it would be deleted in the build process. For that, we move it to the app folder, and then in the Gruntfile.js we add it to the copy task under dist: > files: > src:


And we will also protect the res folder, where the icons and splash screens for the different platforms are. Also move that folder to app and add to the copy task src list:


We should clean up the res folder and the config.xml and leave only the platforms that we are going to build for. We could also automate the creation of the icons with different sizes based on the same SVG source file using grunt rasterize, but for now let’s move on.

Building the app

We are ready to build the app from the source code to the files that will serve as source for building the different platforms. Run:

grunt build

and now you should get the files compiled and compressed in the www folder. Let’s test how it looks in our emulator:

phonegap run ios

Screen Shot 2014-04-15 at 14.53.53

Great! we managed to get our app running in the device.

Adding PhoneGap Functionality

Now let’s add some PhoneGap functionality to see if works.

First we need to add phonegap.js to our index file. Add below

<!-- build:js scripts/vendor.js -->
<script src="phonegap.js"></script>

This file will be generated by PhoneGap when building the app, so if we open the app in the server, we will get an 404 error. To avoid this simply add an empty phonegap.js file to the app folder.

Now we need to bootstrap angular but only when the device is ready. To do that we need to remove the ng-app="myApp" attribute from the body of the index.html file, so Angular doesn’t get started automatically, and then run it manually when the ‘deviceready’ event gets fired. If the app is run in the browser, we want to bootstrap Angular anyway so we get to see our code (‘deviceready’ event doesn’t fire in the browser). To do this add this code to your app.js file, below angular.module('myApp', []);:

var PhoneGapInit = function () {
  this.boot = function () {
    angular.bootstrap(document, ['myApp']);

  if (window.phonegap !== undefined) {
    document.addEventListener('deviceready', function() {
  } else {
    console.log('PhoneGap not found, booting Angular manually');

angular.element(document).ready(function() {
  new PhoneGapInit();

Now the page should still be loading as it did before, in browser of if we build and run into the emulator.

Adding the Ionic Framework

Lets add Ionic framework. At the time of writing, the bower repository didn’t have the latest version 1.0 as default (probably because it was still in beta), so we would need to specify the version manually:

bower install ionic#1.0.0-beta.1 --save

If it prompts to choose the Angular version, choose the latest.

Now to see if it is working, let’s add a ionic directive to views/main.html. Replace the content for:

  <h1 class="title">My Sample App</h1>

You can check the different directives, and here the different styles.

Before Angular can interpret this new directive we need to include ‘ionic’ as dependency in app.js:

  .module('myApp', [

If we run grunt serve, we will see that the browser is rendering a header with the ionic styles.

Screen Shot 2014-04-16 at 22.53.25

Notice that because we are using bowerInstall in our Gruntfile.js, when we run grunt serve, the ionic css and js files get included in index.html automatically. You can check the index.html source code after running grunt serve.

Using PhoneGap Plugins

Now let’s use some native capabilities of our devices using the PhoneGap plugins. Let’s take for example Geolocation.

First thing we need to do is install the plugin.

phonegap local plugin add org.apache.cordova.geolocation

Now we can use it in the MainCtrl that we already had from the Yeoman welcome code at scripts/controllers/main.js. Replace the content with:

    .controller('MainCtrl', function ($scope) {
  var onSuccess = function(position) {
    $scope.position = position;
  var onError = function(error) {
    console.log('ERROR! code: ' + error.code + ' ' + 'message: ' + error.message);
  navigator.geolocation.getCurrentPosition(onSuccess, onError, {timeout: 10000, enableHighAccuracy: true});

And then read the position values in the template. Add to main.html:

<ion-content padding="true">
  <b>My geolocation:</b> <br />
  Latitude: {{position.coords.latitude}} <br />
  Longitude: {{position.coords.longitude}} <br />
  Altitude: {{position.coords.altitude}} <br />
  Accuracy: {{position.coords.accuracy}} <br />
  Altitude Accuracy: {{position.coords.altitudeAccuracy}} <br />
  Heading: {{position.coords.heading}} <br />
  Speed: {{position.coords.speed}} <br />
  Timestamp: {{position.timestamp}} <br />

If we try this code it won’t work as expected, because the success callback happens within PhoneGap framework and outside Angular framework. For the scope to work, we need to force Angular to evaluate it. To do that, we need to inject $rootScope and call $apply:

    .controller('MainCtrl', function ($scope, $rootScope) {
  var onSuccess = function(position) {
    $rootScope.$apply(function() {
      $scope.position = position;
  var onError = function(error) {
    console.log('ERROR! code: ' + error.code + ' ' + 'message: ' + error.message);
  navigator.geolocation.getCurrentPosition(onSuccess, onError, {timeout: 10000, enableHighAccuracy: true});

Now the app in the browser should give us some information (if you are in Chrome, make sure you accept the browser notification to track location):

Screen Shot 2014-04-17 at 01.16.09

And if we build and run in the emulators it should work too:

Screen Shot 2014-04-17 at 01.20.02

In Android, the emulator don’t read GPS values, so we need to send them via command line. We need to start a telnet session in the port that the emulator is running (you can check the port in the emulator window title, the number at the beginning, in my case 5554). Find here the complete list of commands:

telnet localhost 5554

and then run the command:

geo fix -122.4 37.78

After that you can do:

phonegap run android

and the location should work:

Screen Shot 2014-04-17 at 03.29.05

If you close the app you need to re-send the geolocation, so if it doesn’t work, just run the geo fix command just after opening the app, before the timeout event fires.


What about other platforms

Since we are using PhoneGap, same code base should work for all the supported platforms with very little modifications. For some platforms you need a specific operating system. For example for iOS you need OS X, and for Windows Phone 8 you need Windows. In my case since I’m in OS X to build for Windows Phone 8 I’d need to do it through virtualisation with VirtualBox.

The other alternative is using PhoneGap Build, which compiles the app for you in the cloud. To do that you can just run:

phonegap remote login -u -p mypassword
phonegap remote build ios


Next Steps

This should give you the fundamental blocks to build our hybrid mobile app. From here the sky is the limit. I recommend to read the documentation for the PhoneGap plugins and Ionic.

Some things that you should do at some point moving forward is:

  • Remove old browser compatibility: [if IE] checks, es5-shim and json3 dependency from Bower
  • Remove Google Analytics
  • Show a loading notification until the geolocation success callback is not fired.
  • Clean up res folder, and automate creation of images from a SVG source.
  • Add tests.

You can find the code for this tutorial in Github.

38 Responses to “Getting Started with Mobile App Development with PhoneGap + Yeoman + AngularJS + Ionic”

  1. Ricky Boyce

    Epic post Carrera, just what i was looking for at the time. Going to finish reading tomorrow

  2. Grsmto

    Thanks for this tutorial, great ressource!
    Just a note, your npm commands should not need “sudo” to work.

    • Jesús Carrera

      If you install something with the -g flag, it usually requires sudo to work

  3. Rob

    Beautiful tutorial. This is just what I was looking for.

  4. Ahmed

    Thanks for that awesome tutorial, I found it very useful! I was wondering what you think would be a good backend to use to complement this AngularJS + Phonegap combination. Thanks!

    • Jesús Carrera

      Do you mean, backend in the app to store data? probably HTML5’s localStorage (both PhoneGap and AngularJS have plugins)

  5. Serge van den Oever [Macaw]

    Hi Jesús, nice post! It is the exact same framework stack that I’m currently using with some minor adjustments:

    – I’m using the cordova tooling instead of the phonegap tooling. Phonegap brings you the extra mile using their build services, but it is slow compared to building on Mac/Windows, and it only supports a white-list of supported plugins. The list of plugins is growing, so this is good, but still…
    – I’m using Typescript ( as programming language instead of plain Javascript. It is very close to Javascript (any Javascript IS Typescript) but it provides type safety and a lot of checks which prevents a lot of common errors. It compiles to plain Javascript that looks almost the same as your Typescript.

    During development I work with a native Android device (I bought a Nexus 10 and upgraded it to Kitkat 4.4.2 – essential, I come to that later) because it is so much faster than the emulator interpreting arm code on an intel processor. You can improve speed by adding an x86 based emulator (also through AVD) and using Intel HAXM use latest version if working on Windows 8.1, otherwise your computer freezes from time to time). Another great (and fast) emulator is GenyMotion (, but in the end nothing beats a native device.

    Why is using Android with Kitkat 4.4.2 so important during development? Fire up the Chrome browser, open the uri chrome://inspect and you can inspect the browser window used as render surface within the native app host. You can do complete remote debugging: inspect the DOM, debug you Javascript. See for all the magic. Exit Weinre! ( On iOS you can do a similar thing from your Mac Safari browser ( but in my experience it is really buggy. On Windows Phone development you are stuck with Weinre ( As far as I can see even the new WP 8.1 release has no support for remote debugging like Android and iOS has.

    Another thing we did was creating a bootstrapper.html page that is the entry point for the cordova/phonegap app, this bootstrapper unzip a file from the apk or other host application (app for iOS, xap for WP8) to another place on disk, and run the index.html as real starting point of the app from there. This allows for two things: 1. it is now possible tp check from the bootstrapper if there is a newer version of the file, and if there is one expand this package over our old code, in that way updating the actual application independent from the app store. This is similar to “hydration” in Phonegap build ( or “liveUpdate” in the Intel XDK (, or “reload” provided by ( 2. It is now possible to have a kind of “live reload” of your current “web page” in your app on your device on saving code during development. I extended my grunt build script to support this. The latest version of the Intel XDK ( does support this feature as well. Really nice, but the problem is that with Intel XDK you are stuck with their set of included plugins… From the grunt script we copy changed files (after all the required compilation for the Typescript code) using “adb push” to the device, and trigger a reload of the current page.

    I will soon blog about the things we did at

    Last thing: Webstorm 8 ( is a great tool for edting your application code. Great support for AngualrJS, Typescript Intellisense (although I do the compilation of the Typescript with grunt), and grunt support.

    Good luck with your blogging!

  6. Keith Moore

    @jesus – Thanks for posting this great article. I really needed the angular bootstrap solution you presented. Works great!

    @serge – Thanks for posting all the great information. I was unaware of the remote chrome debugging capability. That is so awesome. The other topics you mentioned are very interesting as well. Thanks again!

  7. pihomeserver

    Thanks for this tutorial. After testing many for last three days, i found yours and it works perfect without modifications !
    Very good job :-)

  8. Markus

    If you get stuck at the “device is ready” point and it just wont show, try changing your phonegap.js to cordova.js in your www/index.html
    worked for me, although I am still trying to figure out why, since I only installed phonegap, not cordova (also trying to figure out the difference nowadays since apache split them up and made what looks like an extended cordova by releasing phonegap)

    Feel free to explain to me why this happens.. I’m mad curious!

  9. Markus

    Next, if you are installing for the first time and you have trouble at this step:
    sudo npm install -g ios-deploy

    try “sudo make” to see if you have agreed to the terms and conditions for xcode.

    if you haven’t the install will fail due to make not running

  10. Markus

    If you get stuck at “grunt serve” make sure you have installed compass as this may block the progress

  11. Markus

    Should this:
    “Find the config: task and add to src:”
    “Find the copy: task and add to src:”
    ? Also, feel free to not approve all my comments, I hope I’m more helpful than annoying.. :)

    • Jesús Carrera

      The config: task is inside the copy: task. As that paragraph is talking about the copy task I think that should be clear enough

  12. Markus

    For some reason the app isn’t always updated when building it for ios.
    Any idea what the reason for that could be?

    • Jesús Carrera

      Are you doing grunt build before phonegap run?

      • Markus

        Yep, for some reason it doesn’t always update and I also seem to have issues for ionic icons to show, when running it on PG ios.
        Not sure if this is related.

  13. Russell

    Thanks! Saved me hours on my first time setup. Best article I found so far.

  14. nathan

    There is no config: parameter in my gruntfile.

    • Jesús Carrera

      it’s under the copy task if you are using yeoman

      • matt

        my copy task also doesn’t have config. Here is my copy:
        copy: {
        dist: {
        files: [{
        expand: true,
        dot: true,
        cwd: ”,
        dest: ”,
        src: [
        }, {
        expand: true,
        cwd: ‘.tmp/images’,
        dest: ‘/images’,
        src: [‘generated/*’]
        styles: {
        expand: true,
        cwd: ‘/styles’,
        dest: ‘.tmp/styles/’,
        src: ‘{,*/}*.css’

        • Jesús Carrera

          You are right, it’s under dist: > files: > src:

          Thanks for pointing it out. I updated the article

      • nikolal

        I don’t have it either. I have
        copy –> dist –> files…
        copy –> styles…

        • Jesús Carrera

          Yes, that was an error. It’s under dist: > files: > src: I just updated the article

  15. Francisco Castro

    Hi! Thank you for the excellent tutorial. I´m having problems including Ionic in the app, could you explain a little more how this works -> “Notice that because we are using bowerInstall in our Gruntfile.js”? It is not working for me, the app doesn’t find ionic module.

    • Jesús Carrera

      Make sure you run “bower install ionic#1.0.0-beta.1 –save” with the –save flag. Then it should be included automatically.

    • ivo

      One other issue I had was that of mismatched angular libraries once I included ionic#beta.something. It included angular 1.3.x but angular-animation-1.2.x & these two were not compatible.

      I used bower to remove ionic & reinstalled with the latest beta (1.0.0.beta14) then it was angular 1.3 and libraries.

  16. leroy

    This helped me a lot…. I think you may have saved me months of research…. Will do the same when I reach your level one day :)

  17. Magnus

    Im stuck at the steps where you add files to copy and move files.

    I changed the dist: from ‘dist’ to ‘www’ at line 21 inside var appConfig={…} Is that correct?
    I also added ‘config.xml’ and ‘res/*’ to copy:dist:files:src: at line 338. But,
    “We also need to protect the config.xml file inside the www folder, or it would be deleted in the build process. For that, we move it to the app folder,…” My config file is located at the same folder level as the www folder, is that the correct one?

    When I run grunt build, it runs two of the tasks but it does not go anywhere. I look in the www folder and its empty. Any idea?

  18. Rutger

    Thanks for this great tutorial, I’m having trouble after I install Ionic though… I can run the ‘grunt serve’ task without errors, but I notice that my ‘main.html’ view is not loading, the console shows no errors.

    When I uninstall Ionic and remove the dependencies, ‘main.html’ loads again. What could be wrong?

  19. ivo

    Thanks for the article!

    I am following it using Cordova. One thing I found was that the cordova.js script include on the HTML page needs to go *before* the vendor.js tag.

    If you place it after the vendor tag like you indicate the js minification step will remove the tag as it attempts to compress it into the final single js file. The cordova.js file though is not present during this minification step, it is supplied by the runtime environment so the tag needs to be present in the final html file.

  20. Phil

    Very good tutorial, thank you so much :)

    My only problem is when I add images to the application, the images only display on a browser (using grunt serve) but not on my Android emulator (Phonegap run android). Did I miss anything?

    Any help would be greatly appreciated.

    • Jesús Carrera

      Make sure you add the images in the images folder, and reference them correctly from the HTML

  21. billy roberts

    Thanks Jesus!! That saved days of work.


Leave a Reply