In the first two articles in this series, you built a REST API using StrongLoop LoopBack and several AngularJS directives that use your API via the LoopBack AngularJS SDK. With these two components, you've done most of the work of building a hybrid mobile app using the Ionic framework. The Ionic framwork is based on AngularJS, and, thanks to AngularJS' flexibility, you'll be able to use the timer and timeList directives in your mobile app without any changes.

Your Ionic mobile app will live in a separate GitHub repo. Keeping your Ionic mobile app in the same repo as your API server is possible, but cumbersome. Using a separate repo, though, makes it necessary for you to package your directives in a way that makes them easy to install using bower. The first step in this article will be to compile your directive HTML into JavaScript, so you can include your directives and their corresponding HTML with a couple script tags.

Packaging Your Directives For Ionic

TLDR; See a diff for this step on GitHub

Recall that your directives from the second article in this series use AngularJS' templateUrl property to load their HTML from the server. For instance,

directive('timer', function() {
  return {
    templateUrl: 'templates/timer.html'
    // ...
  };
});

Loading templates from the server makes sense for desktop browser apps, but it's a terrible choice for mobile. Mobile app users often have unreliable internet access, so depending on an HTTP request to render your app is infeasible. In order to avoid making an HTTP request for a template, AngularJS developers usually leverage the template cache. If you want to learn about the template cache in more detail, check out Chapter 6 of Professional AngularJS. For the purposes of this article series, you only need to know that AngularJS won't make an HTTP request for the template if you pre-populate the template cache. That is, if you set a value for templates/timer.html in the cache as shown below, AngularJS will use the cached value.

$templateCache.put('templates/timer.html', '<h1>This is my template!</h1>');

However, you wrote your directive templates in HTML. How are you going to translate them into JavaScript? As is usually the case with JavaScript, there's an npm module for that: gulp-html2js. This module takes a bunch of HTML files and generates a JavaScript file that calls $templateCache.put() for each of the HTML files. To use this module, first you need to add gulp and a couple plugins to your package.json:

"devDependencies": {
  "jshint": "^2.5.6",
  "gulp": "3.8.11",
  "gulp-concat": "2.6.0",
  "gulp-html2js": "0.2.0"
}

Next, you're going to write a gulpfile that defines a 'compile' task. This task is going to write 2 files, dist/all.js and dist/templates.js. The dist/all.js file contains all your JavaScript files concatenated into one. The dist/templates.js file contains all your HTML files piped through gulp-html2js. By their powers combined, including these two files and adding a dependency on the 'core' and 'templates' modules is sufficient to use the directives you wrote in the second article in this series. Below is the code for the 'html2js' task. You can find the full gulpfile on GitHub

