This is a sample application that I use as a skeleton when bootstrapping new mobile applications.
It utilizes the Ionic framework to achieve a user interface that feels like a native application. The Ionic framework in turn utilizes AngularJS.
The application is written primarily in TypeScript which brings object oriented paradigms, type-safety, compile-time checking, and IDE tooling (refactoring! code completion! huzzah!).
Development is done in Visual Studio 2015 using the Visual Studio Tools for Apache Cordova project (previously known as Multi-Device Hybrid Application).
In-browser development and debugging is possible via the Apache Ripple emulator. When deployed to a physical device, the application runs in the Cordova application container. Both of these are provided out of the box when using VS2015.
Applications created with this starter project can target iOS, Android, Windows, and Chrome (as an extension).
Screenshots can be found on the project page here.
If you are looking for a version of this project that is IDE and platform agnostic, you can check out it's sister project: Ionic-TypeScript-Starter.
The following is a list of JavaScript libraries that are included.
- Ionic (1.0.0)
- AngularJS (1.3.13 via the Ionic bundle)
- lodash (2.4.1)
- Moment.js (2.7.0)
- NProgress.js (0.1.6)
- ngCordova (0.1.2-alpha)
- URI.js (1.13.2)
The following is a list of Cordova plug-ins that are used:
- org.apache.cordova.console (0.2.13)
- org.apache.cordova.device (0.3.0)
- org.apache.cordova.file (1.3.3)
- org.apache.cordova.dialogs (0.3.0)
- com.verso.cordova.clipboard (0.1.0)
- nl.x-services.plugins.toast (2.0.5)
- org.pbernasconi.progressIndicator (1.1.0)
The Visual Studio Tools for Apache Cordova are now included with Visual Studio 2015 RC and can be downloaded here.
In addition to installing Visual Studio, the installer will take care of downloading and setting up all the dependencies you need for developing Cordova applications (Node.js, Apache Ant, Oracle JDK 7, the Android SDK, Google Chrome and more).
Finally, you'll need the 1.5 beta version of TypeScript (VS2015RC currently ships with TypeScript 1.4). The 1.5 beta can be obtained from the TypeScript Homepage or via the direct download page.
You can clone the repository to your machine using:
$ git clone https://github.com/Justin-Credible/Ionic-TypeScript-MDHA-Starter
Once you've installed VS2015, and cloned this repository you should be able to open the solution file:
JustinCredible.SampleApp.sln
The VS Cordova Tools make it easy to build and run your mobile application for multiple platforms. You can simply choose the build type, platform and action (which can either be to run on the device, run on the device emulator, or run in the Apache Ripple in-browser simulator. This is done via the configuration drop downs on the Visual Studio's standard toolbar.
The project has been written to work well with the Ripple emulator; while in the browser several device only features (toast notifications, clipboard access, dialogs) will be mocked up and replaced with versions that will work on the browser. For example, the Ionic dialogs will be used in the emulator, but the native OS dialogs will be used when running on the device.
This is taken care of via the RippleMockApi.js and MockApis.ts files.
When you click the play button (or press F5), the build process will kick off. The VS Cordova Tools will perform TypeScript compilation, setup a Cordova project, and then copy in all of the resources.
If you are using the Ripple emulator a Chrome instance will be started and Visual Studio will attach to it to enable DOM inspection and JavaScript debugging.
While debugging, any changes to files in the www
directory should cause the Ripple emulator to refresh. You can also force a refresh by building using Build > Build Solution.
You may notice that if you try to open Chrome's F12 Developer Tools while Visual Studio is attached, the Chrome instance may crash. This has gotten much better in recent versions, so you may not encounter this issue, but if you do you can simply detach VS (Debug > Detach All) before opening the Developer Tools in Chrome.
The following information is now out of date, but I'm leaving it here because it is still useful to know how to start an instance of the Ripple Emulator manually.
Personally, I find the VS attachment to the Chrome process takes too long and I also prefer Chrome's debugging tools over those provided in Visual Studio. If you want a faster debug/refresh cycle, you can install the Ripple emulator manually by using npm:
$ npm install ripple-emulator
When you are ready to debug, instead of clicking run/play/F5, do a regular build (Build > Build Solution or press F6) and then start the Ripple emulator manually:
$ cd bld\Ripple\Android\Debug
$ ripple emulate
You'll need to replace "Android" and "Debug" with the proper device and configuration.
This will start the Ripple emulator's web server on port 4400 and open your default browser to:
http://localhost:4400/?enableripple=cordova-3.0.0
At this point you can run/test/debug and when you make a change you can just do a build again via F6 and then refresh your browser. This allows for much quicker development in my opinion.
I've included a simple batch file Build-Chrome-Extension.cmd
which will generate an extension that can be loaded in Chrome.
Once built, the extension can be loaded into Chrome using the chrome://extensions
URL and enabling development mode. The extension payload will be located at bin\Chrome
.
The application uses a left slide out menu for navigation (which is visible all the time when in tablet mode and only visible on demand on phones).
Four sample views are provided called "categories". These would be the main views for your application. The views can be re-ordered via the "Reorder Categories" menu item. The order is stored as a user preference. Finally, the settings view provides access to user configuration.
The "Cloud Sync" view is used to demonstrate an example directive.
The "Configure PIN" view allows the user to setup a PIN. This PIN will be prompted for when resuming the application after it has been in the background for 10 minutes.
The "Logs" view provides a list of log entries. Logs can be written to device storage or store only in memory. The log entry view allows the user to view the date/time, message, and stack trace (if applicable). They can also optionally copy the data to the device's clipboard or e-mail it.
Log entries are written when there are global JavaScript exceptions or exceptions within an Angular digest cycle. HTTP requests can also be logged, which can optionally include the full request/response body, which is useful for debugging.
The "Development Tools" view houses several options that are useful during development. It is described below.
The "About" view shows the application name, build timestamp, and version number, among other things.
The "Development Tools" view is available via the settings menu and houses several options that are useful during development. It is only available when the build mode is "debug" or if the application is placed into the developer mode. This mode can be activated by tapping the icon on the about screen 10 times.
It can be used to enable the Mock API mode (described below), change the logging mode, test dialogs and/or toast notifications, and get information about the platform.
A custom HTTP Interceptor is provided via HttpInterceptor.ts
. It is responsible for several things:
- Keeps track of outgoing requests
- Handles showing of the NProgress activity bar at the top of the screen (optional)
- Handles blocking the UI with a message/spinner (optional)
- Handles setting HTTP headers (such as authorization token, API, version, content type etc)
- Handles pre-pending the base API URL to the URL
- Handles logging HTTP requests
- Broadcasts events for certain status codes (eg 401, 403, 404) so the application can handle them
The interceptor will respect the flags specified via the IRequestConfig
interface.
var httpConfig: Interfaces.IRequestConfig;
httpConfig = {
method: "GET",
url: "~/some-resource/123",
data: null,
blocking: true,
blockingText: "Please Wait..."
};
this.$http(httpConfig).then((response: ng.IHttpPromiseCallbackArg<DataTypes.ISomeResource>) => {
// Got my strongly typed response object!
});
The development tools can be used to enable the "Mock API" mode. In this mode all HTTP API requests will never leave the device and can be configured to return specific values.
This is useful for quick debugging without a backend or testing on devices without setting up a wi-fi connection etc.
The responses for the HTTP requests are defined in the mockHttpCalls
method in MockApis.ts
.
The file layout is mostly self describing via the directory names. All of the application code is located in the scripts/app
directory, with sub-directories for controllers, directives, view models, services, etc.
Application.ts
is the main file that bootstraps the application. It is responsible for setting up Ionic/Angular and registering the controllers, directives, routes, etc. It also takes care of device level events and handling exceptions.
Visual Studio will compile the TypeScript and bundle it all into a single file located at www/scripts/appBundle.js
.
Third party JavaScript libraries are stored in www/lib
. I'm not currently using NuGet for third party libraries because I can't control the location of the scripts.
One thing that is slightly different from normal Ionic/Angular development is the obvious addition of TypeScript. There are base classes for controllers and directives, and all of the view models, models, services etc are strongly typed.
If you are used to declaring controllers and directives inline and using closures to share data, this will require a bit of adjustment. I've included a sample directive and there are several controllers extending BaseController which can be used as examples.
Injection for controllers is done by setting a public static variable named $inject
:
export class MenuController extends BaseController<ViewModels.MenuViewModel> implements IMenuController {
// Specify the things to inject.
public static $inject = ["$scope", "$location", "$http", "Utilities"];
// This is where we'll store the injected things.
private $location: ng.ILocationService;
private $http: ng.IHttpService;
private Utilities: Services.Utilities;
// The constructor receives the injected arguments in the same order as the $inject variable.
constructor($scope: ng.IScope, $location: ng.ILocationService, $http: ng.IHttpService, Utilities: Services.Utilities) {
super($scope, ViewModels.MenuViewModel);
// Save off a reference to the injected things.
this.$location = $location;
this.$http = $http;
this.Utilities = Utilities;
}
}
The Utilities.ts
file defines a Utilities
service which provides several convenience methods for checking the device version, build mode (debug vs release), application version numbers.
It also includes helpers for manipulating strings (startsWith
, endsWith
, format
), creating GUIDs, and working with the clipboard.
The UiHelper.ts
file defines a UiHelper
service which provides several methods for working with the UI.
Any accessors for native plug-ins will be exposed here (eg toast and progress indicator). Exposing plug-ins via this service makes it easier to write unit tests, as all code will use these accessors instead of global variables like window.plugins.toast
.
It also provides helpers to show native dialogs via alert/prompt/confirm when running on a device, or via $ionicPopup
when running in Ripple.
Finally, it provides a showDialog
helper for working with dialogs.
Cordova's file system API required a few too many callbacks to do simple file I/O, so I created a bunch of helper methods in FileUtilities.ts
. There are helps to list files and directories, as well as create, delete, and append to files, among others.
The devices.css
stylesheet includes several classes that make it easy to hide or show elements based on the device orientation or screen size. For example, landscape
and portrait
as well as more specific classes such as phone-landscape
or tablet-portrait
.
The UiHelper
's showDialog
method makes it easy to work with dialogs. Internally, it delegates to Ionic's $ionicModal
service.
All dialogs controllers should extend the BaseDialogController and be opened using the showDialog
method. Doing so makes the dialog eventing consistent and makes it easier to pass data in and out of dialogs without adding lots of closures in surrounding code.
/**
* ...
* V - The type of the view model that this controller will utilize.
* D - The type of data object that will be passed in when this dialog is opened.
* R - The type of the data object that will be returned when this dialog is closed.
*/
export class BaseDialogController<V, D, R> extends BaseController<V>
There are two sample dialogs provided; one for PIN entry and one for re-ordering categories.
Copyright © 2015 Justin Unterreiner.
Released under an MIT license; see LICENSE for more information.