Yeoman

A new project with PhoneGap and AngularJS

This is part of a series posts relating to PhoneGap. PhoneGap is an open source framework for quickly building cross-platform mobile applications using HTML5 and Javascript, allowing you to make use of the many open licensed Javascript libraries available on the internet. You also get the benefits of the native features on the targeted device and the simplicity and quick development cycle of a web page. You can write the application once and deploy it to multiple targets with ease.

What is AngularJS?

AngularJS is an open source javascript framework maintained by Google. It allows you to develop static web application using a MVC software architectural pattern. It’s a perfect companion framework for building powerful PhoneGap applications quickly while maintaining organized and reusable code.

AngularJS is perfect for a PhoneGap application where the device might not always be online. In today’s article I will be demonstrating how to setup and build a new AngularJS / PhoneGap project.

Creating a new AngularJS / PhoneGap Project

We will be using Yeoman for generating our project. Yeoman provides a generator ecosystem – a plugin that when run with the `yo` command will scaffold complete projects to create a robust workflow. Read more about Yeoman.

First make sure you have the required software installed: PhoneGap, npm, Ruby, and Compass.

Install the required npm modules.

npm –g install grunt grunt-cli bower yo generator-karma generator-angular 

Use PhoneGap to create a new project and then create a new AngularJS project using Yeoman inside the same folder.

phonegap create new-project
cd new-project
yo angular "New Project"

PhoneGap expects your application to live in the www folder while AngularJS expects the application to live in the app folder. It is easier to change grunt’s application folder than it is to change PhoneGap. In Gruntfile.js, add the following variable to your appConfig declaration.

  // Configurable paths for the application
  var appConfig = {
    app: require('./bower.json').appPath || 'app',
    dist: 'dist',
    phonegap: 'www'
  };

Add a new clean task for PhoneGap:

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/{,*/}*',
            '!<%= yeoman.dist %>/.git{,*/}*'
          ]
        }]
      },
      phonegap: ['<%= yeoman.phonegap %>/*', '!<%= yeoman.phonegap %>/config.xml', '!<%= yeoman.phonegap %>/res'],
      server: '.tmp'
    },