gulp.task('html2js', function() {
  gulp.src('./client/templates/*').
    pipe(html2js({
      // Note this replace option. By default, gulp-html2js
      // will do `$templateCache.put('client/templates/timer.html')`
      // This rename option lets you transform the first argument to `put()`.
      rename: function(name) {
        return name.replace(/^client\//, '');
      },
      outputModuleName: 'templates'
    })).
    pipe(concat('templates.js')).
    pipe(gulp.dest('./client/dist'));
});

Introducing Ionic

TLDR; See a diff for this step on GitHub

Getting started with Ionic is pretty easy. First, you need to npm install -g cordova ionic. Once you've installed Ionic, you should be able to create a new Ionic app in the current directory using Ionic's handy CLI. For instance, you can create a new app called 'stopwatch-example' using the 'tabs' app template using ionic start stopwatch-example tabs. You can also just do git checkout step-2 in the 'stopwatch-example' GitHub repo to get the starter app.

Once you have the starter app, run ionic serve --lab. This will start Ionic's powerful CLI and launch a browser that has a side-by-side view of how your app will look on iOS and on Android. The CLI should look like the below picture.

The browser should look like the below picture.

Including Your Directives in Ionic

TLDR; See a diff for this step on GitHub

In this step, you'll take the Ionic tabs starter app and make it display the timer directive. This will give you a functioning stopwatch app, minus the ability to make HTTP requests to the server.

An easy way to include your directives and templates in your Ionic app is to use the bower package manager. Ionic already uses Bower, so you don't need to install anything extra. Just add a bower dependency on your GitHub repo:

"dependencies": {
  "angular-resource": "1.4.3",
  "moment": "2.10.6",
  "directives": "https://raw.githubusercontent.com/vkarpov15/stopwatch-server-example/master/client/dist/all.js",
  "templates": "https://raw.githubusercontent.com/vkarpov15/stopwatch-server-example/master/client/dist/templates.js"
}

Once you run bower install, bower will install ngResource and momentJS alongside your directives and templates. You can set the install path in your .bowerrc file, so bower installs into a directory your Ionic app can access.

{
  "directory": "./www/js"
}

The www directory is the root directory for your Ionic app. You might have noticed that it has an index.html file - this is the root HTML file for your Ionic app. Now that you've installed your dependencies, add the below script tags to index.html to include ngResource, moment, and your stopwatch directives.

<script src="js/angular-resource/angular-resource.js"></script>
<script src="js/moment/moment.js"></script>
<script src="js/directives/index.js"></script>
<script src="js/templates/index.js"></script>

Your Ionic app also has a main AngularJS module, defined in www/js/app.js. Add the 'core' (from directives/index.js) and 'templates' (from templates/index.js) modules to the starter module's list of dependencies.

angular.module('starter',
  ['ionic', 'starter.controllers', 'starter.services', 'core', 'templates'])

Now that you've properly included your AngularJS modules, you should be able to include the timer directive in your app's dashboard tab as shown in this GitHub diff. You should then see your timer directive in your ionic serve --lab window as shown below.

Tying It All Together

TLDR; See a diff for this step on GitHub

You may have noticed that your timer directive from the previous example can't actually save your times. There's two reasons why you can't save:

  1. The directive saves by making an HTTP request to /api/Times rather than http://localhost:3000/api/Times, which is where your REST API is.
  2. You haven't logged in with Facebook, and so you aren't authorized.

Thankfully, fixing these two problems is simple. The first problem can be solved with an AngularJS HTTP interceptor. Interceptors enable you to transform every HTTP request your application makes. In particular, the below request interceptor converts every request to /api/Times into a cross-origin request to http://localhost:3000/api/Times.

.config(function($httpProvider) {
  $httpProvider.interceptors.push(function() {
    return {
      request: function(req) {
        // Transform **all** $http calls so that requests that go to `/`
        // instead go to a different origin, in this case localhost:3000
        if (req.url.charAt(0) === '/') {
          req.url = 'http://localhost:3000' + req.url;
          // and make sure to send cookies too
          req.withCredentials = true;
        }

        return req;
      }
    };
  });
})

The above interceptor also does one more important task. Notice the withCredentials property? That tells AngularJS to send cookies on cross-origin requests. Because of browser security restrictions, AngularJS doesn't send cookies on cross-origin requests by default.

The second problem can be solved with a Cordova plugin called inappbrowser. You won't need this plugin in your ionic serve --lab workflow, but Cordova by itself doesn't have good support for popup windows. Adding this plugin requires adding a line to your package.json.

Now that you have the inappbrowser plugin, you get to deal with the most unpleasant part of working with Ionic: browser security restrictions in ionic serve --lab. Opening up an inappbrowser is as simple as calling the window.open() function in JavaScript. The cordova plugin even has some handy events for tracking the state of the new window.

However, window.open() in regular browsers has numerous cumbersome security restrictions. Thankfully, this only affects the login flow in ionic serve --lab, so you can wait for the user to close the popup window before reloading. In a real app, you can register a listener to the 'loadstop' event that the inappbrowser plugin broadcasts when the window's URL changes. You can take a look at the $facebookLogin service that encapsulates this logic here.

Once you've configured the HTTP interceptor and added Facebook login, your timer and timeList directives should work as written. You can start your timer, save it, and then see your timer results in the 'account' tab.

Moving On

Congratulations! You just took your AngularJS directives and packaged them into an Ionic framework app by adding a build step and making a couple configuration changes. With ionic serve --lab, you can work further on your mobile app without any bloated IDEs or SDKs. Note that you'll still need the proper Android/iOS SDK to compile your app for the app store using ionic build. In the next article in this series, you'll learn about another key advantage of hybrid mobile apps with Ionic: leveraging AngularJS' powerful testing modules to write integration tests for your hybrid mobile app.

Found a typo or error? Open up a pull request! This post is available as markdown on Github
comments powered by Disqus