Getting started with Ionic
Older Article
This article was published 10 years ago. Some information may be outdated or no longer applicable.
This article first appeared on Full Stack Training.
So you’ve had enough of web applications and you want to build mobile apps. But who’s got time to learn new programming languages, let alone two if you want to target both Android and iOS? The Ionic Framework lets you use your existing AngularJS skills to build mobile applications. We’ll set it up and put together a simple app.
Github
The full application code is available on GitHub - https://github.com/fullstacktraining/ionic-starwars-app
Installation
Install Ionic via npm by running npm install -g ionic cordova
Cordova (formerly PhoneGap) is an Apache project (technically an Adobe project, but the history’s a separate rabbit hole) that provides a framework for developing mobile applications. Ionic builds on top of Cordova.
I’m going to assume you’ve got the appropriate environment set up, including XCode and Android Studio. (I’m on a Mac, which makes iOS development much simpler.)
Let’s also install Genymotion. We’ll use it to run an Android virtual machine for testing, because the standard Android Studio emulator takes ages to start, even with the optimisation settings from the Ionic docs. (I once left it running for 30 minutes and it still hadn’t loaded my app.)
Add a new virtual device through the Genymotion interface. I’m using Google Nexus 5 - 5.1.0 - API 22.
To setup a virtual device please follow this guideline: https://www.genymotion.com/#!/developers/user-guide
With all that sorted, let’s write some code.
Create a blank Ionic application
We’ll start with a blank project. Ionic ships with a decent generator that can scaffold full application templates, but we’ll stick with the blank one.
Run ionic start starwars blank to create a folder called ‘starwars’ with a blank project inside.
The files we’ll mostly work with live under the www folder.
To run the project, execute ionic serve from the project folder. This should open your default browser and load localhost:8100 with your Ionic application.
If you receive an error message stating that port 8100 is taken you can run
ionic serve -p XXXXwith a port number that is not taken by another application.
Let’s also run the app in both the Android and iOS emulators.
We need to add these platforms to the project first. Run ionic platform add ios for iOS and ionic platform add android for Android.
Add in the Android SDK
Now’s a good time to set up the Android SDK. Since I’m on a Mac with XCode installed, iOS just works. Android takes more effort. Once you’ve got Android Studio installed, select ‘Configure’ from the main screen, then click ‘SDK Manager’. Find the ‘Launch Standalone SDK Manager’ link:
(You can also launch it by running: /Users/{your-username}/Library/Android/sdk/tools/android)
Install the appropriate Android SDK version. Ionic throws helpful error messages, so check your CLI output for clues if you’re unsure what to install.
Last step: add $ANDROID_HOME (your SDK location) to your $PATH. On my system: export ANDROID_HOME=/Users/{your-username}/Documents/android/sdk. Yours may differ.
With the configuration out of the way, let’s fire up the (blank) mobile app.
See the application in Android
We’re skipping the built-in Android emulator and using Genymotion instead. Two things need to happen:
- Have the Genymotion virtual device running
- Start the application with
ionic run android(notionic emulate android)
Start the Genymotion virtual device. Once you see the stock Android apps, execute the run command.
Run the application on iOS
Running on iOS is simpler (if you’re on a Mac). Execute ionic emulate ios to launch the iOS Simulator.
At this point you should have the blank template running in your browser, on iOS, and on Android.
Note that we did not build our projects - we don’t need to do that as the run/emulate commands build the project automatically. If you want to build a project without emulating it you can simply execute
ionic build ios
Code up the application using AngularJS
Time to add some code. We’re using AngularJS to build the app, and Ionic gives us a set of CSS classes plus AngularJS directives.
Navigate to your project folder and find the www folder. That’s where the source code lives and where we’ll build out the mobile app.
If you’ve worked with Angular before, nothing here will surprise you. There’s a js folder with app.js containing some cordova/ionic-specific code. We can add controllers, filters, and other Angular pieces just like any web app.
Our application will have two menus: one listing Star Wars films, another listing characters. Tapping a film or character opens a detail view with more information. The data comes from a publicly available Star Wars API.
We’ll build the film menu here. The characters menu is left as an exercise.
Let’s start with the interface by placing a menu into our application.
Setting up the app menu
We’ll pull Star Wars character and film information from a web service.
Replace the content of <body> with:
<ion-nav-bar class="bar-stable">
<ion-nav-back-button></ion-nav-back-button>
</ion-nav-bar>
<ion-nav-view></ion-nav-view>
Both <ion-nav-bar> and <ion-nav-view> are AngularJS directives provided by Ionic.
This gives us a back button for navigating the current stack and a navigation view.
Using AngularUI Router to define application states
Ionic tracks navigation history and uses AngularUI Router instead of the built-in AngularJS $route service. Check the AngularUI Router documentation for details. The key difference: $route organises your app around URLs, while AngularUI Router organises it around states (which can have routes and behaviours attached).
Add the routing configuration to app.js:
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('tab', {
url: '/tab',
abstract: true,
templateUrl: 'templates/tabs.html'
})
.state('tab.films', {
url: '/films',
views: {
'tab-films': {
templateUrl: 'templates/films.html'
}
}
});
// set the default route
$urlRouterProvider.otherwise('/tab/films');
});
We’re setting up a ‘tab’ state with a ‘films’ route. Hitting that endpoint loads the template defined by templateUrl.
Define a view template for the menu
The tabs.html template:
<ion-tabs class="tabs-icon-top tabs-color-active-positive">
<ion-tab
title="Films"
icon-off="ion-ios-videocam-outline"
icon-on="ion-ios-videocam"
href="#/tab/films"
>
<ion-nav-view name="tab-films"></ion-nav-view>
</ion-tab>
<ion-tab
title="Characters"
icon-off="ion-ios-people-outline"
icon-on="ion-ios-people"
href="#/tab/characters"
>
<ion-nav-view name="tab-characters"></ion-nav-view>
</ion-tab>
</ion-tabs>
This adds two tabs with icons.
Interestingly enough the tabs will be added to the bottom on an iOS device however they’ll be displayed on the top on all Android devices. We don’t need to do any alteration in our code - this is all done automatically by Ionic.
Building an Ionic view with AngularJS
Here’s what films.html contains. We’re creating a view with a simple header tag to verify that navigation works:
<ion-view view-title="Films">
<ion-content>
<h1>hello</h1>
</ion-content>
</ion-view>
We’ll replace that hello message with something useful shortly. Consider it a placeholder.
Using a REST API with Ionic
Time to fetch some data. We’ll use SWAPI (the Star Wars API). Since we’re working with an Angular application, we need a controller that uses the $http service to hit the REST endpoint. First, add the controller to app.js:
.state('tab.films', {
url: '/films',
views: {
'tab-films': {
templateUrl: 'templates/films.html',
controller: 'FilmsController as vm'
}
}
});
Call the API from an AngularJS contorller
Create a new file films-controller.js:
angular.module('starter').controller('FilmsController', FilmsController);
FilmsController.$inject = ['$http'];
function FilmsController($http) {
var vm = this;
$http
.get('http://swapi.co/api/films')
.then(function (response) {
vm.films = response.data.results;
})
.catch(function (error) {
console.log(error);
});
}
This uses AngularJS’s $http service to call the API. When data comes back, it gets stored in vm.films.
Reference the controller in the app
Two more steps: include the controller in index.html via a <script> tag, and create the films.html template:
<ion-view view-title="Films">
<ion-content>
<ion-item ng-repeat="film in vm.films | orderBy: 'release_date'"
>">
<div>
<h2>{{ film.title }}</h2>
<p><small>Director: {{ film.director }}</small></p>
<p><small>Released: {{ film.release_date }}</small></p>
<p>{{ film.opening_crawl }}</p>
</div>
</ion-item>
</ion-content>
</ion-view>
Testing and debugging Ionic applications
When you test the app, it’ll work in your browser with ionic serve and on Android. But iOS will show a blank screen with no data, and you won’t see errors in your browser.
Debugging in iOS
Time to debug. If you haven’t enabled Safari Developer Tools, do that now (Safari > Preferences > Advanced tab > “Show Develop menu in menu bar”). Then open Safari, go to Develop > Simulator > index.html. Check the console and you’ll find: “Failed to load resource: The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.” (Hit CMD+R to refresh the app from within the console if you don’t see anything.)
In short: your app shouldn’t talk to non-https REST services. And it really shouldn’t. But for testing, we’ll disable this check. Find starwars-info.plist and add the following just after the last </array> tag:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true />
</dict>
Re-run in the iOS emulator and data should flow in.
Ideally we should change our API call to go to an
httpsendpoint as opposed to apply the above configuration change, however in some cases that may not be possible.
Debugging in Android
Debugging Android apps works through Chrome. Open a new tab and go to chrome://inspect. If Genymotion and your app are running, you’ll see it listed under Devices. Click ‘inspect’ to open Developer Tools.
Using URL parameters
Let’s add one more feature: viewing detailed information about a single film on a separate tab. We need to give the <ion-item> directive an href attribute. Remember our state configuration? We have a tab state with different routes, so the href needs to follow the #/tab/films pattern:
<ion-item
ng-repeat="film in vm.films | orderBy: 'release_date'"
href="#/tab/films/{{ $index + 1 }}"
></ion-item>
Why
$index + 1? I couldn’t find an easier way to link to each image and theepisode_idproperty doesn’t give me what I want. This is unfortunately a limitation at the API end, so please consider this as a simple workaround.
Define the new route
Update app.js with the new state:
.state('tab.film', {I
url: '/films/:filmId',
views: {
'tab-film': {
templateUrl: 'templates/film.html',
controller: 'FilmController as vm'
}
}
});
Define the controller
Add a new controller:
angular.module('starter').controller('FilmController', FilmController);
FilmController.$inject = ['$http', '$stateParams'];
function FilmController($http, $stateParams) {
var vm = this;
var filmId = $stateParams.filmId;
$http
.get('http://swapi.co/api/films/' + filmId)
.then(function (response) {
vm.film = response.data;
})
.catch(function (error) {
console.log(error);
});
}
Define the template
The template:
<ion-view view-title="{{ vm.film.title }}">
<ion-content> {{ vm.film.opening_crawl }} </ion-content>
</ion-view>
And that’s it. You’ve got a functioning mobile app.
Further exercises
As an exercise, try building out the ‘Character’ section on your own. Once that’s done, separate the $http requests from the controller into a custom AngularJS service.