This will clean all of the files in the PhoneGap folder but will leave the res folder with the project’s icons and splash screen images. Now modify the copy task:

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,png,txt}',
            '.htaccess',
            '*.html',
            'views/{,*/}*.html',
            'images/{,*/}*.{webp}',
            'styles/fonts/{,*/}*.*'
          ]
        }, {
          expand: true,
          cwd: '.tmp/images',
          dest: '<%= yeoman.dist %>/images',
          src: ['generated/*']
        }, {
          expand: true,
          cwd: '.',
          src: 'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*',
          dest: '<%= yeoman.dist %>'
        }]
      },
      phonegap: {
        expand: true,
        cwd: '<%= yeoman.dist %>',
        dest: '<%= yeoman.phonegap %>',
        src: '**'
      },
      styles: {
        expand: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

Modify the build task. We will be configuring this task in the style of ‘grunt build:target’ so that you can just run ‘grunt build:phonegap’ to build for PhoneGap.

grunt.registerTask('build', 'build task', function(target) {
      target = target || 'dev';
      if (target === 'phonegap') {
          
          grunt.task.run([
            'clean:phonegap',
            'clean:dist',
            'wiredep',
            'useminPrepare',
            'concurrent:dist',
            'autoprefixer',
            'concat',
            'ngAnnotate',
            'copy:dist',
            'preprocess:phonegap',
            'cdnify',
            'cssmin',
            'uglify',
            'filerev',
            'usemin',
            'htmlmin',
            'copy:phonegap'
          ]);
      } else {
          grunt.task.run([
            'clean:dist',
            'wiredep',
            'useminPrepare',
            'concurrent:dist',
            'autoprefixer',
            'concat',
            'ngAnnotate',
            'copy:dist',
            'preprocess:dist,
            'cdnify',
            'cssmin',
            'uglify',
            'filerev',
            'usemin',
            'htmlmin'
          ]);
      }
  });

Now we can build a web version (grunt build) or a PhoneGap version (grunt build:phonegap) from the command line.

In order for your application to make use of the device’s mobile features we will need to include PhoneGap’s cordova.js file and not bootstrap AngularJS until after the device is ready.

We will use grunt-preprocess to provide different a conditional index.html depending on if your project is built for web or for PhoneGap.

Add the following to Gruntfile.js.

    preprocess: {
        options: {
            context: {
            }
        },
        dist: {
            src: '<%= yeoman.app %>/index.html',
            dest: '<%= yeoman.dist %>/index.html'
        },
        phonegap: {
            options: {
                context: {
                    phonegap: true
                }
            },
            src: '<%= yeoman.app %>/index.html',
            dest: '<%= yeoman.dist %>/index.html'
        }
    },

In index.html, we will need to remove the ng-app directive so that we can control when AngularJS will bootstrap. In the <head> section we will load cordova.js for PhoneGap. The ng-app directly have been removed from the <body> tag and javascript has been added above the </body> closing tag that will wait for PhoneGap to report the device as ready before bootstrapping.

Make the following changes to index.html.

<!doctype html>
<html class="no-js">
  <head>
    <meta charset="utf-8">
    <title></title>
    <meta name="description" content="">
    
    <!-- @ifdef phonegap -->
    <script type="text/javascript" src="cordova.js"></script>
    <meta name="format-detection" content="telephone=no" />
    <meta name="msapplication-tap-highlight" content="no" />
    <!-- WARNING: for iOS 7, remove the width=device-width and height=device-height attributes. See https://issues.apache.org/jira/browse/CB-4323 -->
    <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
    <!-- @endif -->
    <!-- @ifndef phonegap -->
    <meta name="viewport" content="width=device-width">
    <!-- @endif -->
    
    <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
    <!-- build:css(.) styles/vendor.css -->
    <!-- bower:css -->
    <!-- endbower -->
    <!-- endbuild -->
    <!-- build:css(.tmp) styles/main.css -->
    <link rel="stylesheet" href="styles/main.css">
    <!-- endbuild -->
  </head>
  <body>
    <!--[if lt IE 7]>
      <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
    <![endif]-->

    <!-- Add your site or application content here -->
    <div class="header">
      <div class="navbar navbar-default" role="navigation">
        <div class="container">
          <div class="navbar-header">

            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#js-navbar-collapse">
              <span class="sr-only">Toggle navigation</span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
            </button>

            <a class="navbar-brand" href="#/">newProject</a>
          </div>

          <div class="collapse navbar-collapse" id="js-navbar-collapse">

            <ul class="nav navbar-nav">
              <li class="active"><a href="#/">Home</a></li>
              <li><a ng-href="#/about">About</a></li>
              <li><a ng-href="#/">Contact</a></li>
            </ul>
          </div>
        </div>
      </div>
    </div>

    <div class="container">
    <div ng-view=""></div>
    </div>

    <div class="footer">
      <div class="container">
        <p><span class="glyphicon glyphicon-heart"></span> from the Yeoman team</p>
      </div>
    </div>


    <!-- Google Analytics: change UA-XXXXX-X to be your site's ID -->
     <script>
       !function(A,n,g,u,l,a,r){A.GoogleAnalyticsObject=l,A[l]=A[l]||function(){
       (A[l].q=A[l].q||[]).push(arguments)},A[l].l=+new Date,a=n.createElement(g),
       r=n.getElementsByTagName(g)[0],a.src=u,r.parentNode.insertBefore(a,r)
       }(window,document,'script','//www.google-analytics.com/analytics.js','ga');

       ga('create', 'UA-XXXXX-X');
       ga('send', 'pageview');
    </script>

    <!-- build:js(.) scripts/vendor.js -->
    <!-- bower:js -->
    <script src="bower_components/jquery/dist/jquery.js"></script>
    <script src="bower_components/angular/angular.js"></script>
    <script src="bower_components/bootstrap-sass-official/assets/javascripts/bootstrap.js"></script>
    <script src="bower_components/angular-animate/angular-animate.js"></script>
    <script src="bower_components/angular-cookies/angular-cookies.js"></script>
    <script src="bower_components/angular-resource/angular-resource.js"></script>
    <script src="bower_components/angular-route/angular-route.js"></script>
    <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
    <script src="bower_components/angular-touch/angular-touch.js"></script>
    <!-- endbower -->
    <!-- endbuild -->

        <!-- build:js({.tmp,app}) scripts/scripts.js -->
        <script src="scripts/app.js"></script>
        <script src="scripts/controllers/main.js"></script>
        <script src="scripts/controllers/about.js"></script>
        <!-- endbuild -->
        
    <script>


    window.isPhoneGap = function() {
            return (document.URL.indexOf( 'http://' ) === -1 && document.URL.indexOf( 'https://' ) === -1) || document.location.port == 3000;
        };

        // This is a function that bootstraps AngularJS, which is called from later code
        function bootstrapAngular() {
            console.log("Bootstrapping AngularJS");
            // This assumes your app is named "app" and is on the body tag: <body ng-app="app">
            // Change the selector from "body" to whatever you need
            var domElement = document.querySelector('html');
            // Change the application name from "app" if needed
            angular.bootstrap(domElement, ['newProjectApp']);
        }

        // This method of user agent detection also works, though it means you might have to maintain this UA list
        if (window.isPhoneGap()) {
            console.log("UA: Running in Cordova/PhoneGap");
            document.addEventListener('deviceready', bootstrapAngular, false);
        } else {
            console.log("UA: Running in browser");
            jQuery(function($){
                bootstrapAngular();
            });

        }
    </script>
        
</body>
</html>

Using this workflow you can create and test your AngularJS application using your browser by running:

grunt serve

You can test your AngularJS/PhoneGap app by running:

grunt build:phonegap
phonegap build android
If everything worked as expected your device should show the Yeoman scaffold boilerplate.

PhoneGap AngularJS scaffold

Hutz Media Logo

Bryan Wiebe is a web developer and mobile developer located in the Okanagan, British Columbia. He works for Hutz Media Ltd. This post is an entry in a blog series covering development with PhoneGap.

PhoneGap

PhoneGap Developer App

In our previous post I demonstrated how to install PhoneGap, create your first project, and compile and run it on an Android device. Continuing a series of PhoneGap posts, I’m going to demonstrate the most useful PhoneGap tool at your disposal; the PhoneGap Developer App.

What is the PhoneGap Developer App?

PhoneGap Developer App allows you to develop locally and then see the changes on your mobile device instantly. There is no need to re-sign, re-compile, and reinstall your app on a mobile device just to test your code. It even provides access to the device APIs that you can’t test in a web browser.

Downloading the mobile app

The PhoneGap Developer App is available on Android, iOS, and Windows Phone. Download the app to your mobile devices to get started.

The Android version is available in the Google Play Store. It requires Android 4.1+.

The iOS version is available in the App Store. It requires iOS 6.0 or later.

The Windows Phone version is available in the Windows Phone Store. It requires Windows Phone 8.0 or later.

Connect the app with your desktop

Open up a command prompt or shell and change directories to your PhoneGap application and type “phonegap serve”. Take note of the IP address that the server is listening on.

> cd hello-world-app
> phonegap serve
[phonegap] starting app server...
[phonegap] listening on 192.168.1.199:3000
[phonegap]
[phonegap] ctrl-c to stop the server
[phonegap]

Open the PhoneGap developer app on your mobile device. Enter in the IP address the server is listening on and press “Connect”. In this example, 192.168.1.199:3000.

Connect with developer app

The server will wait for any IO changes on your project directory and will automatically send a refresh to the mobile device. Open up your project index.html and make some changes.

<div id="deviceready" class="blink">
    <p class="event listening">Connecting to Device</p>
    <p class="event received">This line will change when saved.</p>
</div>

Automatically updated

Hutz Media LogoBryan Wiebe is a web developer and mobile developer located in the Okanagan, British Columbia. He works for Hutz Media Ltd. This post is an entry in a blog series covering development with PhoneGap.