Organizations and developers struggle with an interesting challenge. We no longer live in a single-platform, single-device, single-vendor world. We have multiple mobile platforms and we have multiple desktop platforms. To be relevant in today's market, you need to target all of them when you're building an app. Unfortunately, each platform has its own nuances. There are languages and frameworks to learn that are unique and specific to each platform. Even within a single vendor, targeting a phone operating system can be quite a different challenge than targeting desktops.

For the very first time in the history of computing, we can say that “write once, run everywhere” actually works. Yes, we heard that about Java, but we all know how that went. It was write once, test everywhere, and deliver poor experience everywhere.

Given this bad historical taste, I'm not surprised that many developers seem to discount Apache Cordova because it isn't “native”. That, I feel, is a serious mistake. Cordova is incredibly powerful, more powerful than native apps. Am I saying that Cordova is suitable for 100% scenarios and native is completely useless? Absolutely not. What I'm saying is that 98% of the apps you see and use every day could very well have been written using Cordova instead of native. In fact, you'd be surprised to know how many apps in app stores are written using Cordova. There are some very good reasons for this:

  • Cordova apps, by default, present their UIs in HTML, but you have a choice of presenting the UI in native code and keeping your logic code as JavaScript. React Native and NativeScript use this approach. And yes, JavaScript runs quite fast, even on mobile devices. Gone are the days when Apple used to throttle down the hosted UIWebView. Although I can't speak for what Apple may do in the future, I see no logical reason for Apple to gimp the UIWebView either.
  • Cordova apps can also selectively show parts of their UX using native code. For instance, a single drop-down could be native whereas other portions of the app could use HTML.
  • Cordova apps can be updated on the fly without going through app stores. This is true as long as your app's permissions don't change. Now take this with a small grain of salt. If you're targeting the public app store on iOS, you may run into some approval challenges if you abuse this power. However for enterprise deployment on iOS and Android in general, this is a very powerful technique. It's especially powerful considering that enterprises desire control, 100% control, and they rarely have the patience to wait for Apple to take four weeks to approve or reject their changes.
  • There are far more Web technology developers out there than there are C#, Swift, or Java developers. Let's face it, the best platform is the one you actually know. And if you know Web-based technologies, you're ready to target all major mobile platforms using Cordova.

In short, Cordova allows you to target multiple platforms with a single code base. It does allow you to use your existing Web technology skills and target mobile platforms, but there are a few things to learn before you can call yourself an expert Cordova developer.

  • You have to learn Cordova basics, which I will cover in this article.
  • Like any other Web-based project, you have to pick a good framework. I feel that Angular and TypeScript are very good choices. Because Cordova apps are built and deployed on a mobile device, certain things become more difficult. For instance, live-reload doesn't work like you'd expect it to in a browser. You also have to do some additional things to make sure that TypeScript debugging works, along with sourcemaps. This will be the main focus of this article.
  • Writing good UX using Cordova is a skill on its own. There are frameworks, such as Ionic, to help you here, but sometimes you may choose to write your own UX. Let me be honest, downloading the next-best Bootstrap theme and using it in a Cordova app isn't the best idea. Frameworks such as Bootstrap can be too heavy for mobile UX. This topic won't be a focus in this article.

Let's get started. In the rest of this article, I'll walk you through three main topics:

  • Setting up your dev environment and what you need to follow this article.
  • Writing a very simple Cordova app.
  • Writing a Cordova app using TypeScript and Angular 2.

Setting Up Your Dev Environment

Because Cordova is cross-platform, you can use almost any operating system to follow this article. I prefer to use a Mac for one simple reason: On a Mac, I can target every platform, even Windows. In order to target iOS, I have to use a Mac. Yes, I know I am coloring between the lines here; there are some cloud-based services that allow you to get around this limitation. But if you're going to do serious mobile development in today's world, get yourself a Mac, and an Android, and an iOS physical device.

You can follow this article using a Linux or Windows computer as well. Just make sure that you have Node.js installed, and if targeting Android, go ahead and install Android Studio. Once you've installed Android Studio, create a virtual device using the emulator.

Writing a Cordova App

Writing a Cordova app is really simple. Assuming that you have Node.js installed, run the following:

sudo npm install -g cordova

Note that “sudo” is Mac's equivalent of “Run as administrator”.

Once you've installed Cordova, create a new Cordova project using this command:

cordova create myApp

Creating this project creates a folder like that shown in Figure 1.

Figure 1: The basic Cordova project structure
Figure 1: The basic Cordova project structure

Cordova allows Web-based code to interplay with native code. Native code sits under the plugins directory, and there are plugins available for most of the common stuff you may need to do. Your custom code resides under the www folder.

Let's enhance the code a bit. The aim of this application is to write out the details of the device the application runs under. This is achieved by adding a plugin called Cordova-plugin-device. In order to add this plugin, open terminal (or command prompt) in the directory of your project, and issue the following command:

cordova plugin add cordova-device-plugin

You could test the application in a browser. The advantage of testing in a browser is that you get functionality such as live-reload, but the disadvantage is that you get fake native device capabilities. In order to run and test the application in a browser, issue the following commands:

cordova platform add browser
cordova run browser

The UX of this application is very simple, so I'll skip over running in the browser. I'll add support for two other platforms, as shown here:

cordova platform add android
cordova platform add ios

If you're not on a Mac, skip iOS, or instead, add Windows.

It's worth mentioning that your development environment needs to be already set up to use Cordova. For instance, if you wish to target Android systems, you need to have Android Studio installed. If you wish to target iOS, go ahead and install XCode and run it once. And if you wish to target Windows, your Windows computer should have Visual Studio installed with all the necessary pieces in place to create universal Windows apps.

Next, add some code. The purpose of this application is quite simple: It allows the user to press a button and get information about the device or emulator the application is running under. There are two parts to this code change. You need to change the UX to add a button, which is shown in Listing 1. This is just a button; as long as the button works, you can add it anywhere; I've added this code under the default “device is ready” message.

Listing 1: Button to call the custom code

<p>
<button id="populateDetailsButton">Populate</button>
<div id="deviceDetails">
</div>
</p>

The second portion of the code change goes in index.js. You intend to call a JavaScript function onclick of this button. Now why couldn't you simply tap into the “onclick” event in HTML? The answer is: you could! But it's not considered best practice from a security point of view. In fact, if you examine the meta tags in the index.html file, you'll find this line:

<meta http-equiv="Content-Security-Policy"
      content="default-src 'self'
        data: gap: https://ssl.gstatic.com
        'unsafe-eval'; style-src 'self'
        'unsafe-inline'; media-src *">

This line prevents unsafe-line JavaScript execution. I'd recommend leaving it as is.

Now, back in index.js. Locate the “bindEvents” function, and in that function, add the following line:

document.getElementById("populateDetailsButton")
        .addEventListener("click", this.populateDeviceDetails);

This line of code subscribes to the “click” event of the populateDetailsButton, and calls a function called “populateDeviceDetails”. The populateDeviceDetails function can be seen in Listing 2.

Listing 2: The populateDeviceDetails function

populateDeviceDetails: function() {
    var deviceDetails = "";
    deviceDetails += "<br/>Cordova:" + device.cordova;
    deviceDetails += "<br/>model:" + device.model;
    deviceDetails += "<br/>platform:" + device.platform;
    deviceDetails += "<br/>uuid:" + device.uuid;
    deviceDetails += "<br/>version:" + device.version;
    deviceDetails += "<br/>manufacturer:" + device.manufacturer;
    deviceDetails += "<br/>isVirtual:" + device.isVirtual;
    deviceDetails += "<br/>serial:" + device.serial;
    document.querySelector('#deviceDetails').innerHTML = deviceDetails;
}

As can be seen from Listing 2, you're using a global variable called “device”. This variable is available to you because you've added the Cordova-plugin-device plugin. At this point, you can run your application using the following commands:

cordova run android
cordova run iOS

You should see the applications running, as shown in Figure 2 and Figure 3.

Figure2: The application running in iOS
Figure2: The application running in iOS
Figure 3: The application running in Android
Figure 3: The application running in Android

Using Angular and TypeScript to Write Cordova Apps

Congrats! You've written your first Cordova app. Although the app was impressive, you'll soon find that it's difficult to write reliable and well-structured code in JavaScript. Sure, there were frameworks such as jQuery and Angular 1 that took you quite a distance, but the lack of strong typing and higher-level language features in JavaScript meant that you could only go so far. Thus TypeScript was born.

I've talked about TypeScript in my previous article in CODE-Magazine (here: http://www.codemag.com/article/1511051). In short, it's a superset of JavaScript, it lets you use a better-structured language with more features, and it transpiles to JavaScript. There's simply no reason to not write your code in TypeScript going forward.

Another good friend of TypeScript is Angular 2. Although you could write Angular 2 using ES5 (plain old JavaScript), there's simply no reason to. You should use TypeScript. When you pair Angular2 with TypeScript, it's like discovering cold fusion and artificial intelligence.

Using them in Cordova raises two interesting challenges:

  • You need to build a transpilation step, so what runs on the device within a Cordova shell is JavaScript.
  • And this transpilation step must understand sourcemaps so debugging in the device works.

Debugging in the device is especially interesting because file paths among iOS, Android, and Windows are different. So a hard-coded sourcemaps location doesn't work.

Let's address these challenges. The final code for this section can be found at https://github.com/maliksahil/cordovangts, or you can follow this article to build it step-by-step.

The first step is to create a Cordova project, which I assume you already know how to do.

Next, add a package.json. In this package.json, add the various dependencies for Angular2. Also add devdependencies for TypeScript and typings. You still need typings so that ES6 features, such as Promises, don't throw TypeScript errors.

Next, you need to add support for bundling. This is something I constantly struggle with. I like webpack, but all the official Angular samples are in SystemJS. For this article, I'll explain stuff using SystemJS. And because I'm using SystemJS, my choice of bundling tool is JSPM, or JavaScript Package Manager.

On your computer, go ahead and install JSPM globally using this command:

sudo npm install ?g JSPM

The way JSPM works is that you first give it enough hints via the package.json and a JSPM.config.js file. These hints tell JSPM where the various JavaScript libraries are. They can come from the JSPM registry, or git, npm, etc. And you tell JSPM how to bundle them. It's not uncommon for SystemJS.config.js and JSPM.config.js to be the same file; because you're using Cordova, you don't have that luxury. This is because the file paths keep changing depending upon what platform you're targeting.

Next, JSPM walks your code, looks at all the import statements, and bundles things appropriately. You can ask it to bundle sourcemaps also. And you can ask it to create a bundle that has no dependency on SystemJs at runtime. That, frankly, is quite incredible.

If you're an Angular guru, you may have heard of something called AOT, or Ahead-of-Time compilation. AOT allows you to strip even Angular out of the final runtime JavaScript.

Between bundling and AOT, it's not uncommon for your application to start up 10-15 times faster and run two-to-five times faster. Imagine the impact of that on mobile applications.

You need to give JSPM some hints. These go in both package.json and JSPM.config.js.

My package.json excerpt can be seen in Listing 3.

Listing 3: Package.json

"scripts": {
    "start": "tsc && npm run bundle && cordova run android",
    "bundle":
        "JSPM bundle-sfx www/app/main.js
        www/dist/bundle.js --inline-source-maps
        --source-map-contents",
    "tsc": "tsc",
    "postinstall": "typings install",
    "typings": "typings"
},
"dependencies": {
    "@angular/common": "2.0.0",
    "@angular/compiler": "2.0.0",
    "@angular/core": "2.0.0",
    "@angular/forms": "2.0.0",
    "@angular/http": "2.0.0",
    "@angular/platform-browser": "2.0.0",
    "@angular/platform-browser-dynamic": "2.0.0",
    "@angular/router": "3.0.0",
    "@angular/upgrade": "2.0.0",
    "angular2-in-memory-web-api": "0.0.20",
    "systemjs": "^0.19.38",
    "es6-shim": "0.35.1",
    "core-js": "^2.4.1",
    "reflect-metadata": "^0.1.3",
    "rxjs": "5.0.0-beta.12",
    "zone.js": "^0.6.17"
},
"devDependencies": {
    "Typescript": "^2.0.3",
    "typings": "^1.0.4"
},
"JSPM": {
    "configFile": "JSPM.config.js",
    "dependencies": {
        "angular/angular": "github:angular/angular@^2.0.0",
        "es6-shim": "npm:es6-shim@0.35.1",
        "reflect-metadata": "npm:reflect-metadata@0.1.3",
        "rxjs": "npm:rxjs@5.0.0-beta.12",
        "systemjs": "npm:systemjs@^0.19.38",
        "ts": "github:frankwallis/plugin-Typescript@^4.0.16",
        "zone.js": "npm:zone.js@0.6.17"
    },
    "devDependencies": {
        "babel": "npm:babel-core@^5.8.24",
        "babel-runtime": "npm:babel-runtime@^5.8.24",
        "core-js": "npm:core-js@^1.1.4",
        "Typescript": "npm:Typescript@^2.0.3"
    }
}

The JSPM.config.js file instructs JSPM, where in the node modules are the interesting bits. For instance, let's consider zone.js. Zone.js is used by Angular, and its node_modules folder contains a bunch of files, such as the TypeScript source code and the source map. You don't really care about that at runtime.

Also, you're not going to bundle all of that with your application. For one, the application would be huge; the bigger issue perhaps, is that these node_modules prevent the Android build from succeeding.

You need to provide a list of packages and a map to JSPM. Providing the list of packages and map to JSPM is done via JSPM.config.js. This is exactly the same purpose as systemjs.config.js has at runtime. This is why JSPM and SystemJS go hand-in-hand.

If you were using Webpack, this section would be entirely different. That's beyond the scope of this article, so I'll leave it for a future article.

I suggest that you give the JSPM.config.js a look in the GitHub repo because it's far too wordy to write out the whole thing here.

With these things in place, you can now write the Angular app that makes use of the Cordova plugin. So just like before, add the Cordova-plugin device.

Writing the Actual App

My app is quite simple. It has three components:

  • The appComponent, which holds a router outlet and router links to About and Device.
  • The About component, which acts as the home page of the app and informs the user what this app is all about.
  • The Device component, which holds the logic of querying the device and writing out the details of the hardware you're running the app on.

Besides that, there's an AppModule.ts that's the app's module, and there's an app.routing.ts that sets up routing for the application. Because the focus of this article isn't Angular, but rather, using Angular and TypeScript with Cordova, I won't explain the basics of the application and will dive straight into the interesting bit, which is the device.component.ts file. This file can be seen in Listing 4.

Listing 4: The device.component.ts file

import { Component } from '@angular/core';
@Component({
    template: `
    <h5>Device Component</h5>
    <button (click)="populateDeviceDetails()">Populate</button>
    <div [innerHTML]="deviceDetails">
    </div>
    `
})

export class DeviceComponent {
    private deviceDetails = "";
    populateDeviceDetails() {
        var device:Device = (<any>window).device;
        this.deviceDetails = "";
        this.deviceDetails += "<br/>Cordova:" + device.cordova;
        this.deviceDetails += "<br/>model:" + device.model;
        this.deviceDetails += "<br/>platform:" + device.platform;
        this.deviceDetails += "<br/>uuid:" + device.uuid;
        this.deviceDetails += "<br/>version:" + device.version;
        this.deviceDetails += "<br/>manufacturer:" + device.manufacturer;
        this.deviceDetails += "<br/>isVirtual:" + device.isVirtual;
        this.deviceDetails += "<br/>serial:" + device.serial;
    }
}

This code is almost identical to the JavaScript code. So what's the advantage of using TypeScript here? The advantage is being able to use TypeScript itself - the higher-level language features that you're really not making use of here. You can imagine that in a more complex app you would use them.

Another handy advantage is Help and IntelliSense and refactoring over the project. You can get Cordova IntelliSense in TypeScript projects too. Here is how.

First, install typings globally.

sudo npm install ?g typings

Next, search for typings for Cordova.

typings search cordova

Once you find the appropriate typing, install it.

typings install dt~cordova --global --save

Note that by doing so, you're editing the typings.json file. Your package.json file was rigged to contain a “postinstall” action, which was the install typings. The next time you do npm install, it will update typings. This means that in a folder called typings typings puts the appropriate .d.ts files into the appropriate folder, and because they're a part of your TypeScript project, and thanks to the jsconfig.json file, they'll be picked up for IntelliSense purposes.

With all of this in place, you'll now see full IntelliSense and Help for even Cordova plugins, as can be seen in Figure 4.

Figure 4: There's full IntelliSense in Cordova.
Figure 4: There's full IntelliSense in Cordova.

Now go ahead and run the application using npm start, which:

  • Runs the Typescript transpiler.
  • Does the bundling.
  • Launches the application.

You should see the application running, as shown in Figure 5.

Figure 5: The Angular TypeScript Cordova application running
Figure 5: The Angular TypeScript Cordova application running

This is great! But you've seen the application running before. What's different this time is that you can actually debug it in TypeScript, while it runs on a device or emulator. You can do so directly from Chrome when running Android, but let me show you the steps to debug directly from VSCode.

First, install the Cordova Tools extensions from the extension gallery.

Next, if you're targeting iOS, install ios-webkit-debug-proxy. The easiest way to do this is using Homebrew.

brew install ideviceinstaller
    ios-webkit-debug-proxy

Once you've done this, go ahead and set a breakpoint in your code, and in VSCode, on the left hand side, look for the debug pane, the one that looks like a bug with an X through it. Launch either iOS or Android (remember to add iOS as a platform in your project first).

You'll note that you're able to debug TypeScript code running on an iPhone via VSCode. This is shown in Figure 6. Here's the best part: Until very recently, Safari didn't understand sourcemaps. Via VSCode, you could debug an iOS Cordova app and debug directly in TypeScript. Amazing! The newest version of Safari does understand sourcemaps.

Figure 6: Debugging Cordova TypeScript in VSCode
Figure 6: Debugging Cordova TypeScript in VSCode

Summary

I could hardly contain my excitement writing this article. A few years ago, when all my friends were writing iOS and Android apps, and had so many opportunities thrown at them, I was dangling around Silverlight. With Cordova, I feel so empowered, and I can use TypeScript and Angular and debug with the ease of well-put-together products.

Technology is evolving really fast. A few years ago, I had to sit back and think of a good topic to write. Now I have to pick from among so many topics. Perhaps the biggest challenge is change. I assure you, jumping on the TypeScript-JavaScript bandwagon is a bet that won't go wrong. There's a lot of good stuff to talk about. You'll have to wait until the next article, though. In the meantime, happy coding!