Académique Documents
Professionnel Documents
Culture Documents
1 Introduction 10
Welcome! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Changelog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
New Concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
ECMAScript 6 (ES6) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
TypeScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Transpiling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Web Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2 Ionic 2 Basics 25
Installing Ionic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Adding Platforms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
1
Lesson 4: Decorators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Lesson 5: Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
What is a Class? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Classes in Ionic 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Creating a Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Creating a Component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Creating a Directive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Creating a Pipe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Creating an Injectable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Lesson 6: Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
The * Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Looping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Conditionals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Lesson 8: Navigation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Tabs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
2
Local Storage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
SQLite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
Checklists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
3
Checklist Items . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
Theming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
4 Giist 214
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
4
Fetching Data from Reddit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
5 Snapaday 279
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
5
Lesson 4: Taking Photos with the Camera . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347
6
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 437
7
Adding Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 465
Lesson 6: Local and Remote Backend with PouchDB and Cloudant . . . . . . . . . . . . . . 471
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 494
Building for iOS & Android Using PhoneGap Build (without MAC) . . . . . . . . . . . . . . . . 528
8
Building with PhoneGap Build . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 529
9
Chapter 1
Introduction
Welcome!
Hello and welcome to Building Mobile Apps with Ionic 2! This book will teach you everything you need
to know about Ionic 2, from the basics right through to building an application for iOS and Android and
People will have varying degrees of experience when reading this book, many of you will already be familiar
with Ionic 1, some may have some experience with Ionic 2, and some may have no experience with either.
Whatever your skill level is, it should not matter too much. All of the lessons in this book are thoroughly
This book does not contain an introduction to HTML, CSS, and JavaScript though. You should have a
reasonable amount of experience with these technologies before starting this book. If you need to brush
up on your skills with these technologies Id recommend taking a look at the following:
Learn Javascript
This book has many dierent sections, but there are three distinct areas. We start o with the basics,
we then progress onto some application walkthroughs and then we cover building and submitting
10
applications.
All of the example applications included in this course are completely standalone. Although in general, the
applications increase in complexity a little bit as you go along, I make no assumption that you have read
the previous walkthroughs and will explain everything thoroughly in each example. If there are concepts
that need to be explained in more than one walkthrough, I have copied information into both rather than
Ionic 2 is still in development, so that means that it is still changing. It is reasonably stable now, so most of
what you read in this book wont change, but there will still most likely be some changes until the release
version is reached. I will be frequently updating this book to reect any changes that are made to the
framework, and you will receive these updates for free. Any time I update the book you should receive
Ill be keeping a close eye on changes and making sure everything works, but its a big book so if you think
you have found an error please email me and Ill get an update out as soon as I can.
The layout used in this book doesnt require much explaining, however you should look out for:
As they are actions you have to perform. For example, these blocks of text might tell you to create a le
or make some code change. You will mostly nd these in the application walk throughs. This syntax is
useful because it helps distinguish between code changes I want you to make to your application, and just
NOTE: You will also come across blocks of text like this. These will contain little bits of information that
11
IMPORTANT: You will also see a few of these. These are important Gotchas you should pay careful
attention to.
Ok, enough chat! Lets get started. Good luck and have fun!
12
Changelog
Switched to importing PouchDB instead of requiring, added a note on installing typings for Camper-
Chat
Formatting improvements
Implemented ionicBootstrap
13
Updated to use @ViewChild instead of getComponent
Added changelog
14
New Concepts
Ionic 1 was built on top of Angular 1, which is a framework for building complex and scaleable Javascript
applications. What Ionic does on top of Angular is that it provides a bunch of functionality to make making
mobile apps with Angular easier. Then along came Angular 2 which is the next iteration of the Angular
framework, which comes with a bunch of changes and improvements. In order for Ionic to make use of
Angular 2 a new framework was required on their end as well, which is how Ionic 2 came about. In short,
by using Ionic 2 & Angular 2 we will be able to make apps that perform even better on mobile, adhere to
the latest web standards, are scalable, reusable, modular and so on.
With the introduction of Angular 2, there has been a lot of changes to how you develop an application.
There are massive conceptual changes, and there have also been a few changes to things like template
syntax as well.
<ion-menu [content]="content">
<ion-toolbar>
<ion-title>Pages</ion-title>
</ion-toolbar>
<ion-content>
<ion-list>
<button ion-item *ngFor="let p of pages" (click)="openPage(p)"></button>
</ion-list>
</ion-content>
</ion-menu>
15
which isnt too dierent to Ionic 1, and your Javascript will look something like this:
@Component({
template: '<ion-nav [root]="rootPage"></ion-nav>'
})
export class MyApp {
constructor(platform: Platform) {
platform.ready().then(() => {
});
}
}
which is very dierent to Ionic 1. If youre already familiar with ECMAScript 6 or TypeScript then a lot of
this probably wont be too hard of a change for you, but if these are completely new concepts to you
(and for most people it will be) the transition might be a little more dicult. To help put your mind at ease
somewhat, ES6 and TypeScript was all completely new to me when the Ionic 2 alpha rst came out, and
within a pretty short time period, I started to feel very comfortable with it. Now I am way more comfortable
with the new syntax and structure than I ever was with Ionic 1.
In this lesson we are going to broadly cover some of the new concepts and syntax in Ionic 2 & Angular 2.
The intention is just to give you a bit of a background, we will get into specics later.
16
ECMAScript 6 (ES6)
Before we talk about ECMAScript 6, we should probably talk about what ECMAScript even is. Theres
quite a bit of history involved which we wont dive into, but for the most part: ECMAScript is a standard,
Javascript is an implementation of that standard. ECMAScript denes the standard and browsers imple-
ment it. In a similar way, HTML specications (most recently HTML5) are dened by the organising body
and are implemented by the browser vendors. Dierent browsers implement specications in dierent
ways, and there are varying amounts of support for dierent features, which is why some things work
The HTML5 specication was a bit of a game changer, and in a similar way so is the ECMAScript 6 spec-
ication. It will bring about some pretty drastic changes to the way you will code with JavaScript and in
general, will make Javascript a much more mature language that is capable of more easily creating large
and complex applications (which JavaScript was never really originally intended to do).
Were not going to go too much into ES6 here, because you will learn what you need to know throughout
the book, but I will give a few examples to give you a sense of what it actually is. Some features ES6
Classes
class Shape {
constructor (id, x, y) {
this.id = id
this.move(x, y)
}
move (x, y) {
this.x = x
this.y = y
}
}
17
This is a big one, and something you would be familiar with if you have experience with more traditional
programming languages like Java and C#. People have been using class-like structures in Javascript for
a long time through the use of functions, but there has never been a way to create a real class. Now there
is. If you dont know what a class is, dont worry, there is an entire lesson dedicated to it later.
Modules
// lib/math.js
export function sum (x, y) { return x + y }
export var pi = 3.141593
// someApp.js
import * as math from "lib/math"
console.log("2PI = " + math.sum(math.pi, math.pi))
// otherApp.js
import { sum, pi } from "lib/math"
console.log("2PI = " + sum(pi, pi))
Modules allow you to modularise your code into packages that can be imported anywhere you need in
your application, this is something that is going to be heavily used in Ionic. We will get into this more later,
but essentially any components we create in our application we export so that we can import them
elsewhere.
Promises
Promises are something that have been made available by services like ngCordova previously, but now
they are natively supported, meaning you can do something like this:
doSomething().then((response) => {
console.log(response);
});
18
Block Scoping
Currently, if you dene a variable in Javascript it is available anywhere within the function that it was dened
in. The new block scoping features in ES6 allow you to use the let keyword to dene a variable only within
If I were to try and access the x variable outside of the for loop, it would not be dened.
One of my favourite new additions is fat arrow functions, which allow you to do something like this:
someFunction((response) => {
console.log(response);
});
rather than:
someFunction(function(response){
console.log(response);
});
At a glance, it might not seem all that great, but what this allows you to do is maintain the parents scope.
In the top example if I were to access the this keyword it would reference the parent, but in the bottom
var me = this;
someFunction(function(response){
console.log(me.someVariable);
19
});
to achieve the same result. With the new syntax, there is no need to create a static reference to this you
This is by no means an exhaustive list of new ES6 features so for some more examples take a look at
es6-features.org.
TypeScript
Another concept we should cover o on is TypeScript which is used in Ionic 2. Its important to point out
that although Ionic 2 uses TypeScript, you dont have to use it yourself to build Ionic 2 applications - you
can just use plain ES6. That said though, TypeScript provides additional features and makes some things
(dependency injection in particular) a lot easier, and it will soon become the default for Ionic 2 so it doesnt
We will be using TypeScript in this book, so lets talk a little bit more about what it is and how it is dierent
If youre anything like me then you still wouldnt know what TypeScript is from that description (it seems
easy to understand denitions are a big no-no in the tech world). In fact, a StackOverow post did a much
better job at explaining what TypeScript is basically, TypeScript adds typing, classes and interfaces to
JavaScript.
Using TypeScript allows you to program in the way you would for stricter, object oriented languages like
Java or C#. Javascript wasnt originally intended to be used for designing complex applications so the
language wasnt designed that way. It certainly is possible already to use JavaScript in an object oriented
manner by using functions as classes as we discussed before but its not quite as clean as it could be.
But I mentioned before that ES6 is already adding the ability to create classes so why do we still need
20
Its called TypeScript not ClassScript
TypeScript still provides the ability to use static typing in JavaScript (which means it is evaluated at compile
time, opposed to dynamic typing which is evaluated at run time). Using typing in TypeScript will look a
The code above states that x should be a number (x: number), y should be a number (y: number),
and that the add function should return a value that is a number (add(): number). So in this example,
we will receive an error because were trying to supply characters to a function that expects only numbers.
This can be very useful when creating complex applications, and adds an extra layer of checks that will
constructor(platform: Platform) {
platform.ready().then(() => {
});
}
}
You can see some TypeScript action going on. The code above is saying that rootPage can be the any
type, which is a special type which basically just means it can be anything at all, and platform has a type
21
of Platform. As you will see later, the ability to give things types comes in very handy for an important
Since the default option for Ionic 2 is TypeScript, and it is what most people are using, this book focuses
on using TypeScript. For the most part, ES6 and TypeScript projects look pretty much the same, and
Transpiling
Transpiling means converting from one language to another language. Why is this important to us? Ba-
sically, ECMAScript 6 gives us all of this cool new stu to use, but ES6 is just a standard and it is not
completely supported by browsers yet. We use a transpiler to convert our ES6 code into ES5 code (i.e. the
All the code inside of the app folder is transpiled into valid ES5 code
You dont need to worry about this process as it is all automatically handled by Ionic.
Web Components
Web Components are kind of the big thing in Angular 2, and they werent really feasible to use in Angular
1. Web Components are not specic to Angular, they are becoming a new standard on the web to create
modular, self contained, pieces of code that can easily be inserted into a web page (kind of like Widgets
in WordPress).
In a nutshell, they allow us to bundle markup and styles into custom HTML elements. - Rob Dodson
Rob Dodson wrote a great post on Web Components where he explains how they work and the concepts
22
behind it. He also provides a really great example, and I think it really drives the point home of why Web
Basically, if you wanted to add an image slider as a web component, the HTML for that might look like this:
<img-slider>
<img src="images/sunset.jpg" alt="a dramatic sunset">
<img src="images/arch.jpg" alt="a rock arch">
<img src="images/grooves.jpg" alt="some neat grooves">
<img src="images/rock.jpg" alt="an interesting rock">
</img-slider>
<div id="slider">
<input checked="" type="radio" name="slider" id="slide1" selected="false">
<input type="radio" name="slider" id="slide2" selected="false">
<input type="radio" name="slider" id="slide3" selected="false">
<input type="radio" name="slider" id="slide4" selected="false">
<div id="slides">
<div id="overflow">
<div class="inner">
<img src="images//rock.jpg">
<img src="images/grooves.jpg">
<img src="images/arch.jpg">
<img src="images/sunset.jpg">
</div>
</div>
</div>
<label for="slide1"></label>
<label for="slide2"></label>
<label for="slide3"></label>
23
<label for="slide4"></label>
</div>
In the future, rather than downloading some jQuery plugin and then copying and pasting a bunch of HTML
into your document, you could just import the web component and add something simple like the image
Web Components are super interesting, so if you want to learn more about how they work (e.g. The Shadow
Dom and Shadow Boundaries) then I highly recommend reading Rob Dodsons post on Web Components.
24
Chapter 2
Ionic 2 Basics
25
Lesson 1: Generating an Ionic 2 Application
Weve covered quite a bit of context already, so you should have a reasonable idea of what Ionic 2 is all
about and why some of the changes have been made. With that in mind, were ready to jump in and start
Installing Ionic
Before we can start building an application with Ionic 2 we need to get everything set up on our computer
rst. It doesnt matter if you have a Mac or PC, you will still be able to nish this book and produce both
IMPORTANT: If you already have Ionic 1 set up on your machine then you can skip straight to the next
section. All you will need to do is run npm install -g ionic@beta or sudo npm install -g
ionic@beta to get everything needed for Ionic 2 set up. Dont worry if you want to keep using Ionic 1 as
well, after you update you will be able to create both Ionic 1 and Ionic 2 projects.
First you will need to install Node.js on your machine. Node.js is a platform for building fast, scalable
network applications and it can be used to do a lot of dierent things. Dont worry if youre not familiar
with it though, we wont really be using it much at all - we need it installed for Ionic to run properly and to
https://nodejs.org/
Once you have Node.js installed, you will be able to access the node package manager or npm through
> Install Ionic and Cordova by running the following command in your terminal:
26
or
You should also set up the Android SDK on your machine by following one of these guides:
If you are on a Mac computer then you should also install XCode which will allow you to build and sign
applications. You dont have to worry about setting up the iOS SDK as if you have a Mac this will be
handled by XCode and if you dont have a Mac then you cant set it up on your computer anyway (well
talk more about how you can build iOS applications without a Mac later).
You should now have everything you need set up and ready to use on your machine! To verify that the
Ionic CLI (Command Line Interface) is in fact installed on your computer, run the following command:
ionic -v
You can also get some detailed information about your current installation by running the following com-
ionic info
It should spit out some info about your current environment, heres mine at the time of writing this:
27
NOTE: The Ionic Framework and Ionic CLI (Command Line Interface) are two separate things. The CLI
is what we just installed, and it provides a bunch of tools through the command line to help create and
manage your Ionic projects. The Ionic CLI will handle downloading the actual Ionic Framework onto your
Once Ionic is installed, generating applications is really easy. You can simply run the ionic start
command to create a new application with all of the boilerplate code and les you need.
To generate a new application called MyFirstApp that uses the blank template. Ionic comes with some
28
templates built in, in the example above we are using the blank template, but you could also use:
or
to use the default starter which is a tabs application. Notice that every time we are supplying the v2 ag.
If you leave this ag o it will just create a normal Ionic 1 application (handy for those of you who still need
to use V1 as well, but make sure you dont forget it when building Ionic 2 apps!).
NOTE: All Ionic 2 projects use TypeScript by default now. Since TypeScript is an extension of ES6, ES6
code will still work in TypeScript projects if you want to use it, but all Javascript les should have the .ts
Were just going to stick with a boring blank template for now. Once your application has been generated
you will want to make it your current directory so we can do some more stu to it.
> Run the following command to change to the directory of your new Ionic project
cd MyFirstApp
If using the command prompt or terminal is new to you, you might want to read this tutorial for a little more
in depth explanation - the content is specically for Ionic 1 but it should give you a general sense of how
Adding Platforms
Eventually we will be building our application with Cordova (in fact the application that the Ionic CLI gen-
erates is a Cordova application), and to do that we need to add the platforms we are building for. To add
29
the Android platform you can run the following command:
If you are building for both platforms then you should run both commands. This will set up your application
so that it can be built for these platforms, but it wont really have any eect on how you build the application.
As I will explain shortly, most of our coding will be done inside of the app folder, but you will also nd
another folder in your project called platforms - this is where all of the conguration for specic platforms
live. Were going to talk about all that stu way later though.
The beauty of HTML5 mobile applications is that you can run them right in your browser whilst you are
developing them. But if you try just opening up your project in a browser by going to the index.html le
An Ionic project needs to run on a web server - this means you cant just run it by accessing the le directly,
but it doesnt mean that you actually need to run it on a server on the Internet, you can deploy a completely
self contained Ionic app to the app stores (which we will be doing). Fortunately, Ionic provides an easy
way to view the application through a local web server whilst developing.
> To view your application through the web browser run the following command:
ionic serve
This will open up a new browser with your application open in it and running on a local web server. Right
30
Not only will this let you view your application but it will also update live with any code changes. If you edit
and save any les, the change will be reected in the browser without having to reload the application by
31
To stop this process just hit:
Ctrl + C
when you have your command terminal open. Also keep in mind that you cant run normal Ionic CLI
commands whilst ionic serve is running, so you will need to press Control + C before running any
commands.
There may come a time when you want to update to a later version of Ionic, especially during the beta
period where changes will be happening more frequently. The easiest way to update the version of Ionic
that your application is using is to rst update the Ionic CLI by running:
or
again, and then updating the package.json le of your project. You should see something like this in that
le:
"dependencies": {
"@angular/common": "^2.0.0-rc.1",
"@angular/compiler": "^2.0.0-rc.1",
"@angular/core": "^2.0.0-rc.1",
"@angular/http": "^2.0.0-rc.1",
"@angular/platform-browser": "^2.0.0-rc.1",
"@angular/platform-browser-dynamic": "^2.0.0-rc.1",
"@angular/router": "^2.0.0-rc.1",
"es6-shim": "^0.35.0",
"ionic-angular": "2.0.0-beta.7",
32
"ionic-native": "^1.1.0",
"ionicons": "3.0.0",
"pouchdb": "^5.3.2",
"reflect-metadata": "^0.1.3",
"rxjs": "5.0.0-beta.6",
"zone.js": "^0.6.12"
},
Simply change the ionic-angular version number to the latest version, and then run:
npm install
inside of your project directory. This will grab the latest version of the framework and add it to your project.
IMPORTANT: Keep in mind that there may be other dependencies in package.json that need to be up-
Make sure to read the changelogs to check for any breaking changes when a new version is released,
which may mean that you have to update parts of your code as well.
Often it is easiest to just create a fresh new project after updating the Ionic CLI, and porting your code over.
If that is not an option, just make sure you read the changelog carefully, and update your dependencies
and code accordingly. As Ionic 2 becomes more and more stable, this becomes less of a problem as the
33
Lesson 2: Anatomy of An Ionic 2 Project
Now that weve covered how to get Ionic 2 installed and how to generate a project, I want to cover what
the various les and folders contained within your newly generated project do. When you create a blank
34
At rst glance, theres an intimidating amount of stu there - but theres really not that much you need to
35
worry about, and it will all make total sense with a little explanation. Were going to discuss what everything
does, but I will cover the important stu rst in more detail (mainly the les and folders that you will be
These les and folders are ones that you will be using on a frequent basis, so its important that you
app
This is where most of the action occurs. In a default application, your app folder will contain:
A pages folder
A theme folder
An app.ts le
The pages folder will contain all of the page components for your application. If you look inside of the
pages folder you should see another folder called home. This is a component, and is made up of a class
denition (home.ts), a template (home.html), and style denitions (home.scss). For every page in your
application you will add another folder here (we can actually get the Ionic CLI to do this for us automatically),
so as well as the home folder you may also have login, intro, checkout, about and many more.
The theme folder will contain all of the .scss les which dene application wide styles for the application.
There are iOS, Android and Windows specic les, a core shared le, and another that contains variables
you can override. There is a whole section later in this book dedicated to theming Ionic 2 applications so
The app.ts le denes the root component for your application. Again, we discuss what the root compo-
nent is and what it does in detail in other parts of this book so I wont cover much right now, but essentially
36
As well as the default folders that your app will contain, as you start building your application you will likely
see a few more folders in here as well, but well get to that later.
IMPORTANT: This is a really important concept to understand so make sure you read this over a couple
of times if you dont get it initially. In the introduction section of this book we talked about webpack,
transpiling and a bunch of other fancy ES6 stu - the important thing to remember is that the ES6 and
TypeScript features we are using arent actually supported by browsers yet, so we need to transpile
them into valid ES5 code. When you run or build your application, Ionic will bundle up everything in this
app folder, perform all the magic it needs to perform, and then spits it out into the build folder inside of
www.
When you are viewing your application through the browser you are actually viewing the bundled version
inside of the www folder, not the code you created in the app folder. Likewise, when you build your
application for release on iOS and Android (which we will discuss later) it is this www folder that will be
used, not the app folder. DO NOT EDIT CODE IN THE WWW FOLDER, EXCEPT FOR INDEX.HTML -
any changes you make in there (which is a bad idea anyway) will be overwritten when your app is rebuilt.
Although you should not edit code in this folder, you can however add other static resources to this folder
Using Angular 2 and the new ES6 syntax means the project structure and build process is quite a bit more
complicated, but fortunately for us Ionic handles basically all of it for us. So dont worry too much about
it, this is just something important to keep in mind so that you know what is going on.
www
Weve already touched on this above, but the www folder is the web root folder, and will contain the
compiled code for your application that is ready to be run through the browser or on devices.
The most important thing to remember about this folder is that although in general you dont need to touch
it, you will need to add some static resources here that your app will use. In most cases that will be images,
but it also might be things like JSON les that you want to bundle with your application to provide some
data.
37
If you wanted to use an image in your application for example, you would add an <img> tag to one of your
components in the app folder. The path you would use would look something like this:
Given that path, you might assume that your image should also live inside of the app folder as well. But
since your app gets bundled into a app.bundle.js le that will then be moved inside the www folder
automatically, thats actually where your image will need to live. So given the path above, your image
www/images/cutedog.jpg
and if you were referring to the image in one of your .scss les the path should be:
../../images/cutedog.jpg
because the compiled CSS les will be located in their own css folder in the build directory (inside of
www). All of this isnt exactly obvious and is a little confusing, but it works - just something to remember!
cong.xml
You wont need to edit this le very often, but it is a very important le. The cong.xml le is basically
used as a way to tell Cordova how to build your application for iOS and Android. Youll have to supply
some important conguration information in here which we will discuss later, but you can also dene some
preferences in here (like whether the splash screen should auto hide, whether the app should be portrait
only, and a bunch more). You will usually only really worry about this le when youre starting to look at
Obviously everything in your Ionic project plays a role, otherwise it wouldnt be there. But some of it is for
more advanced use cases you might not need to worry about, and some of it is just pure conguration
38
Conguration Files
If you take a look in the root folder of your generated project you will see a bunch of conguration les.
Aside from the cong.xml le which we discussed above, you can pretty safely just ignore all of these.
The only le you may want to edit is the package.json le to update the version of Ionic you are using.
resources
This folder simply contains the splash screens and icons that will be used for iOS and Android when you
build your application. Ill show you a really easy way to create these resources with the Ionic CLI later.
hooks
Hooks are used as part of your applications build process, and you can add custom scripts in here to
hook into various parts of the build process. At a beginner level you likely will not need to touch this at
all, but if you wanted to start developing more complicated (but useful) workows, related to versioning or
deployment of your application for example, you could create some hooks here.
node_modules
This is another folder that you wont have to touch at all, but its where all the goodies are stored. If you
take a look in this folder you will nd things like ionic-angular, angular2, and ionicons. This is where all
of the source les for the various libraries that your application depends on are stored (including the Ionic
framework itself).
39
Lesson 3: Ionic CLI Commands
The Ionic CLI is a super powerful tool - weve already gone through how to use it to generate a new project
and display your application in the browser, but theres a bunch more commands you should know about
too, so lets go through some of them. This is by no means an exhaustive list, but it will cover all of the
Im going to list out a few commands now and what they do, and since some commands have multiple
dierent arguments that you can supply to them I will be using the following syntax:
in place of:
and
for the sake of keeping things neat and tidy. Lets get into it!
ionic serve -l
Youve already seen ionic serve which will launch your application in the browser with live reloading,
but this command will launch Ionic Lab, which looks like this:
40
It will allow you to quickly see side by side what your application looks like on both iOS and Android.
This will allow you to add platforms that you plan on building for.
This will allow you to add Cordova plugins to your project, simply supply the plugin name.
When you are ready to build your application for iOS and Android you can simply run these commands to
41
build your project. Theres a little bit more to it than this to actually get it on your device and then submit
If you want to test your application on a real device you can use this command to deploy the app directly to
your device. For more information on this, please see the Testing & Debugging section later in the book.
Instead of deploying your application to a real device, this will launch an emulator on your machine and
ionic g [page|component|directive|pipe|provider|tabs]
This is the generate command and it is one of my favourites, its a real time saver. As I mentioned before,
your Ionic project will contain a bunch of components like the home page. To create more components you
can manually create a new folder and add all the required les, or you can just run the ionic generate
command to do it automatically for you, with some handy boilerplate code in place. As well as pages, this
command can also generate generic components, directives, pipes, providers and tabs. Id recommend
using it for any new component you are adding to your application.
This will allow you to see a list of all the plugins you have installed in your project.
This will allow you to remove any plugin from your project by supplying the plugin name.
This will allow you to remove a platform that you have previously added.
As I mentioned, this is not an exhaustive list but it does cover the most commonly used commands. I
42
Lesson 4: Decorators
Each class (which we will talk about in the next section) you see in an Ionic 2 application will have a
@Component({
someThing: 'somevalue',
someOtherThing: [Some, Other, Values]
})
They denitely look a little weird, but they play an important role. Their role in an Ionic 2 application is
to provide some metadata about the class you are dening, and they always sit directly above your class
@Decorator({
/*meta data goes here*/
})
export class MyClass {
/*class stuff goes here*/
}
This is the only place you will see a decorator, they are used purely to add some extra information to a
class (i.e. they decorate the class). So lets talk about exactly how and why we would want to use these
The decorator name itself is quite useful, heres a few you might see in an Ionic 2 application:
@Component
@Pipe
@Directive
We can supply an object to the decorator to provide even more information on what we want. Heres the
43
@Component({
templateUrl: 'build/pages/home/home.html'
})
export class HomePage {
Now this class knows where it needs to fetch its template from, which will determine what the user will
actually see on the screen (well be getting into that later as well). If youve got a super simple template,
maybe you dont even want to have an external template le, and instead dene your template like this:
@Component({
template: `<p>Howdy!</p>`
})
export class HowdyPage {
Some people even like to dene large templates using template. Since ES6 supports using backticks
(the things surrounding the template above) to dene multi line strings, it makes dening large templates
like this a viable option if you prefer (rather than doing something ugly like concatenating a bunch of strings).
This object that you can supply to the decorator can be used for a lot more than just dening the template
though, heres a look at a more complex decorator that you might come across:
@Component({
templateUrl: 'build/pages/my-page/my-page.html',
providers: [MyProvider],
directives: [MyCoolDirective, MyCoolerDirective],
pipes: [MyPipe]
})
export class MyPage {
44
}
Now things are looking a little interesting. Were still telling the class where to grab the template from, but
now we are also telling it that we wanted to include some providers, directives and pipes. If you dont
know what these are yet dont worry, well be discussing them shortly.
Im not going to try and cover everything you can do with a decorator here, but one more important
thing to note is that since Ionic has removed the @App decorator in favour of following a style that is more
consistent with Angular 2, we now need to bootstrap our applications (the @App decorator used to handle
Bootstrapping just basically means setting up the root component of your application (which lives in the
app.ts le), which is essentially the rst component that is created that will then go on and create all of the
@Component({
template: '<ion-nav [root]="rootPage"></ion-nav>'
})
export class MyApp {
constructor(platform: Platform) {
platform.ready().then(() => {
StatusBar.styleDefault();
45
});
}
}
ionicBootstrap(MyApp, [Data]);
You may notice that this @Component doesnt really look any dierent to any of the other components,
ionicBootstrap(MyApp, [Data]);
This is a special Ionic avoured version of the bootstrap function provided by Angular 2. We supply it the
component we want to use as the root component (which is the component dened in the same le), and
we also provide it an array of any providers we are using (rather than declaring them in the @Component
You can also supply a cong option as a third parameter to the bootstrap function to dene some default
ionicBootstrap(MyApp, [Data], {
backButtonText: 'Go Back',
iconMode: 'ios',
modalEnter: 'modal-slide-in',
modalLeave: 'modal-slide-out',
tabbarPlacement: 'bottom',
pageTransition: 'ios'
});
This denes some behaviour that we want the app to exhibit, so if we used the cong above our app would
use Go Back as the back button text and would use the iOS icons instead of the Android icons (as well
You can also use the cong object to dene platform specic behaviour:
46
ionicBootstrap(MyApp, [Data], {
tabbarPlacement: 'bottom',
platforms: {
ios: {
tabbarPlacement: 'top',
}
}
});
This conguration says that we want to place our tab bars at the bottom by default, but if we are running
on the iOS platform then we want them to display at the top instead (whereas usually tabs are placed at
Now that weve covered the basics of what a decorator is and what it does, lets take a look at some
specics.
There are quite a few dierent decorators that we can use. In the end, their main purpose is simply to
describe what the class we are creating is, so that it knows what needs to be imported to make it work.
Lets discuss the main decorators you are likely to use, and what the role of each one is. Were just going to
be focusing on the decorator for now, we will get into how to actually build something useable by dening
@Component
I think the terminology of a component can be a little confusing in Ionic 2. As I mentioned, our application
is made up of a bunch of components that are all tied together. These components are contained within
47
home
home.ts
home.html
home.scss
A @Component is not specic to Ionic 2, it is used generally in Angular 2. A lot of the functionality provided
by Ionic 2 is done through using components. In Ionic 2 for example you might want to create a search
bar, which you could do using one of the components that Ionic 2 provides like this:
<ion-searchbar></ion-searchbar>
You simply add this custom tag to your template. Ionic 2 provides a lot of components but you can also
create your own custom components, and the decorator for that might look something like this:
@Component({
selector: 'my-cool-component'
})
which would then allow you to use it in your templates like this:
<my-cool-component></my-cool-component>
NOTE: Technically speaking a component should have a class denition and a template. Things like pipes
and providers arent viewed on the screen so have no associated template, they just provide some addi-
tional functionality. Even though these are not technically components you may often see them referred
@Directive
The @Directive decorator allows you to create your own custom directives. Typically, the decorator would
@Directive({
selector: '[my-selector]'
48
})
Then in your template you could use that selector to trigger the behaviour of the directive you have created
by adding it to an element:
<some-element my-selector></some-element>
It might be a little confusing as to when to use @Component and @Directive, as they are both quite similar.
The easiest thing to remember is that if you want to modify the behaviour of an existing component use a
@Pipe
@Pipe allows you to create your own custom pipes to lter data that is displayed to the user, which can
@Pipe({
name: 'myPipe'
})
which would then allow you to implement it in your templates like this:
<p>{{someString | myPipe}}</p>
Now someString would be run through your custom myPipe before the value is output to the user.
@Injectable
An @Injectable allows you to create a service for a class to use. A common example of a service created
using the @Injectable decorator, and one we will be using a lot when we get into actually building the apps,
is a Data Service that is responsible for fetching and saving data. Rather than doing this manually in your
classes, you can inject your data service into any number of classes you want, and call helper functions
49
from that Data Service. Of course this isnt all you can do, you can create a service to do anything you
like.
An @Injectable will often just look like a normal class with the @Injectable decorator tacked on at the top:
@Injectable()
export class DataService {
IMPORTANT: Remember that just about everything you want to use in Ionic 2 needs to be imported rst
(we will cover importing in more detail in the next section). In the case of pipes, directives, injectables and
components they not only need to be imported, but also declared in your decorator like in the example I
gave above:
@Component({
templateUrl: 'build/pages/my-page/my-page.html',
providers: [DataService],
directives: [MyCoolDirective, MyCoolerDirective],
pipes: [MyPipe]
})
Summary
The important thing to remember about decorators is: theres not that much to remember. Decorators are
powerful, and you can certainly come up with some complex looking congurations. Your decorators may
become complex as you learn more about Ionic 2, but in the beginning, the vast majority of your decorators
@Component({
templateUrl: 'build/pages/home/home.html'
})
50
I think a lot of people nd decorators o putting because at a glance they look pretty weird, but they look
way scarier than they actually are. In the next lesson well be looking at the decorators partner in crime:
the class. The class denition is where we will do all the actual work, remember that the decorator just sits
51
Lesson 5: Classes
In the last section we covered what a decorator is. To recap, its a little bit of code that sits above our class
denitions that declares what the class is, what the class needs, and how the class should be congured.
What is a Class?
Depending on your experience with programming languages, you may or may not know what a class is.
So Im going to take a step back rst and explain what a class is as a general programming concept, as it
Classes are used in Object Oriented Programming (OOP), they essentially behave as blueprints for ob-
jects. You can dene a class, and then using that class you can create, or instantiate, objects from it. If
classes are a completely new concept to you, itd be worth doing a little bit of your own research before
class Person {
constructor(name, age){
this.name = name;
this.age = age;
}
setAge(age){
this.age = age;
return true;
}
getAge(){
return this.age;
52
}
setName(name){
this.name = name;
return true;
}
getName(){
return this.name
}
isOld(){
return this.age > 50;
}
}
This class denes a Person object. The constructor is run whenever we create an instance of this
class (an object is an instance of a class), and it takes in two values: name and age. These values are
used to set the member variables of the class, which are this.name and this.age.
These values can be accessed from anywhere within the object by using the this keyword. The this
keyword refers to the current scope, so what it evaluates to depends on where you use it, but if you use it
within a class (and not within a callback or anything else which would change the scope) this will refer to
If you imagine yourself as this and your location in the physical world as the scope, consider the following
example: if you are in your hotel room your scope is the room, if you leave your room your scope is now
the hotel itself, if you leave the hotel your scope is now the world (or the country you are in if you prefer).
If youre not familiar with the this keyword, Id recommend reading this.
We have our class dened which acts as a blueprint for creating objects, so we could create a new Person
53
object like this:
The two values Ive supplied here will be passed into the constructor of the Person class to set up the
console.log(john.getName());
Johns name would be logged to the console. Similarly we could also call the getAge function to retrieve
his age or we could even change his name or age using the set functions. Getters and setters are very
common for classes, but weve also dened a more interesting function here which is isOld. This will
return true if the Person is over 50 years old, which is not the case for John.
Perhaps the most important concept to remember is that the class is just a blueprint, an object is kind
of like an individual copy of a class. So we can have multiple objects created from the same class, e.g:
console.log(john.isOld());
console.log(louise.isOld());
console.log(david.isOld());
In the code above, John, Louise and David are all individual objects of the Person class, and maintain
their values separately. If we ran the code above, it would only return true for David (he may be old, but
Im sure he is wise).
Classes in Ionic 2
We know what a class is, but why are they suddenly a thing in Ionic 2 and Angular 2? Weve touched on this
earlier, but classes are a new addition to Javascript with the ES6 specication. It is certainly a welcome
54
change because it is one of the most widely used patterns in programming, and in fact most JavaScript
applications were already using classes anyway, ES6 just made it more ocial.
Before ES6 it was common (and I guess it still is since most people are still using ES5) to create a class
like structure by using functions. This looks a little something like this:
Person.prototype.isOld = function() {
return this.age > 50;
};
It looks a bit dierent, but the end result is basically the same. Since ES6 adds a class keyword we can
@Component({
templateUrl: 'build/pages/home/home.html'
})
export class HomePage {
55
}
The rst thing you will notice is the import statements. Anything that is required by the class that you are
creating will need to be imported. In this case we are importing Component from @angular/core which
allows us to use the @Component decorator, and NavController from the Ionic library which we can use
to control navigation.
We are also importing SomePage which is a class of our own creation. The path for this simply follows
the directory structure of your project, in this case we have the SomePage component dened inside a
folder called pages which is one level above the current le. The import should link to wherever the .ts le
is for the class, but it is not necessary to include the .ts extension.
Next up we have the decorator, which you should be pretty familiar with by now. Remember that anything
that you are supplying to the decorator (like pipes, directives etc.) will need to be imported rst, just like
the classes above were imported. It might look something like this:
@Component({
templateUrl: 'build/pages/home/home.html',
pipes: [MyPipe],
directives: [MyDirective]
})
export class HomePage {
56
constructor() {
}
}
In the case of providers, pipes, components and directives, they will also need to be declared inside of the
decorator. However, if you are declaring providers in your root component (app.ts) make sure you add
Once we get past the decorator, we nally arrive at the class itself. Notice though that it is preceded by
The export keyword works in tandem with the import keyword, so we export classes that we want to
import somewhere else. The last thing we have left to discuss is the constructor. Weve already talked
about the role of a constructor in classes in general, and it is no dierent here: the code inside of the
Theres a little bit more to it that you need to know though. In Ionic 2 we need to inject any services that
we want to use inside of this class into the constructor, which looks like this:
platform.ready().then(() => {
});
In this example we want to use the Platform service to detect when the device is ready, so we inject it into
57
the constructor and then we can use it within the constructor.
We dont need to use platform outside of the constructor here, but in most cases you will want to use the
services you inject elsewhere in the class as well. So to make the service available to any function within
the class, you must set it as a member variable. This will look like this:
@Component({
templateUrl: 'build/pages/home/home.html'
})
export class HomePage {
constructor(platform: Platform) {
this.platform = platform
}
someOtherMethod(){
this.platform.ready().then(() =>{
});
}
}
OR we can use the public keyword I mentioned before when using TypeScript to automatically create
58
@Component({
templateUrl: 'build/pages/home/home.html'
})
export class HomePage {
someOtherMethod(){
this.platform.ready().then(() =>{
});
}
}
You can also use the private keyword instead of public which limits access to that particular member
variable to the current class (i.e. another class would not be able to access the variable if it were declared
private).
Also note that any variables declared above the constructor like someMemberVariable is here will also
in this class through this.someMemberVariable. Any services you need to inject (and possibly set
up as member variables) should be added to the constructor, any member variables you want to create
for any other purpose should be declared above the constructor. If this concept sounds confusing to you
right now it should make a little more sense when we start going through some examples.
Now we can access platform from anywhere in this class through this.platform. If we did not set
59
up this member variable and tried to access platform from our someOtherMethod function, it would not
work.
Creating a Page
Pages will usually make up a large portion of your application - for each screen you want to have in your
application you will have a separate Page dened. Previously, there was a special decorator in Ionic for
So, as weve already discussed, the class for a page might look something like this:
@Component({
templateUrl: 'build/pages/home/home.html'
})
export class MyPage {
constructor() {
}
}
and the template le we are referencing in the decorator might look something like this:
<ion-header>
<ion-navbar>
<ion-title>
My Page
</ion-title>
</ion-navbar>
</ion-header>
60
<ion-content>
<ion-list>
<ion-item>I</ion-item>
<ion-item>Am</ion-item>
<ion-item>A</ion-item>
<ion-item>List</ion-item>
</ion-list>
</ion-content>
The template le makes up what the user will actually see (were going to discuss templates in a lot more
detail later). The template le and the class work in unison: the class denes what template is to be shown
to the user, and the template can make use of data and functions available in the class.
Weve covered the basic structure of what a Page class looks like and what the constructor function
does, but you can also add other functions that your page can make use of, for example:
@Component({
templateUrl: 'build/pages/home/home.html'
})
export class MyPage {
constructor() {
//this runs immediately
}
someMethod(){
//this only runs when called
61
}
someOtherMethod(){
//this only runs when called
}
You could have your constructor call these other functions or you could even have them triggered by a
user clicking a button in the template for example. These additional functions can be added to any class,
its not just specic to pages. We will cover these concepts in much more detail later, for now we just want
to understand the basic structure of the dierent class types and what they do.
NOTE: You can auto generate a Page in your project using the command ionic g page MyPage
Creating a Component
The code for a generic component looks a lot like the code for a page (remember, a page is a component),
just as it will look like just about everything else we create. When creating pages with components we use
Ionics built in navigation to handle displaying them. A page is a component that will take up the whole
screen (i.e. a view for the user), but a component will also allow you to create your own custom element
that you can insert into your templates as well. Maybe you want to create a custom date picker component
to add to your page, or a box that displays random motivational quotes - in both of these cases you would
@Component({
selector: 'my-component',
62
templateUrl: 'build/components/my-component/my-component.html'
})
export class MyComponent {
text: any;
constructor() {
this.text = 'Hello World';
}
}
Really the only dierence with the component here is that it species a selector. This will be the name of
the element that you use to insert the component into your template, i.e:
<my-component></my-component>
Before we get to using it though, lets also talk about the template for the component. This isnt really any
dierent to how the template for a page works. Were referencing a le called my-component.html which
<div>
{{text}}
</div>
Just like with a page, we can reference data that is stored in the class denition (as well as functions). With
<div>Hello World</div>
into the DOM. Which is pretty boring of course, but you can create some pretty interesting, and reusable,
stu with this functionality. So lets take a quick look at how to now actually use the component in a page.
First, you just need to import it and add it to the list of directives for the page you want to use it on:
63
import {Component} from '@angular/core';
import {MyComponent} from '../../components/my-component/my-component';
@Component({
templateUrl: 'build/pages/home/home.html',
directives: [MyComponent]
})
Weve imported the component into our page, declared it in the directives array in the decorator, and then
<my-component></my-component>
Its actually pretty unlikely that you will need to create your own custom components like this, as Ionic al-
ready provides most of what you would need (lists, tabs, buttons, inputs and so on). If you need something
that Ionic does not already provide though, then youll have to look at creating your own component.
NOTE: You can auto generate a Component in your project using the command ionic g component
MyComponent
Creating a Directive
As I mentioned before, components and directives are very similar, but in general a component is used to
create a completely new element, and a directive is used to modify the behaviour of an existing one.
@Directive({
selector: '[my-directive]'
})
64
export class MyDirective {
constructor() {
}
}
In this directive, we also have a selector like we did for the component - but its slightly dierent. Rather
than representing the name of a tag, this can be used as an attribute on an element. You will do this a lot
<button secondary>
or on your lists:
<ion-list no-lines>
but in this case were creating our own custom directive that we can use on anything, e.g:
<button my-directive>
But notice we dont actually have a template for this directive. Although we usually refer to any feature in
our applications as components, technically speaking a component consists of a class and a template
(view) - if it does not have a view then it is not a component (it would be better to refer to it as a service
or provider). To actually use this directive, you will need to make sure to import it and include it in the
decorator of the page you want to use it on, just like before:
@Component({
templateUrl: 'build/pages/home/home.html',
directives: [MyDirective]
})
65
I wanted to just cover the basics here, but I think its also useful to know about ElementRef. This will give
you access to the element that the directive was added to. You can include it in your directive like this:
@Directive({
selector: '[my-directive]'
})
export class MyDirective {
constructor(element: ElementRef) {
this.element = element;
}
}
NOTE: You can auto generate a Directive in your project using the command ionic g directive
MyDirective
Creating a Pipe
Pipes might seem a little complex at rst glance, but theyre actually really easy to implement. They look
like this:
@Pipe({
name: 'myPipe'
})
@Injectable()
export class MyPipe {
transform(value, args) {
66
//do something to 'value'
return value;
}
}
Notice that a pipe is also an @Injectable, we will talk about what that is in just a moment. So the idea is
that whatever you are passing through the pipe will go to this transform function, you do whatever you
need to do to the value, and then you return the new value back. Now this new value will be rendered out
to the screen, rather than the initial value. You can use it in your templates like this:
<p>{{someValue | myPipe}}</p>
which will run whatever someValue is through your custom pipe before displaying it. Once again, make
sure you import and reference the pipe in your decorator wherever you want to use it:
@Component({
templateUrl: 'build/pages/home/home.html',
pipes: [MyPipe]
})
NOTE: You can auto generate a Pipe in your project using the command ionic g pipe MyPipe
Creating an Injectable
An Injectable allows you to create a service that you can use throughout your application (like an interface
between your application and an external or internal data service). Injectables might also be referred to as
Providers. The @Injectable that the Ionic CLI automatically generates looks like this:
67
import {Injectable, Inject} from '@angular/core';
import {Http} from '@angular/http';
@Injectable()
export class MyProvider {
constructor(http: Http) {
this.http = http;
this.data = null;
}
load() {
if (this.data) {
// already loaded data
return Promise.resolve(this.data);
}
68
});
}
}
This code creates a provider called MyProvider that loads data from a JSON source (which can either be
a local JSON le, an external JSON le or a response from a server). It returns a promise, which will allow
the data to be retrieved from the http request after it has nished executing. If the data has already been
loaded then it just returns the data directly (also through a promise). Well go into fetching data with http
in more depth later, for now we just want to focus on the basics of an injectable.
So if we want to grab the data that this service returns, we would rst inject it into wherever we want to
@Component({
templateUrl: 'build/pages/home/home.html',
providers: [MyProvider]
})
export class MyPage {
and then you could make use of any function the injectable provides through this.myProvider, for
example:
this.myProvider.load().then((data) => {
69
console.log(data);
});
Notice that we use then() here because it is a promise that is returned, so we need to wait for the promise
to complete before we can access the data. You might extend this provider further so that it also oered
this.myProvider.save(someData);
Of course, you can use a provider to do other things besides fetching data - but thats a very common use
case.
NOTE: You can auto generate an Injectable in your project using the command ionic g provider
MyProvider
Summary
Weve taken a pretty broad and basic look at how to create the dierent types of classes in Ionic 2, and
theres certainly a lot more to know. But you should know enough now that it wont all look weird and foreign
to you when we start diving into some real examples. when we start diving into some real examples.
70
Lesson 6: Templates
Templates, I think, are one of the most fun bits of Ionic 2. Its where the power of the framework really
<ion-header>
<ion-navbar secondary>
<ion-title>
My Friends
</ion-title>
<ion-buttons end>
<button (click)="doSomethingCool()"><ion-icon
name="add-circle"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-searchbar (input)="getItems($event)"></ion-searchbar>
<ion-list>
<ion-item *ngFor="let item of items">
<ion-avatar item-left>
<img [src]="item.picture">
</ion-avatar>
<h2>{{item.name}}</h2>
<p>{{item.description}}</p>
</ion-item>
</ion-list>
</ion-content>
71
with no additional styling, the code above would look like this right out of the box:
It doesnt look amazing, but we already have a pretty complex layout set up with just a few lines of code,
throw a bit of custom styling in and wed have a pretty sleek interface. Were going to go through dierent
72
aspects of creating templates in Ionic 2 more thoroughly in just a moment, but I wanted to give you a sense
of what a full template for a page might look like, and also how easy it is to use the components provided
by Ionic.
Theres a lot more to know about template syntax in Ionic 2, and theres been a few signicant changes
from what you might have been used to in Ionic 1 - so were going to dive into templates in a lot of detail.
Well also be covering quite a few other topics after this, but this is the last of what I would consider to
be the core knowledge required to get started with Ionic 2 - once youve got the basics of classes and
templates down you can start jumping into building some stu. So, strap in - were going to start o with
The * Syntax
Perhaps one of the most confusing things about the new template syntax in Ionic 2 is this little guy: *.
or
<p *ngIf="someBoolean"><p>
and so on. In Angular 2 the * syntax is used as a shortcut for creating an embedded template, so if we
<template [ngIf]="someBoolean">
<p></p>
</template>
The reason for using templates is that Angular 2 treats templates as chunks of the DOM that can be
dynamically manipulated. So in the case of *ngIf we dont want to just literally render out:
<p *ngIf="someBoolean"></p>
73
to the DOM. We want to render out:
<p></p>
if someBoolean is true, and nothing if it is false. Similarly, if we were to use *ngFor we dont want to
we want to render out paragraph tags stamped with the information for each particular item:
<p>Bananas</p>
<p>More Bananas</p>
<p>Pancakes</p>
So we need to use <template> to allow for this functionality, but writing out templates manually is a lot
Now that weve got that out of the way, lets jump into some specics of how to use directives like *ngIf
and *ngFor.
Looping
Quite often you will want to loop over a bunch of items - when you have a list of articles and you want to
render all of the titles into a list for example. We can use the ngFor directive which is supplied by Angular
<ion-list>
<ion-item *ngFor="let article of articles" (click)="viewArticle(article)">
{{article.title}}
</ion-item>
</ion-list>
In this example, we create an <ion-list> and then for every article we have in our articles array we
add an <ion-item>. I mentioned before (in the basics section) the use of let to create a local variable
74
and we are using that here. This allows us to access whatever article the loop is currently up to, and we
are using that to grab the title of the current article and render it in the list, and also to pass it into the
By passing a reference to the current article to the viewArticle function we would then be able to do
something like trigger a new page with the specics of that article on it.
Conditionals
Sometimes you will want to display certain sections of the template only when certain conditions are met,
<div *ngIf="someBoolean">
ngIf will render the node it is attached to only if the expression evaluates to be true. So in this case, if
someBoolean is true, it will be added to the DOM, if it is false then it will not be added to the DOM.
ngIf is great for boolean - true or false - scenarios, but sometimes you will want to do multiple dierent
things based on a value. In that case you can use ngSwitch:
<div [ngSwitch]="paragraphNumber">
<p *ngSwitchWhen="1">Paragraph 1</p>
<p *ngSwitchWhen="2">Paragraph 2</p>
<p *ngSwitchWhen="3">Paragraph 3</p>
<p *ngSwitchDefault>Paragraph</p>
</div>
In this example we are checking the value of paragraphNumber with ngSwitch. Whichever
ngSwitchWhen statement the value matches will be the DOM element that will be rendered, and if none
match the ngSwitchDefault element will be used.
Another method to display or hide certain elements based on a condition is to use the hidden property.
For example:
75
<ion-avatar [hidden]="hideAvatar" item-left>
In this example, if the hideAvatar expression evaluates to be true the element will be hidden, but if it is
false then it will be rendered. Using this method, you would have a this.hideAvatar variable in your
class denition which you could toggle to hide and show these elements.
As well as conditionally displaying an entire element, you could also attach dierent classes to an element
This is a similar concept to the [hidden] method above, but instead of showing and hiding the element
based on a condition, it will add a class you have dened in your CSS based on a condition. This can
come in really handy, for example you might want to use it to style items that have already been read by
Everything weve covered above is general Angular 2 stu, theres nothing specic to Ionic there (except
for the <ion-list> and <ion-item> elements we used). You will be using this syntax a lot throughout
your templates, along with some Ionic specic components. Were going to go through some of the Ionic
specic stu now, starting with the basic layout of an Ionic 2 page template:
<ion-header>
<ion-navbar>
<ion-title>
Home
</ion-title>
</ion-navbar>
</ion-header>
<ion-content class="home">
76
<ion-card>
<ion-card-header>
Card Header
</ion-card-header>
<ion-card-content>
Hello World
</ion-card-content>
</ion-card>
</ion-content>
This is the automatically generated code you will get for your template if you use the blank layout. Theres
two important components here that are used in just about every template and they are <ion-navbar>
and <ion-content>.
The <ion-content> element is simply used to hold the main content of the page (which in this case is
just a card), and allows for scrolling. Notice that it has a class of home, if you look in the home.scss
le you should also see a home class dened. This doesnt do anything special, its just a convention to
allow you to target styles to that <ion-content> specically (remember, even though you add styles to
the home.scss le they will still apply to the whole application, the separate le is purely for organisation).
The more interesting of the two is <ion-navbar>. This is what adds the header bar to the top of the page,
where you can place the title of the page as well as buttons on the left or right. Its not purely an aesthetic
thing though, it also has a lot of inbuilt smarts for navigation. If you were to push a new page (a concept
we will cover in detail later), then a back button will automatically be added to the <ion-navbar> which
will automatically allow the user to navigate back to the previous page, rather than you having to handle
that manually.
All of the above covers o on the basic template syntax you will see in a lot of your Ionic 2 pages, the
rest is basically just dropping in and conguring various components that Ionic 2 oers (or if youre feeling
Now lets take a look at how to implement a few of Ionics components into our templates. Were not going
77
to be covering anywhere near all of them because there is so many, I just want to give you a taste. For a
full list of all the available components, take a look at the Ionic 2 documentation.
Lists
Lists are one of the most used components in mobile applications, and they provide an interesting chal-
lenge. That smooth scrolling you get when you swipe a list on native applications, with smooth acceleration
and deceleration, and it all just feels right - well thats really hard to replicate. Fortunately, you dont have
to worry about it because Ionic 2 does all the hard stu for you, and using a list is as simple as adding the
<ion-list>
<ion-item>Item 1</ion-item>
<ion-item>Item 2</ion-item>
<ion-item>Item 3</ion-item>
</ion-list>
or if you wanted to dynamically create your list for a bunch of items dened in your class:
<ion-list>
<ion-item *ngFor="let item of items" (click)="itemSelected(item)">
{{item.title}}
</ion-item>
</ion-list>
Slides
Slides are another common element used in mobile applications, slides look like this:
78
where you have multiple dierent images or pages to show and the user can cycle through them by swiping
left or right. Just like lists in Ionic 2, creating slides is really easy as well:
79
<ion-slides [options]="slideOptions">
<ion-slide>
<h2>Slide 1</h2>
</ion-slide>
<ion-slide>
<h2>Slide 2</h2>
</ion-slide>
<ion-slide>
<h2>Slide 3</h2>
</ion-slide>
</ion-slides>
A container of <ion-slides> is used, and then each individual slide is dened with <ion-slide>. It
is also possible to supply some options to dene the behaviour of the slides; whether it should loop and
whether it should have a pager for example (Ill give you a more complete example later).
Input
In Ionic 2, rather than using <input> tags for user input you use the Ionic equivalent which is
<ion-input>. Just like a normal <input> you can specify a type depending on what sort of data you
are capturing, but by using the Ionic versions we will be taking advantage of the custom inputs Ionic has
<ion-list>
<ion-item>
<ion-label fixed>Username</ion-label>
80
<ion-input type="text" value=""></ion-input>
</ion-item>
<ion-item>
<ion-label fixed>Password</ion-label>
<ion-input type="password"></ion-input>
</ion-item>
</ion-list>
As well as <ion-input> specically, you will also nd that Ionic provides other input elements like
Grid
The Grid is a very powerful component, and you can use it to create complex layouts. If youre familiar
with CSS frameworks like Bootstrap, then it is a very similar concept. When placing components into your
templates, in general things just display one after the other, but with the Grid you can come up with just
It works by positioning elements on the page based on rows and columns. Rows display underneath each
other, and columns (which are placed inside of rows) display side by side. For example:
<ion-row>
<ion-col></ion-col>
<ion-col></ion-col>
<ion-row>
<ion-row>
<ion-col></ion-col>
<ion-col></ion-col>
<ion-col></ion-col>
81
<ion-row>
This will create a layout with two rows, the top row will have two columns and the bottom row will have
three columns. By default everything will be evenly spaced, but you can also specify how wide columns
<ion-row>
<ion-col width-10></ion-col>
<ion-col width-20></ion-col>
<ion-col width-25></ion-col>
<ion-col width-25></ion-col>
<ion-col width-20></ion-col>
</ion-row>
This will create a single row with 5 dierent columns of varying widths (you will probably want to make sure
Icons
Icons are heavily used in most applications today, they are great because they allow you to communicate
what something does rather than relying on text. Its good for usability (most of the time) and looks way
better than using a button that says something like Add Item.
Ionic provides a ton of icons that you can use out of the box, like:
<ion-icon name="heart"></ion-icon>
You just have to specify the name of the icon you want to use. They even have variations of the same icons
so that they display dierently between iOS and Android to better match the style of the platform. For a
82
Theres a ton more default components, and a lot more to know about even the ones Ive mentioned here,
so make sure you have a look through the documentation to familiarise yourself with whats available.
83
Lesson 7: Styling & Theming
One thing I really like about Ionic is that the default components look great right out of the box. Everything is
really neat, sleek and clean but also maybe a little boring. I like simple, simple is great, but you probably
dont want your app to look like every other app out there. Take the example we used in the templates
lesson:
84
We have a simple and clean interface, but its probably not going to win any awards for its design. It uses
the default styling with absolutely no customisation whatsoever. If you take a look at some of the example
apps that Ive included in this course you will see that they mostly all have custom styling:
85
Some of the applications have simple styling, where just a few changes are made to achieve a much more
attractive look. Some of the applications have more complex styling, that completely change the look and
86
I certainly dont claim to be some design guru, but I think the themes Ive created for these applications are
visually pleasing and help give the apps some character. In this lesson Im going to show you the dierent
ways you can customise your Ionic 2 applications, and the theory behind theming in general.
When styling an Ionic 2 application, there is nothing inherently dierent or special about it its no dierent
than the way you would style a normal website. I often see questions like:
and the answer is generally yes. Could you do it on a normal webpage? If you can then you can do it in
Ionic as well.
A lot of people may be used to just editing CSS les to change styles, but there is some added complexity
with Ionic, which is primarily due to the fact that it uses SASS. Again, SASS isnt specic to Ionic or mobile
web app development it can also be used on any normal website but many people may not be as
If youre not already familiar, .scss is the le type for SASS or Syntactically Awesome Style Sheets. If
this is new to you, you should read more about what SASS is and what it does here. For those of you
short on time, what you put in your .scss les is exactly the same as what you would put in .css les, you
can just do a bunch of extra cool stu as well like dene variables that can be reused in multiple areas.
These .scss les are then compiled into normal .css les (its basically the same concept we use in Ionic 2,
where we code using all the fancy new ES6 features, but that is then transpiled into ES5 which is actually
When theming your application, youre mainly going to be editing your .html templates and .scss
stylesheets you will NEVER edit any .css les directly. The .css les are generated from the .scss les,
so if you make any changes to the .css le its just going to get overwritten.
If you take a look at the les generated when you create a new Ionic 2 project, you will see a bunch of
.scss les inside of your theme folder, so lets quickly run through what their purpose is.
87
app.core.scss is the main .scss le. It is used to declare any styles that will be used globally
throughout the application, and this le is also responsible for importing the stylesheets for other
components
app.md.scss is used to create Android specic styles (md stands for Material Design)
app.variables.scss is used to modify the apps shared variables. Here you can edit the default
values for things like $colors which sets up the default colours for the application, as well as
so it can be a great place to make quick changes. For a list of all the variables that you can overwrite,
On top of these .scss les inside of your theme folder, you will also have one for each component you
create (or at least you should). To refresh your memory, most components you create in Ionic 2 will look
like this:
my-component
my-component.ts
my-component.html
my-component.scss
We have the class denition in the .ts le, the template in the .html le and any styles for the component
in the .scss le. Although its not strictly required, you should always create the .scss le for any
components that have styling, rather than just dening the style in the app.core.scss le. If you use the
auto generate commands the Ionic CLI provides then the .scss le will be created automatically for you
anyway.
Why? Well you could just put all of your styles in the app.core.scss le and everything would work exactly
the same, but theres two major benets to splitting your styles up in the way I described above:
Organisation Splitting your code up in this way will keep the size of your les down, making it a lot easier
88
to maintain. Since all of the styles for a particular component can be found in that components .scss le,
Modularity one of the main reasons for the move to this component style architecture in Angular 2 and
Ionic 2 is modularity. Before, code would be very intertwined and hard to separate and reuse. Now, almost
all the code required for a particular feature is contained within its own folder, and it could easily be reused
Now that weve gone over the theory, lets look at how to actually start styling our Ionic 2 applications.
Im going to cover a few dierent ways you can alter the styles in your application. It may seem a little
unclear what way to do things, because in a lot of cases you could achieve the same thing multiple dierent
ways. In general, you should try to achieve what you want to do without creating custom styles (which
we will cover last here). Instead you should rst try using the pre-dened attributes or overriding SASS
variables. If it can not be done any other way, then look into creating your own custom styles. Dont worry
too much though, just try to keep things as simple as you can.
1. Attributes
One of the easiest ways to change the style of your application is to simply add an attribute to the element
youre using. As I mentioned above, SASS is used to dene some colours, and these are:
primary
secondary
danger
light
dark
favorite
89
which you can see dened in the app.variables.scss le:
$colors: (
primary: #387ef5,
secondary: #32db64,
danger: #f53d3d,
light: #f4f4f4,
dark: #222,
favorite: #69BB7B
);
As you can see above, Ionic provides some defaults for what these colours are, but you can also override
each of these to be any colours you want. So if you add the primary attribute to most elements it will turn
blue, or if you add the danger attribute it will be a red colour. But if you modied these then primary could
To give you an example, if I wanted to use the secondary colour on a button I could do this:
<button secondary></button>
or if I wanted to use the secondary colour on a the nav bar I could do this:
<ion-navbar secondary></ion-navbar>
Keep in mind that these attributes arent limited to just changing the colour of elements, some attributes
<ion-navbar secondary>
<ion-buttons end>
<button primary>I'm a primary coloured button in the end position of the
nav bar</button>
</ion-buttons>
</ion-navbar>
90
The example above uses the end attribute to decide where the buttons should appear. We could also
<ion-list no-lines></ion-list>
or even whether a list item should display an arrow to indicate that it can be tapped:
<ion-item detail-push></ion-item>
Theres a bunch more of these attributes, so make sure to poke around the documentation when you are
using Ionics in built components. The no-lines attribute is a real easy way to remove lines from a list, but if
you didnt know this attribute existed (which is quite possible) then youd likely end up creating your own
custom styles unneccesarily. This is why I recommend trying to do things with attributes rst if you can,
2. SASS Variables
The next method you can use to control the style of your application is to change the default SASS variables
(like editing the $colors we talked about above). These are really handy because it allows you to make
app wide style changes to specic things. I touched on SASS variables before, but basically in your .scss
$my-variable: red;
and then you could reference $my-variable anywhere in the .scss le. So for example if you wanted
to make the background colour on 20 dierent elements red, rather than doing:
background-color: red;
background-color: $my-variable;
The benet of this is that now if you wanted to change the background color from red to green, all you
have to do is edit that one variable not every single class you have created. This is why youll nd that
91
variables are named in the manner of primary and danger rather than specically blue and red. There
may come a time when you want to change your primary colour to be purple, but if you give variables
specic names like $my-blue-color and you change it to be purple its going to make your code pretty
confusing.
You probably wont be creating many of your own variables, but Ionic denes and uses a bunch of these
variables, and you can easily overwrite them to be something else. Lets take a look at a few:
$background-color
$link-color
$list-background-color
$list-border-color
$menu-width
$segment-button-ios-activated-transition
You can look at the documentation for more information on these and what they default to, but its pretty
clear by their name what they do. As you can see by the last example there, they even get very specic.
Editing these variables is really simple, just open app.variable.scss and insert your own denitions. Heres
$colors: (
primary: #387ef5,
secondary: #32db64,
danger: #f53d3d,
light: #f4f4f4,
dark: #222,
favorite: #69BB7B
);
$list-background-color: #fff;
$list-ios-activated-background-color: #3aff74;
$list-md-activated-background-color: #3aff74;
92
$checkbox-ios-background-color-on: #32db64;
$checkbox-ios-icon-border-color-on: #fff;
$checkbox-md-icon-background-color-on: #32db64;
$checkbox-md-icon-background-color-off: #fff;
$checkbox-md-icon-border-color-off: #cecece;
$checkbox-md-icon-border-color-on: #32db64;
In this example some of the default colours have been changed, and some overrides for specic styles on
Notice the use of md here, this stands for material design and is used for Android. Ionic 2 seamlessly
adapts to the conventions of the platform it is running on with little to no style changes required from you
The great thing about editing these default SASS variables is that you can, with one change, make all the
changes necessary everywhere in the app. Some variables use the values of other variables, so if you
wanted to just do this manually with CSS you would probably need to make a lot of edits to get the eect
you wanted.
3. Conguration
Another convenient way to change the styling of your application is through the Cong object that you
In general this is used for setting app wide defaults like the placement of buttons and tabs, the style of
icons to be used, transitions and so on. Usually its best to leave these unaltered unless you have a specic
reason for changing it, since Ionic will adapt to the conventions of the platform it is running on automatically
Sometimes you will want to force things to be a certain way though, and the Cong can be a good way
93
to do that. Heres an example of what it might look like from the documentation:
ionicBootstrap(MyApp, [], {
backButtonText: 'Go Back',
iconMode: 'ios',
modalEnter: 'modal-slide-in',
modalLeave: 'modal-slide-out',
tabbarPlacement: 'bottom',
pageTransition: 'ios'
});
and if you wanted to force iOS to use Material Design you could set the mode using the Cong options:
ionicBootstrap(MyApp, [], {
mode: 'md'
});
Again, Id stress against doing something like this unless you have a good reason. You might like and be
used to material design if youre an Android user, but your users on iOS (and vice versa) will not have the
same view as you. With that in mind, the Cong also allows you to congure things specically for specic
ionicBootstrap(MyApp, [], {
tabbarPlacement: 'bottom',
platforms: {
ios: {
tabbarPlacement: 'top',
}
}
});
For more information on the Cong object, take a look at the documentation.
94
4. Custom Styles
Before we talked about using attributes to change the colours of elements. Given that you can override
these attributes to whatever you like, its a good approach to set the primary, secondary, danger etc.
variables to match the colour palette of your design, and then use those to set the styles of elements,
But, sometimes there will come a time where you need to dene some plain old CSS classes to achieve
what you want. You can either dene these custom classes in app.core.scss if the class will be used
throughout the application, or in an individual components .scss le if it is only going to be used for one
component.
Before these custom styles will take eect, you will need to import them into the core .scss le, so if I
had a component called checklist that had a checklist.scss le, I would need to add the following
@import "pages/checklist/checklist";
Of course, you can also dene custom styles on the element directly by using the style tag, but make sure
As you can see, theres a few dierent ways you can change the styling of your Ionic 2 applications. In
general, its best to do as little as possible to achieve what you need. Try to achieve as much as you can
with attributes and SASS variables, because it will make your life easier.
As I mentioned before, Ionic seamlessly adapts to the UI conventions of both iOS and Android, so the more
hacky or brute force your solution for styling is, the greater chance you have of breaking this behaviour.
95
Lesson 8: Navigation
If you come from an Ionic 1 or Angular 1 background, then you would be used to handling navigation
through routing with URLs, states and so on. The focus in Ionic 2 though is using a navigation stack,
which involves pushing views onto the navigation stack and popping them o. Before we get into the
specics of how to implement this style of navigation in Ionic 2, lets try to get a conceptual understanding
Imagine your root page is a piece of paper that has a picture of a cat on it, and you put that piece of paper
on a table. It is the only piece of paper currently on the table and you are looking down on it from above.
Since it is the only piece of paper on the table right now, of course you can see the picture of the cat:
Now lets say you want to look at a dierent piece of paper (i.e. go to a dierent page), to do that you can
push it onto the stack of papers you have. Lets say this one is a picture of a dog, you take that piece of
paper and place it over the top of the picture of the cat:
96
The cat is still there, but we cant see it anymore because it is behind the dog. Lets take it even further
and say that now you want to push another piece of paper, a cow, it would now look like this:
Both the cat and the dog are still there, but the cow is on top so that is what we see. Now lets reverse
97
things a bit. Since all of the pieces of paper are stacked in the order they were added we can easily cycle
back through them by popping. If you want to go back to the picture of the dog you can pop the stack
of papers, removing the piece of paper that is currently on top (the cow). If you want to go back to the
picture of the cat you can pop the stack of papers once more to remove the piece of paper that is now on
Im sure you can see how this style of navigation is convenient for maintaining history and it makes a lot of
sense when navigating to child views, but it doesnt always make sense to push or pop. Sometimes you
will want to go to another page without the ability to go directly back to the page that triggered the change
(a login screen that leads to the main app for example, or even just dierent sections of an app available
through a menu).
In this case, we could change the root page which, given our pieces of paper on the table analogy, is like
disregarding the other stack of papers we have and just focusing on a new piece of paper on the table:
In the example above, Ive set the cow page as the root page, so rather than being on top of the other
98
At rst, it may be hard to understand whether you should set the root page to navigate to a dierent page
or push the view. In general, if the view you want to switch to is a child of the current view, or if you want
the ability to navigate back to the previous view from the new view, you should push. For example, if I was
viewing a list of artists and tapped on one I would want to push the details page for that artist. If I was
going through a multi-page form and clicked Next to go to page 2 of the form, I would want to push that
second page.
If the view you are switching to is not a child of the current view, or it is a dierent section of the application,
then you should instead change the root page. For example, if you have a login screen that leads to
the main application you should change the root page to be your main logged in view once the user
has successfully authenticated. If you have a side menu with the options Dashboard, Shop, About and
Contact you should set the root page to whichever of these the user selects.
Keep in mind that the root page is dierent to the root component, typically the root component (which
is dened in app.ts) will declare what the root page is the root page can be changed throughout the
Ok, weve gone through the theory so now were going to get into a more practical Ionic 2 example and
look at how to push, pop, set the root page and even how to pass data between pages.
An important part of all this is the NavController which is provided by Ionic. You will often see this imported
in Ionic 2 applications:
@Component({
templateUrl: 'build/pages/home/home.html',
})
export class MyPage {
99
constructor(public nav: NavController) {
}
}
We inject the NavController and a reference to it is created so that we can use it anywhere within the class.
As you might have been able to guess, the NavController helps us control navigation - so lets take a look
To push a page, which will take a page and put it on top of the navigation stack (which sets it as the current
this.nav.push(SecondPage);
This uses the reference to the NavController we created before, and all you need to supply to it is a
reference to the page that you want to navigate to, which you will need to make sure you also import at
and thats it, your app should switch to the new page whenever the push code is triggered. When you
push a page, a Back button will automatically be added to the nav bar (assuming you have one), so you
often dont need to worry about using pop to navigate back to the previous page since the Back button
There may be circumstances where you do want to manually pop a page o of the navigation stack though,
this.nav.pop();
Easy enough right? As I mentioned before there is still another way to change the page and that is by
setting the root page. If you take a look at your app.ts le you will notice the following line:
100
Declaring rootPage in the root component will set the root page, and thats because the template for the
<ion-nav [root]="rootPage"></ion-nav>
So were setting the root property on <ion-nav> to be whatever rootPage is dened as. To change
the root page at any point throughout the application, you can use our friend the NavController all you
this.nav.setRoot(SecondPage);
A common requirement of mobile applications is to be able to pass data between pages. One really
common example is when using the Master Detail pattern, which is basically where you have a list of
items and then you click on one to go to another page where it displays more details about that item. When
navigating to the detail page, were going to need to know which item we are displaying data for, which
will involve passing in data from the previous page. In Ionic 2 this can be done using NavParams. First,
you must pass through the data you want within the push call (this can also be done when using setRoot):
this.nav.push(SecondPage, {
thing1: data1,
thing2: data2
});
This is exactly the same as what we were doing before, except now there is an extra parameter which is
an object that contains the data we want to send through to SecondPage. Then on the receiving page we
@Component({
101
templateUrl: 'build/pages/second/second.html'
})
export class SecondPage {
constructor(nav: NavController, navParams: NavParams){
}
}
Then you can grab the data that was passed through by doing the following:
this.navParams.get('thing1');
Navigation Components
Some of the components that Ionic provides also eect navigation in some way. These arent really core
navigation concepts, but they will have an impact on navigation in your application. So lets cover what
Modals
Youre probably familiar with the concept of a modal already. In web development, a modal is basically
some box that pops up on the screen and covers the content behind it. Usually modals have the lightbox
style, with a blacked out background and the focus on the content area.
A Modal in Ionic is similar, in that it pops up on top of your content, but it doesnt actually look any dierent
to a normal page. Generally you would want to use a modal, rather than pushing a page, when you want
to give the user the ability to launch and then dismiss (close) a view, rather than navigating back to the
previous page.
One cool thing about Modals are that they give you the ability to pass some data back to the page that
launched it when the Modal is dismissed. For example, you can create a Modal like this (remember to
102
import Modal as well!):
this.nav.present(myModal);
Notice that the modal is presented using the NavController, rather than being pushed onto the naviga-
tion stack. Now if we wanted to allow some data to be passed back from that modal, we could add an
myModal.onDismiss(data => {
console.log(data);
});
this.nav.present(myModal);
Now when the modal is dismissed it will pass back a data object that we can do something with. To
dismiss a modal, all you have to do is call the following code inside of the Modal:
this.view.dismiss();
where this.view is a reference to the ViewController which is kind of like the NavController and also
needs to be imported and injected into your constructor. If we want to pass data back to that onDismiss
let data = {
thing1: "value1",
thing2: "value2"
};
this.view.dismiss(data);
103
Now the data object will be passed back to the onDismiss handler from the page that launched the
modal.
Tabs
Tabs are a very popular component that have a big impact on how navigation works in your application.
Using tabs is really simple, basically in your template you will create something like this:
<ion-tabs>
<ion-tab [root]="tab1Root" tabTitle="Tab 1" tabIcon="navigate"></ion-tab>
<ion-tab [root]="tab2Root" tabTitle="Tab 2" tabIcon="person"></ion-tab>
<ion-tab [root]="tab3Root" tabTitle="Tab 3" tabIcon="bookmarks"></ion-tab>
</ion-tabs>
and then in your class denition you just dene the pages to be used as the tabs like this:
constructor(){
Notice that each tab has its own root page. You can think of switching tabs as switching between dierent
root pages, and then you can push and pop pages in each tab. With a tab layout, you can switch between
dierent tabs, but each tab will still maintain its own history.
Sidemenu
A side menu doesnt really do anything out of the ordinary in terms of navigation, the side menu is really
just a UI element but its a convenient, and common, place to add buttons that allow the user to navigate to
104
another page (the actual switching of pages is just done manually with setRoot or push though). Adding a
side menu to your application is super easy, you just need to modify the template of your root component
<ion-menu [content]="content">
<ion-content>
<ion-list>
<button ion-item (click)="openPage(homePage)">
Home
</button>
</ion-content>
</ion-menu>
We use <ion-menu> to create a menu, and we also have to tell it what to attach itself to. This is why we set
the [content] property to content which is a reference to the local variable we created on the <ion-nav>
by adding #content. So this is basically saying that the <ion-nav> is our main content area, and we
There is a bit to learn about navigation in Ionic 2, but once youve got a handle on the basics discussed in
this lesson you should be able to get by in most circumstances without too much trouble.
105
Lesson 9: User Input
Not all mobile applications require user input, but many do. At some point, youre going to want to collect
some data from your users. That might be some text for a status update, their name and shipping address,
a search term, a title for their todo list item or anything else.
Whatever the data is, the user is going to be entering it into one of the templates in your application. To
give you an example, in Ionic 2 we could create a form in our template with the following code:
<ion-list>
<ion-item>
<ion-label>Username</ion-label>
<ion-input type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label>Password</ion-label>
<ion-input type="password"></ion-input>
</ion-item>
</ion-list>
which would produce a simple login form that looks like this:
106
This would allow the user to enter some information into these input elds. However, we need to know
how to get the data that is being entered into our .html template and make use of it in our .ts class. In
this lesson we are going to discuss a couple of dierent ways you can do that.
This concept will be very familiar to you if youve previously used Ionic 1, if not then dont worry because
its pretty straightforward concept. Two way data binding essentially links a value of an input eld in the
template, to the value of a variable in the class. Take the following example:
Template:
107
Class:
constructor(){
this.myValue = 'something';
}
If we changed the value of the input in the template, then the this.myValue variable in the class will
be updated to reect that. If we change the value of this.myValue in the class, then the input in the
template will be updated to reect that. By using ngModel the two values are tied together, if one changes,
When the user clicks the button we want to log the value they entered in the input to the console. Since
the button calls the logValue function when it is clicked, we could add that to our class:
logValue(){
console.log(this.myValue)
}
This function will grab whatever the current value of the input is and log it out to the screen. Rather than
logging it out to the screen, you could also do something useful with it. This can be a convenient way to
handle input, because we dont need to worry about passing the values through a function, we can just
It becomes a bit cumbersome when we have a lot of inputs though, so its not always the perfect solution.
When dealing with more complex forms, we also have another option, which we will discuss now.
108
Form Builder
Form Builder is a service provided by Angular 2, which makes handling forms a lot easier. Theres quite a
lot Form Builder can do but at its simplest it allows you to manage multiple input elds at once and also
provides an easy way to validate user input (i.e. to check if they actually did enter a valid email address).
To use Form Builder it needs to be imported and injected into your constructor, e.g:
@Component({
templateUrl: 'build/pages/my-details/my-details.html',
})
export class MyDetailsPage {
Notice that Validators are also being imported here, which are what allow you to validate user input with
Form Builder. Lets cover a really quick example of how you can use Form Builder to build a form. The
most important dierence with this method is that your inputs will have to be surrounded by a <form> tag
<ion-item>
<ion-label stacked>Field 1</ion-label>
109
<ion-input ngControl="field1" type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Field 2</ion-label>
<ion-input ngControl="field2" type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Field 3</ion-label>
<ion-input ngControl="field3" type="text"></ion-input>
</ion-item>
</form>
You will also notice in the example above that we have dened the ngControl attribute on all of our inputs,
this is how we will identify them with Form Builder in just a moment. Of course we need a way to submit
the form, so weve added a submit button and have also added a (submit) listener on the form which
calls the saveForm function. We will dene that function in a moment, but for any of this to work we rst
need to initialise the form in the constructor function for the page. This will look something like this:
this.myForm = formBuilder.group({
field1: [''],
field2: [''],
field3: ['']
});
We simply supply all of the elds that are in the form (using the ngControl names we gave them) and
provide the elds with an initial value (which we have left blank in this case). You can also supply a second
110
value to each eld to dene a Validator if you like, e.g:
this.myForm = formBuilder.group({
field1: ['', Validators.required],
field2: ['', Validators.required],
field3: ['']
});
Also note that the variable this.myForm has to be the same as the value we supply for [ngFormModel]
saveForm(event){
event.preventDefault();
console.log(this.myForm.value);
}
We pass through the submit event and then call preventDefault so that the default action for submitting
a form doesnt occur, we just want to handle it ourselves with this function. To grab the details that the
user entered into the form we can simply use this.myForm.value which will contain all the values that
Setting up forms using Form Builder is a little more complex, but its much more powerful and worth the
eort for more complex forms. For more simple requirements, using [(ngModel)] is ne in most cases.
111
Lesson 10: Saving Data
Lets say youve create an Ionic 2 application where users can create shopping lists. A user downloads
your application, spends 5 minutes adding their list and then closes the application and all their data is
gone.
Quite often when creating mobile applications you will want to store data that the user can retrieve later. A
lot of the time this is done by storing the data somewhere that can be accessed through a remote server
(think Facebook, Twitter etc.) which would require an Internet connection and fetching the data remotely
(which we will discuss in the next lesson). In many cases though, we may also want to store some data
The application is completely self contained and there is no interaction with other users, so all data
We need to store data locally for some specic functionality like remembering logged in users
We want to sync online and oine data so that the user can continue using the application even
HTML5 applications run in the browser, so we dont have access to the storage options that native appli-
cations do. We do still have access to the usual browser storage options that websites have access to
though like Local Storage, Web SQL (deprecated) and IndexedDB. These options might not always be
ideal (for reasons I will cover shortly), but we can also access native data storage by using Cordova which
There are plenty of dierent options out there for storing data locally, but we are going to focus on the main
ones when it comes to Ionic 2 applications, and they are Local Storage and SQLite.
112
Local Storage
This is the most basic storage option available, which allows you to store up to 5MB worth of data in the
users browser. Remember, Ionic 2 applications technically run inside of an embedded browser.
Local storage gets a bit of a bad wrap, and is generally considered to be unreliable. I think the browsers
local storage can be a viable option and it is reasonably stable and reliable, but, it is possible for the data
to be wiped, which means for a lot of applications its not going to be a great option. Even if it worked
99% of the time, thats still not good enough for storing most types of data.
In general, you should only use it where data loss would not be an issue, and it shouldnt ever be used
to store sensitive data since it can be easily accessed. One example of where local storage might be a
suitable option is if you wanted to store something like a temporary session token. This would allow you
to tell if a user was already logged in or not, but if the data is lost its not really a big deal because the user
will just need to enter in their username and password again to reauthenticate.
If you are just using local storage to cache data from a server it would also not be a big issue if the data is
Local Storage is a simple key-value system, and can be accessed through the globally available
localStorage object:
localStorage.setItem('someSetting', 'off');
This is the native (as in native to web browsers, not iOS or Android native) way to set and retrieve local
storage data, but Ionic 2 provides its own service for interacting with local storage, which adds promise
support.
If you want to use local storage in Ionic 2 you would do something like this:
113
@Component({
template: `<ion-content></ion-content>`
})
export class MyClass{
local: Storage;
constructor(){
this.local = new Storage(LocalStorage);
this.local.set('didTutorial', true);
}
}
This is the example provided in the documentation, as you can see both Storage and LocalStorage need
to be imported, and then a new instance of Storage is created with LocalStorage supplied as the storage
engine. Once that storage object has been created you can interact with it using the get and set methods.
Keep in mind that retrieving items from storage will return a promise, so the correct syntax to use would
this.local.get('didTutorial').then((result) => {
console.log(result);
});
SQLite
SQLite is basically an embedded SQL database that can run on a mobile device. Unlike a normal SQL
database it does not need to run on a server, and does not require any conguration. SQLite can be utilised
by both iOS and Android applications (as well as others), but the SQLite database can only be accessed
114
We can however use a Cordova to easily gain access to this functionality. Simply run the following com-
It provides the SQL syntax, so it is a very powerful tool for managing data
Although there are some dierences in supported commands between SQL and SQLite, it is almost exactly
the same. Heres an example of how you might execute some simple queries in SQLite:
db.transaction(function(tx) {
tx.executeSql('DROP TABLE IF EXISTS test_table');
tx.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary
key, data text, data_num integer)');
}, function(e) {
console.log("ERROR: " + e.message);
});
});
The example above looks pretty freaky, but if youre familiar with SQL then at least some of it should look
familiar. This is the standard way to use SQLite with Cordova, but Ionic also provides its own service for
115
using SQLite.
In order to use the SQLite service provided by Ionic 2 you will rst need to make sure you have the SQLite
plugin installed:
and then youll have to set it up in your project, much like we just did with the Local Storage service:
@Component({
template: `<ion-content></ion-content>`
})
export class MyClass{
storage: Storage;
constructor(){
this.storage = new Storage(SqlStorage, {name: 'dbname'});
this.storage.set('name', 'Max');
this.storage.get('name').then((name) => {
console.log(name);
});
116
}
}
Its basically the same idea here except we supply SqlStorage as the storage engine instead. Getting and
setting data isnt really any dierent, but obviously we can now run more complex queries for inserting and
The cool thing about this service is that it falls back to using browser based storage if SQLite is not available.
You should always remember to install the SQLite plugin so SqlStorage makes use of it, but if youre
testing your application through a browser where SQLite isnt available then everything should still work
pretty smoothly.
As I mentioned, there are dierent storage options available (and depending which package you bought,
we may discuss this further later) but in general, LocalStorage and SqlStorage will serve you well in most
cases.
117
Lesson 11: Fetching Data, Observables and Promises
Although some mobile applications are completely self contained (like calculators, soundboards, todo lists,
photo apps, ashlight apps), many applications rely on pulling in data from an external source to function.
Facebook has to pull in data for news feeds, Instagram the latest photos, weather apps the latest weather
In this section we are going to cover how you can pull external data into your Ionic applications. Before
we get into the specics of how to retrieve some data from a server somewhere, I want to cover a little
more theory on a few specic things that we are going to be making use of.
The map and lter functions are very powerful and allow you to do a lot with arrays. These are not fancy
new ES6 or Angular 2 features either, theyve been a part of JavaScript for a while now.
To put it simply, map takes every value in an array, runs the value through some function which may change
the value, and then places it into a new array. It maps every value in an array into a new array. To give you
an example of where this might be useful, you might have an array of lenames like this:
You could then map those values into a new array that contains the full path to the les:
['http://www.example.com/file1.jpg', 'http://www.example.com/file2.png',
'http://www.example.com/file2.png']
118
});
So we supply the map with a function that returns the modied value. The function that we provide will
A lter is very similar to a map, but instead of mapping each value to a new array, it only adds values that
meet a certain criteria to the new array. Lets use the same example as before, but this time we want to
return an array that only contains .png les. To do that, we would use lter like this:
Suppose we still want to have the full path as well though. Fortunately, we can quite easily chain lter and
So now we are rst ltering out the results we dont want, and then mapping them to a new array with the
full le path. The result will be an array containing the full paths of only the two .png les. Im going to
leave it there for now, but when we get into an example of how to fetch data soon it will become very clear
119
Observables and Promises
If youve used Ionic 1 or have a reasonably strong background in Javascript then you would probably be
familiar with Promises, but far fewer people are familiar with Observables. Observables are one of the
core new features included Angular 2 (provided by RxJS) so it is important to understand what they are
and how they are dierent to Promises (they do look and behave very similar).
Before we get into Observables, lets cover what a Promise is at a very high level. Promises come into play
when we are dealing with asynchronous code, which means that the code is not executed one line after
another. In the case of making a HTTP request for data, we need to wait for that data to be returned, and
since it might take 1-10 seconds for it to be returned we dont want to pause our entire application whilst
we wait. We want our application to keep running and accepting user input, and when the data from the
A Promise handles this situation, and if youre familiar with callbacks its basically the same idea, just a
little nicer. Lets say we have a method called getFromSlowServer() that returns a promise, we might
getFromSlowServer.then((data) => {
console.log(data);
});
We call the then method which a Promise provides, which basically says Once you have the data from
the server, do this with it. In this case we are passing the data returned into a function where we log it
out to the console. So our application will go about doing whatever else it has to do and when the data is
available it will execute the code above. You could think of it like being at work and writing some report,
you need some additional information so you ask your assistant to go nd it for you, but you dont just sit
there and wait for the assistant to get back - you keep writing your report and when the assistant returns
We understand what a Promise is now, so whats an Observable and what does it do that Promises dont?
An Observable serves the exact same purpose as a Promise, but it does some extra stu too. The main dif-
120
ference between a Promise and an Observable is that a Promise returns a single result, but an Observable
is a stream than can emit more than one value over time. It might be easier to think of Observables as
streams, because they are, they are just called Observables because the stream is observable (as in, we
An Observable looks a lot like a Promise, but instead of using the then method we use the subscribe
method. Since a Promise only returns a single value, it makes sense to have that value returned and then
do something. As I mentioned, an Observable is a stream that can emit multiple values, so it makes sense
to subscribe to it (like your favourite YouTube channel), and run some code every time a value is emitted.
someObservable.subscribe((result) => {
console.log(result);
});
It is obvious that our program would need to wait for data to be returned when making a HTTP request,
and thus Promises and Observables would be useful. Its not the only instance of where you will need to
program asynchronously though. There are some less obvious situtations like fetching locally stored data,
or even getting a photo from the users camera, where you would also need to wait for the operation to
If you want to go more indepth into everything weve discussed above, I highly recommend this interactive
tutorial. It introduces RxJS which includes Observables, but also builds up a solid foundation of how to
use map, lter and other functions. If youd also like to dive into some more specics about how an
Ok, you should be armed with all the theory you need now - lets get into an example. Were going to
use the Reddit API to demonstrate here because it is publicly accessible and very easy to use. If youve
purchased one of the packages for this book that includes the Giist application then we will be exploring
121
You can create a JSON feed of posts from subreddits simply by visiting a URL in the following format:
https://www.reddit.com/r/gifs/top/.json?limit=10&sort=hot
If you click on that link, you will see a JSON feed containing 10 submissions from the gifs subreddit, sorted
by the hot lter. If youre not familiar with JSON, I would recommend reading up on it here but essentially
it stands for JavaScript Object Notation and is a great way to transmit data because it is very readable to
humans, and is also easily parsed by computers. If youve ever created a JavaScript object like this:
var myObject = {
name: 'bob',
age: '43',
hair: 'purple'
};
then you should be able to read a JSON feed pretty easily once you tidy it up a little. But how do we get
The answer is to use the Http service which is provided by Angular 2, and allows you to make HTTP
requests. If youre not familiar with what a HTTP request is, basically every time your browser tries to load
anything (a document, image, a le etc.) it sends a HTTP request to do that. So we can make a HTTP
request to a page that spits out some JSON data, and pull that into our application.
First we need to set up the Http service, so lets take a look at a test page that has that service imported
@Component({
templateUrl: 'build/pages/page1/page1.html'
})
export class Page1 {
122
constructor(public http: Http) {
}
}
Since we have injected the Http service into our constructor and made it available through this.http
by using public, we can now make use of it anywhere in this class. Also note that we are importing the
map operator from the RxJS library. As I mentioned before map is a function that is provided by default on
arrays - so why would we need to import it from some weird library? Its because the Http service doesnt
return an array, it returns an Observable. The RxJS library makes the map function available for us on
Now lets take a look at how we might make a request to a reddit URL:
@Component({
templateUrl: 'build/pages/page1/page1.html'
})
export class Page1 {
this.http.get('https://www.reddit.com/r/gifs/new/.json?limit=10').map(res
=> res.json()).subscribe(data => {
console.log(data);
});
}
123
}
The rst part of the call returns us an Observable. Then we make use of the map function, what were
doing here is taking the plain text JSON response (which is just a string) and converting it into a JavaScript
object by calling the json()function. This makes it much more friendly for us to play with.
IMPORTANT: Remember, Http requests are asynchronous. This means that your code will continue exe-
cuting whilst the data is being fetched, which would take anywhere from a few milliseconds, to 10 seconds,
to never. So its important that your application is designed to deal with this. To give you an example, if
this.posts = null;
this.http.get('https://www.reddit.com/r/gifs/top/.json?limit=2&sort=hot')
.map(res => res.json()).subscribe(data => {
this.posts = data.data.children;
});
console.log(this.posts);
You would see null output to the console. But if you were to run:
this.posts = null;
this.http.get('https://www.reddit.com/r/gifs/top/.json?limit=2&sort=hot')
.map(res => res.json()).subscribe(data => {
this.posts = data.data.children;
console.log(this.posts);
});
You would see the posts output to the console, because everything inside of the subscribe function will
124
Getting back to our example, after we map the response we chain a subscribe call which allows us to do
something with the data that is emitted from the stream (Observable). As I mentioned above an Observable
is useful because we can listen for multiple values over time but why use it here? The Http call is only ever
going to return one result, why doesnt it just use a Promise instead of an Observable and save everyone
the confusion?
The reason for a bit of favouritism of Observables over Promises is that an Observable can do everything
a Promise can, its technically better behind the scenes, and it can do extra fancy things that Promises
cant. We could set up an interval for example so that the Http call res every 5 or 10 seconds, we can
easily set up debouncing which ensures that a request is not red o too frequently (and subsequently
making a ton of requests to a server), Observables can cancel old in ight requests if a new request is
made before the result of the old request is returned and a whole bunch of other things.
this.subredditControl.valueChanges.debounceTime(1000)
.distinctUntilChanged().subscribe(subreddit => {
this.subreddit = subreddit;
if(this.subreddit != ''){
this.changeSubreddit();
}
});
In this case subredditControl is an Observable. The value of this can be controlled by the user using
an input in the application. Its set up though so that the code inside the subscribe call will only run if
there has been no change for more than 1 second (by using debounceTime) and it will also only run when
a distinct value is supplied. I think this example shows well how a whole bunch of weird and useful stu
If youre looking at the example above and thinking Wow Ionic 2 is way too hard and confusing, take
me back to Ionic 1! then dont. This is a pretty advanced example using Observables to demonstrate a
125
point, Ionic 2 input handling and two way data binding is just as easy as Ionic 1.
Observables are a huge topic, so there is a ton to learn. Dont feel intimidated if you dont really have much
of an idea of whats going on though. Having a better understanding of Observables will help you when
creating Ionic applications, but they are a reasonably small part of Ionic (well, they are a big part but you
wont really have to deal with them much) and you can get by just ne by having just a basic understanding.
We know how to pull in data using a JSON feed like the one provided by reddit, but what if you want to
pull in your own data? How can you go about setting up your own JSON feed?
Going into the detail of how to set up your own API is a bit beyond what I wanted to achieve with this
lesson, but I would like to give you a high level overview of how its done. Basically:
2. Fetch the data using whatever server side language you prefer
Ill quickly walk you through the steps of how you might implement a simple API with PHP, but you could
use whatever language you want - as long as you can output some JSON to the browser.
2. Retrieve the data. In this case Im doing that by querying a MySQL database but the data can come
from anywhere:
while($row = $dbresult->fetch_array(MYSQLI_ASSOC)){
$data[] = array(
'id' => $row['id'],
126
'name' => $row['name']
);
}
if($dbresult){
$result = "{'success':true, 'data':" . json_encode($data) . "}";
}
else {
$result = "{'success':false}";
}
echo($result);
tion
As I mentioned you can use whatever language and whatever data storage mechanism you like to do this.
Just grab whatever data you need, get it in JSON format, and then output it to the browser.
This should give you a pretty reasonable overview of how to fetch remote data using Ionic 2 and the Http
service. The syntax and concepts might be a little tricky to get your head around at rst, but once youve
got the basics working theres not really much more you need to know. Your data might get more complex
and you might want to perform some fancier operations on it or display it in a dierent way, but the basic
127
Lesson 12: Native Functionality
The problem with web based mobile applications is that whilst we can run them on iOS and Android
through a web browser, users cant install them natively on their device and the app can not access native
APIs like Contacts, Bluetooth and so on. This is why we use Cordova in conjunction with Ionic, Cordova
allows us to wrap our applications in a native wrapper which allows for submission to app stores as well
as communication with native APIs through plugins. When using Cordova, a HTML5 mobile application
As I just mentioned, to access native functionality we need to use plugins. Cordova provides a bunch of
Device
Network Information
Camera
Geolocation
File
In App Browser
Media
Splash Screen
But there are hundreds of open sourced plugins developed by the community to do just about everything,
Local Notications
Facebook Connect
SQLite
Social Sharing
Basically what a plugin does is create an interface where Javascript code can trigger native calls. So if
you ever run into a situation where you need a Cordova plugin that doesnt exist yet (which is pretty rare),
you can even write it yourself (but it does involve writing native code).
128
IMPORTANT: Most plugins only work when running on a real device, so if you are trying to test a Cordova
Theres two ways to implement native functionality in Ionic 2. You can just use any Cordova plugin directly
and then accessing the functionality that the plugin provides, which is usually available on a global object
like this:
window.plugins.somePlugin.someMethod();
Nothing needs to be imported, required, called from a specic section of code or anything else - once
you have installed the plugin through the command line you will be able to access it from anywhere. Not
all plugins will be accessible exactly like this, but its how most plugins work. This is not specic to Ionic
2, you can use Cordova plugins in this manner in any Cordova project (the only dierence being that you
would use cordova plugin add instead of ionic plugin add). When using the normal Cordova
syntax, using a plugin in Ionic 2 is no dierent than using it in Ionic 1, Sencha Touch, jQuery Mobile or a
Keep in mind that if you use Cordova plugins in this way, your application may fail to compile due to
TypeScript warnings. This is because TypeScript does not know what it is, and you may need to install
typings for it. To brute force your way past this, you can simply add:
above the decorator in the class that you are using the plugin in.
Alternatively, you can use Ionic Native to make use of Cordova plugins, which is specic to Ionic 2. If
youre familiar with ngCordova from Ionic 1 then this is basically the same thing, just for Ionic 2. If youre
129
not familiar with ngCordova, Ionic Native basically just makes Cordova plugins play a little bit more nicely
Ionic Native is installed by default in all Ionic 2 applications, so all you need to do is install the plugin you
Next you will need to import the plugin from Ionic Native into the class you want to use it in:
Geolocation.getCurrentPosition().then((resp) => {
console.log("Latitude: ", resp.coords.latitude);
console.log("Longitude: ", resp.coords.longitude);
});
Notice that in the code above a promise is returned and we set up a handler using .then(), if we were
just using the standard Cordova syntax this wouldnt be possible - we would instead have to use callback
Its also important to note that not all Cordova plugins are available in Ionic Native. For a list of all of the
available plugins, and how to use them, you should check the Ionic Native documentation. If a plugin you
want to use is not available in Ionic Native, then you can just go back to using the standard Cordova syntax
Although it is not required, you should use Ionic Native wherever possible. Itll make your code cleaner,
and it makes much more sense in the Angular 2 ecosystem (and typings will be handled automatically this
way, so TypeScript wont complain). Using plain old Cordova is not a crime though, so dont feel too bad
about it.
130
Chapter 3
Quick Lists
131
Lesson 1: Introduction
Quick Lists is the de facto step-by-step tutorial application for this course - no matter which of the packages
you purchased, you will have access to this lesson. The reason I chose Quick Lists to ll this role is because
it covers a broad range of the core concepts in Ionic 2, and the skills you learn throughout building this
A lot of people (myself included) create todo application tutorials when explaining some new technology
or framework, the reason for this is usually because a todo application covers most of the basic things you
User Interface
These are all obviously important concepts that need to be covered, but I really wanted to avoid building
another todo application for this book - I wanted to do something that was just a little bit more complex
and interesting. The result is pretty similar and covers the same bases as a todo application would, but I
The idea for Quick Lists came from a personal need of mine. At the time of writing this I am currently working
remotely and traveling around Australia in a caravan. Its certainly a great experience, but essentially
lugging your entire house around the country (and its a big country) every week or so comes with some
complications.
One particularly complicated thing is packing up and hitching the caravan to the car, as well as unhitching
the caravan and setting it up. I wont bore you with the details, but theres at least 20 or so dierent things
that need to be done and checked each time. Some are inconsequential, but some are really important
132
like making sure the chains are attached to the car, that the gas is o and that the brakes and indicators
are working.
So I decided to create an app where you could create pre-ight checklists. The checklists would contain
a bunch of items that you could check o as being done, and when you needed to restart the checklist for
the next time you could just hit a refresh button to reset everything. A repeatable todo list application in a
sense.
As I mentioned, this application covers similar concepts to what a traditional todo application would.
Complex Lists
Data Models
Observables
Simple Navigation
Theming
The rst time the user uses the application an introduction tutorial will be shown
The user can add any number of individual items to any checklist
All data will be remembered upon returning to the application (including the completion state of
checklist items)
133
134
135
Lesson Structure
1. Getting Ready
2. Basic Layout
Ready?
Now that you know what youre in for, lets get to building it!
136
Lesson 2: Getting Ready
In this lesson we are going to prepare our application for the journey ahead. We are going to of course
generate the application, and we are also going to set up all of the components and Cordova plugins we
will need. At the end of this rst lesson we should have a nice skeleton application set up with everything
A good rule of thumb before starting any new application is to make sure you have the latest version of
Ionic and Cordova, so if you havent done it recently then make sure to run:
or
We will be using the blank starter template for this application which, as the name implies, is basically an
empty Ionic project. It comes with one page built in called home which we will repurpose as our main
> Make the new project your current working directory by running the following command:
cd quicklists
Your project should now be generated - now you can open up the project folder in your favourite editor.
You can take a look at how your application looks by running the following command:
137
ionic serve
138
###Create the Required Components
This application will have a total of three page components. We will have our HomePage that will display
a list of all the checklists, an IntroPage that will display the introduction tutorial, and a ChecklistPage
which will display the individual items for a specic checklist. Weve already got the Home page, so lets
> Run the following command to generate the Checklist detail page:
Also remember that any time we generate a new component, we need to import its .scss le into our
app.core.scss le. The Ionic CLI automatically generates pages for us, but it doesnt automatically import
@import "../pages/home/home";
@import "../pages/intro/intro";
@import "../pages/checklist/checklist";
We are going to be creating a couple of services in this application to help us out. We will be creating
a data model for our checklists which will allow us to more easily create and update them, and we will
also be creating a Data service to handle saving the checklist data into storage and retrieving them from
storage.
139
> Run the following command to generate a Checklist data model provider:
Before you can build for certain platforms, you need to add them to your project. This is something we
will be doing way later on in this course, but you may as well just set them up now.
> Run the following command to add the iOS platform to your application
> Run the following command to add the Android platform to your application
This application will use a few dierent Cordova plugins. Remember, Cordova plugins can only be used
when running on a real device. Ill explain each plugin as we run through the commands for adding them.
This plugin gives you access to native storage with an SQLite database. We are adding it to this application
because the Ionic local storage service can make use of this plugin to provide more stable data storage.
> Run the following command to add the Status Bar plugin:
140
We will be adding this plugin to all projects to give us control over the status bar in our application (the bar
at the top of the devices screen that contains the time, battery information and so on).
> Run the following command to add the Splash Screen plugin:
This plugin allows us to control the splash screen (the fullscreen graphic that briey displays when you
open an app)
This plugin is required for all applications, and helps to dene what resources should be allowed to be
loaded in your application. Without it, resources you try to load will fail.
As well as adding the plugin, you also need to dene a Content Security Policy in your index.html
le. We will be adding a very permissive policy which will essentially allow us to load any resources.
Depending on your application, you may look into providing a more strict policy, but an open policy is
This is another plugin that we will add to every application, but you may decide you want to leave it out.
By adding this plugin, when you build for Android Crosswalk will be used. Android has a lot of issues,
especially with older devices, because there is so many dierent software versions out there and dierent
versions have dierent browsers (remember, since we are building HTML5 applications it is actually a
browser engine powering and running our application). What Crosswalk does is bundle a modern browser
141
into your application so no matter what device you are running on your app will be powered by the same
The only real downside to this is that it increases the size of your application by a signicant amount. In
general, I think its worth it and Id recommend you include it but you may leave it out if you like. For more
Set up Images
When building this application we are going to be making use of a few images. Ive included these in your
download pack but you will need to set them up in the application you generate.
> Copy the images folder in the download pack for this application from www/images to your own
www folder
Summary
Thats it! Were all set up and ready to go, now we can start working on the interesting stu.
142
Lesson 3: Basic Layout
Were going to start things o pretty slow and easy in this lesson, and just focus on creating the basic
layout for the application. We will need to create templates for the Home page, which will display all of
the checklists that have been created, and the Checklist page, which will display all of the items for one
specic checklist. If youve been paying attention then youll know theres also one more page, but we will
As Ive mentioned before, Ive tried to make this course as modular as possible so that you can build
the applications that interest you rst, and arent forced to follow a particular order. Since this is the rst
application though, and because it is the only application contained in the basic package Ill be paying
special attention to making sure all the little things are explained thoroughly.
Before we jump into the code, lets get a clear picture in our mind of what we are actually building. Heres
143
Its got a bit of fancy styling which we will cover later, but essentially its a pretty simple list of items with a
button in the top right to add a new checklist. Its not entirely simple though, youll notice one of the list
items looks a little dierent and is displaying an Edit and Delete button. This is because we will be using
144
the sliding list component Ionic provides, which allows us to specify some content that will be displayed
So lets get into building it. First were going to look at the entire template to see everything in context,
then were going to break it down into smaller chunks that we will discuss in detail.
<ion-header>
<ion-navbar secondary>
<ion-title>
<img src = "images/logo.png" />
</ion-title>
<ion-buttons end>
<button (click)="addChecklist()"><ion-icon
name="add-circle"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list no-lines>
<ion-item-sliding>
145
<ion-item-options>
<button light (click)="renameChecklist(checklist)"><ion-icon
name="clipboard"></ion-icon> Edit</button>
<button danger (click)="removeChecklist(checklist)"><ion-icon
name="trash"></ion-icon> Delete</button>
</ion-item-options>
</ion-item-sliding>
</ion-list>
</ion-content>
<ion-header>
<ion-navbar secondary>
<ion-title>
<img src = "images/logo.png" />
</ion-title>
<ion-buttons end>
<button (click)="addChecklist()"><ion-icon
name="add-circle"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
The <ion-navbar> allows us to add a header bar to the top of our application that can hold buttons,
titles and even integrates directly with Ionics navigation system to display a back button when necessary.
We add the secondary attribute to the navbar to style it with our secondary colour, which is dened in
app.variable.scss. Inside of this navbar we use <ion-title>, which is typically used to display a text
title for the current page, to display our logo. We also use <ion-buttons> to create a button in the
146
navbar. By specifying the end attribute, the buttons will be placed on the right side on iOS, but if we were
to specify the start attribute it would be displayed on the left instead. Remember that Ionic 2 has platform
continuity built in, so by default the buttons will be placed in the most appropriate place for the platform
Finally we have the button itself inside of <ion-buttons>. This button uses a circle icon and has a click
handler attached to it, which will call the addChecklist() function in our home.ts le (which we have
not created yet of course). Lets move onto the list section now:
<ion-content>
<ion-list no-lines>
<ion-item-sliding>
<ion-item-options>
<button light (click)="renameChecklist(checklist)"><ion-icon
name="clipboard"></ion-icon> Edit</button>
<button danger (click)="removeChecklist(checklist)"><ion-icon
name="trash"></ion-icon> Delete</button>
</ion-item-options>
</ion-item-sliding>
</ion-list>
147
</ion-content>
Before we get to the list, notice that everything is wrapped inside of <ion-content> - this is what holds
the main content for the page and in most cases everything except the navbar will be inside of here.
<ul>
<li></li>
<li></li>
<li></li>
</ul>
<ion-list>
<ion-item></ion-item>
<ion-item></ion-item>
<ion-item></ion-item>
</ion-list>
Of course, ours looks a little more complicated than that so lets talk through it. The rst thing out of the
ordinary we are doing is adding the no-lines attribute to the <ion-list>. Just like the secondary
attribute we added to navbar, this attribute also styles our list except that it will cause the items in the list
The next bit gets a little trickier, as it is where we set up our sliding item. An <ion-sliding-item>, op-
posed to a normal <ion-item>, has two sets of content - the item itself, and then the <ion-item-options>
The rst block of code inside of <ion-sliding-item> is the normal <ion-item> denition, but instead
of using <ion-item> directly we are using <button ion-item> which is actually a button with the
styling of an item. Visually, these two methods are exactly the same, but on mobile everything that is not
148
a <button> or <a> element that has a click handler will have a slight tap delay. We dont want to have
The click handler we attach to the button calls a function viewChecklist but it also passes in a parameter
of checklist. We havent dened what this is yet, but eventually we will be creating a bunch of these
items from an array of data, and we will create a reference to each individual item that we can pass into
this function. So eventually, the checklist we are passing in here will be a reference to the specic item
that was clicked (we will discuss exactly how we do that later).
Finally, we have the second block of code, <ion-item-options>, which simply allows us to dene what
content we want to display when the user slides the item. In this case we are just adding Edit and Delete
buttons which will also pass in a reference to the checklist it was called on (again, we will have to create
Thats all there is to the home page, so lets move on to the checklist page now.
As we did before, lets rst take a look at what we are building before we jump in:
149
This screen looks very similar to the last one, and for the most part it is, but there are some dierences.
Obviously we have an extra button now, and a back button to return to the main page. The items in our
list also now have a checkbox next to them that will be used for marking an item as complete, and we still
Again, lets add the code for the template to the application and then talk through it:
<ion-header>
<ion-navbar secondary>
<ion-title>
150
CHECKLIST TITLE
</ion-title>
<ion-buttons end>
<button (click)="uncheckItems()"><ion-icon
name="refresh-circle"></ion-icon></button>
<button (click)="addItem()"><ion-icon
name="add-circle"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list no-lines>
<ion-item-sliding>
<ion-item>
<ion-label>ITEM TITLE</ion-label>
<ion-checkbox [checked]="item.checked" (click)="toggleItem(item)">
</ion-checkbox>
</ion-item>
<ion-item-options>
<button light (click)="renameItem(item)"><ion-icon
name="clipboard"></ion-icon> Edit</button>
<button danger (click)="removeItem(item)"><ion-icon
name="trash"></ion-icon> Delete</button>
</ion-item-options>
</ion-item-sliding>
151
</ion-list>
</ion-content>
You already know how the navbar works, this time weve just got an extra button inside of our
<ion-buttons> and it is still using the end attribute, so both items will be aligned to the right. We are
using the <ion-title> in a more traditional way this time to display the title of the checklist that is
currently being viewed (at least, we will be doing that soon). The buttons also both have click handlers to
dierent functions, but since we are in the checklist component now, these functions will be triggered in
You also know how the sliding items work now, but this time we have an <ion-checkbox> as the main
item instead of the title of the checklist and when it is clicked it will trigger the toggleItem() function
which we will need to dene later. Notice that we are just using a plain <ion-item> now instead of
<button ion-item>, this is because were attaching the click handler directly to the checkbox rather
than having it on the entire item.
Theres also a bit of new syntax here, lets take a closer look:
[checked]="item.checked"
that element, and we will be setting it to the expression contained in the quotes, not a string. So in this
case it would set the checked property to the value of item.checked. Right now we havent created a
reference to item so it wont work anyway, but later it will. The important thing to remember here is that the
square brackets will evaluate whatever is inside the quotation marks. Lets imagine you have the following
this.myName = "Josh"
<something myName="myName">
152
the myName attribute would be set to literally myName, but if I were to use this code instead:
<something [myName]="myName">
the myName property would be set to Josh, because myName would get evaluated rst in this instance.
Moving on, theres nothing else surprising in the rest of the template - we simply add the Edit and Delete
If you run ionic serve now, you should see something like this:
153
I think we can both agree thats pretty ugly, but the structure is there. Please keep in mind that we havent
styled the logo properly yet, so depending on what size device you are looking at it with it may not t as
154
In the following lessons we will be getting the list to pull in real data and styling it so that it looks a lot
better. The template syntax in Ionic 2 looks a little confusing at rst, but once you get your head around it
155
Lesson 4: Data Models and Observables
In this lesson were going to design a data model for the checklists that we will use in the application,
which will also incorporate Observables. A data model is not something that is specic to Ionic 2, a model
in programming is a generic concept. Depending on the context, the exact denition of a model may vary,
In the context of Ionic 2 & Angular 2, if we wanted to keep a reference to some data we might do something
like this:
this.myDataArray = [
new MyDataModel('1'),
new MyDataModel('2'),
new MyDataModel('3')
];
So instead of storing plain data, we are creating an object that holds that data instead. At rst it might be
hard to see why we would want to do this, for simple data like the example above it just looks a lot more
complicated, but it does provide a lot of benets. The main benet for us in this application will be that it:
Allows us to create helper functions on the data model to manipulate our data
Allows us to reuse the data model in multiple places, simplifying our code
Hopefully this lesson will show you how useful creating a data model can be, but let me preface this by
saying this isnt something that is absolutely required. You can quite easily just dene some data directly
Were also going to be creating and making use of our own Observable in this data model, but lets cross
156
Creating a Data Model
Usually if we wanted to create a data model we would create a class that denes it (its basically just a
class PersonModel {
constructor(name, age){
this.name = name;
this.age = age;
}
increaseAge(){
this.age++;
}
changeName(name){
this.name = name;
}
Then we could create any number of instances (objects) from it like this:
and we can call the helper functions on any individual instance (object) like this:
person1.increaseAge();
The idea in Ionic 2 is pretty much exactly the same, except to do it in the Ionic 2 / Angular 2 way we create
an Injectable (which we discussed in the basics section). Remember that an Injectable is used to create
157
services that can be injected into any of our other components, so if we want to use the data model we
Lets take a look at what the data model will actually look like, and then walk through the code.
checklist: any;
checklistObserver: any;
this.items = items;
addItem(item): void {
this.items.push({
title: item,
checked: false
});
removeItem(item): void {
158
this.items.splice(index, 1);
}
setTitle(title): void {
this.title = title;
}
toggleItem(item): void {
item.checked = !item.checked;
}
What were trying to do with this data model is essentially create a blueprint for what an individual checklist
is. A checklist has a title and it can have any number of items associated with it that need to be completed.
So we set up member variables to hold these values: a simple string for the title, and an array for the items.
Notice that we allow the title and the items to be passed in through the constructor. A title must be supplied
to create a new checklist, but providing an array of items is optional. If we want to immediately add items
159
to a checklist we can supply an items array when we instantiate it, otherwise it will just be initialised with
an empty array.
We include a bunch of helper functions which are all pretty straight forward, they allow us to either change
the title of the checklist, or modify any of the checklists items (by changing their name, removing an item,
adding a new item to the checklist, or toggling the completion state of an item).
Also notice that we have added : void after each of the functions. Just like we can declare that a variable
checklist: any;
we can also declare what type of data a function returns. In this case, no data is being returned so we
use void. If one of these functions were to return a string, then we would instead use : string on the
function.
With all of that set up, we can easily create a new checklist in any component where we have imported the
Checklist Model (which we will be doing in the next lesson) by using the following code:
or
Were going to get a little bit fancier now and incorporate an Observable into our data model so that we
can tell when any checklist has been modied (which will allow us to trigger a save to memory later).
Adding an Observable
Youve had a little bit of exposure to Observables already in the basics section of this course - to refresh
your memory we can use the Observable the Http service returns like this:
this.http.get('https://www.reddit.com/r/gifs/new/.json?limit=10').map(res
=> res.json()).subscribe(data => {
160
console.log(data);
});
We call the get method, and then subscribe to the Observable it returns. Remember that an Observ-
able, unlike a Promise, is a stream of data and can emit multiple values over time, rather than just once. This
concept isnt really demonstrated when using the Http service, since in most cases we are just retrieving
the data once. The Observable is also already created for us in the case of Http.
We are about to create our very own Observable from scratch in our data model, which will allow other
parts of our application to listen for when changes occur to our checklist (because we will emit some data
every time a change occurs). When implementing this Observable you will see how to create an observable
from scratch, and youll also see how an Observer can emit more than one value over time.
Before we get to implementing it, lets talk about Observables in a little more detail, in the context of what
were actually trying to do here. In the subscribe method in the code above we are only handling one
response:
this.http.get(url).subscribe(data => {
console.log(data);
});
which is actually the onNext response from the Observable. Observers also provide two other responses,
onError and onCompleted, and we could handle all three of those if we wanted to:
this.http.get(url).subscribe(
(data) => {
console.log(data);
},
(err) => {
console.log(err);
},
161
() => {
console.log("completed");
}
);
In the code above the rst event handler handles the onNext response, which basically means when we
detect the next bit of data emitted from the stream, do this. The second handler handles the onError
response, which as you might have guessed will be triggered when an error occurs. The nal handler
handles the onCompleted event, which will trigger once the Observable has returned all of its data.
The most useful handler here is onNext and if we create our own observable, we can trigger that onNext
response as many times as we need by calling the next method on the Observable, and providing it some
data.
Now that we have the theory out of the way, lets look at how to implement the observable.
checklist: any;
checklistObserver: any;
this.items = items;
162
});
addItem(item): void {
this.items.push({
title: item,
checked: false
});
this.checklistObserver.next(true);
removeItem(item): void {
this.checklistObserver.next(true);
163
if(index > -1){
this.items[index].title = title;
}
this.checklistObserver.next(true);
setTitle(title): void {
this.title = title;
this.checklistObserver.next(true);
}
toggleItem(item): void {
item.checked = !item.checked;
this.checklistObserver.next(true);
}
The rst thing to notice here is that we are now importing Observable from the RxJS library. Then in our
Our this.checklist member variable in the code above is now our very own observable. Since it is
an observable, we can subscribe to it, and since it is part of our data model, we can subscribe to it on any
164
let newChecklist = new ChecklistModel('My Checklist', []);
newChecklist.checklist.subscribe(data => {
console.log(data);
});
Of course, we arent doing anything with the Observable yet so its never going to trigger that onNext
response. This is why we have added the following bits of code to each of our helper functions:
this.checklistObserver.next(true);
So whenever we use one of our helper functions to change the title, or add a new item, or anything else, it
will notify anything that is subscribed to its Observable. All we want to know is that a change has occurred
so we are just passing back a boolean (true or false), but we could also easily pass back some data if we
wanted.
The result of this is that now we can observe any checklists we create for changes that occur. Later on
we will make use of this by listening for these changes and then triggering a save.
Summary
In this lesson weve gone a little bit beyond the beginner level and created a pretty robust data model. As
Ive mentioned, this certainly has its benets but dont feel too intimidated if you had trouble following
along with this lesson - as a beginner you can mostly get away with just dening data directly on the class
I particularly dont want to freak you out too much with the Observabes - they are confusing (until you
get your head around them) and outside of subscribing to responses from the Http service, you really
dont have to use them in most simple applications. But once you do understand them, you can do some
Although this lesson was a little more advanced, its a great way to demonstrate how you might make
165
use of Observables in your project, and if youve kept up through this lesson then hopefully the next ones
should be a breeze!
166
Lesson 5: Creating Checklists and Checklist Items
Weve done a lot of setting up and structuring so far, but in this lesson well be getting to the bones of what
were building. Well be adding ways to create new checklists, viewing those checklists and adding items
to them (as well as modifying any items or the checklist itself). Its going to be a big one so strap in and
Checklists
The rst thing we are going to do is add everything we need for creating and viewing checklists. This will
mean adding to our class denition, as well as modifying the template we created before to actually display
@Component({
templateUrl: 'build/pages/home/home.html'
})
export class HomePage {
167
constructor(public nav: NavController, public dataService: Data, public
alertCtrl: AlertController) {
addChecklist(): void {
renameChecklist(checklist): void {
viewChecklist(checklist): void {
removeChecklist(checklist): void{
save(): void{
Were importing a few things from the Ionic library here. NavController you should already be pretty familiar
with, but you might not know what AlertController is. AlertController allows us to present various alerts to
the user, including a basic prompt, prompts with input, conrmation prompts and more. We will be using
168
these as a method to add new checklists.
Were also importing our ChecklistPage which we will nish implementing later, but most importantly we
are importing the Checklist Model we created in the last lesson. As well as importing the Checklist Model,
Finally, we also import the Data provider we generated earlier, but we wont be implementing its functionality
until later.
By adding the public keyword in the constructor we are simply setting up a reference to the Nav-
Controller and Data provider that we can use throughout the class later by referencing this.nav and
Weve also declared a checklists array at the top of the class which will make it accessible throughout
the class by referencing this.checklists. The rest of the class is various functions which we will step
addChecklist
This function will be responsible for allowing the user to create a new checklist. It will launch a prompt,
and use the data that is entered to create a new checklist (making use of the data model we created).
addChecklist(): void {
let prompt = this.alertCtrl.create({
title: 'New Checklist',
169
message: 'Enter the name of your new checklist below:',
inputs: [
{
name: 'name'
}
],
buttons: [
{
text: 'Cancel'
},
{
text: 'Save',
handler: data => {
let newChecklist = new ChecklistModel(data.name, []);
this.checklists.push(newChecklist);
newChecklist.checklist.subscribe(update => {
this.save();
});
this.save();
}
}
]
});
prompt.present();
}
We are presenting a prompt to the user that will contain a single name input eld, and two buttons Cancel
170
and Save. The cancel button does nothing except dismiss the prompt, but we add a handler to the save
button that will pass in the data that was entered into the input eld.
Inside of this handler we rst generate a new checklist by passing the entered name into a new instance of
our checklist model, and then we push that object into our this.checklists array. Then we subscribe
to the observable we added to the data model in the last lesson to listen for whenever the checklist is
modied in anyway, and when it is we trigger the save function. Notice that we have two calls to save here,
one that is triggered by the observable and one that res straight away (since we have just added a new
checklist).
If you take a look at your template le again, youll remember that weve already added a call to this function
<button (click)="addChecklist()"><ion-icon
name="add-circle"></ion-icon></button>
renameChecklist
Next were going to dene the renameChecklist function which, obviously, will allow us to rename a
checklist.
renameChecklist(checklist): void {
171
}
],
buttons: [
{
text: 'Cancel'
},
{
text: 'Save',
handler: data => {
}
}
]
});
prompt.present();
The rst thing that you may notice is that it looks very similar to our addChecklist function, and that is
because it is. We use the same prompt with the same inputs and buttons, we just have a slightly dierent
handler.
Notice that we are passing in a parameter to this function, which will be a reference to the checklist that
172
we want to rename. We will be updating the template shortly to pass in this reference, but for now just
We use this reference to the checklist to nd it in our this.checklists array and then set it to the new
Again, if you recall from before, we have already set up a click handler that will call this function in the
template:
removeChecklist
removeChecklist(checklist): void{
This function is quite a lot simpler because it doesnt require any user input, we just need to get rid of the
checklist. Just as we did before, we are passing in a reference to the checklist and then nding the checklist
in our this.checklists array. We then simply remove it from the array using the splice method and
trigger a save.
173
Heres the code from the template that triggers this function:
viewChecklist
We can create and modify our checklists now, but we also need to be able to see the details of specic
checklists, and to add individual items to checklists. To do this, were going to use our NavController to
push a new page and pass in a reference to the checklist that was clicked.
viewChecklist(checklist): void {
this.nav.push(ChecklistPage, {
checklist: checklist
});
}
We pass in the ChecklistPage we imported before (which we are yet to nish) to the push method, as well
as the data we want to send along to the new page, which is a reference to the checklist the user is trying
to view. We will be able to use NavParams in the class for our checklist page to grab this data later.
save
This one function is going to be the odd one out for now as we arent actually going to implement it. Theres
quite a lot that needs to go into it so well be covering saving and loading data in its own lesson later.
To bring everything together, we need to nish o the template for the home page. We can do all of this
stu with our data now, but we cant even see the results.
174
<ion-header>
<ion-navbar secondary>
<ion-title>
<img src = "images/logo.png" />
</ion-title>
<ion-buttons end>
<button (click)="addChecklist()"><ion-icon
name="add-circle"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list no-lines>
<ion-item-options>
<button light (click)="renameChecklist(checklist)"><ion-icon
name="clipboard"></ion-icon></button>
<button danger (click)="removeChecklist(checklist)"><ion-icon
name="trash"></ion-icon></button>
</ion-item-options>
175
</ion-item-sliding>
</ion-list>
</ion-content>
Were doing a few interesting things here now, most notably we have added an ngFor loop:
What this will do is loop over every entry we have in our this.checklists array and create a sliding
item for it in the list. Remember, the * syntax used in front of the ngFor here is a shortcut for creating
embedded templates in Angular 2, so what we are doing essentially is creating a template that is stamped
out for as many times as we have items in our array. Each time this template is stamped it will contain
the information for the specic item it was stamped out for, so anywhere inside of the ngFor loop we can
grab the data of the specic checklist that is being rendered using:
{{checklist.title}}
Notice that we also have a let infront of checklist in the ngFor loop. In Angular 2 using let like
this allows us to create a local variable, and this is what then allows us to pass a reference to the specic
checklist into all of the functions we just created. To make the concept more clear, if we were to use the
{{check.title}}
removeChecklist(check)
176
That just about nishes up our home page, and you should now be able to add, edit and delete checklists,
as well as launch the details page for a specic checklist (which wont contain anything just yet, and wont
really work).
If you re up ionic serve now you should see something like this:
177
If you try to run this code youre going to get complaints about a missing provider, so you should
178
import {Component} from "@angular/core";
import {Platform, ionicBootstrap} from 'ionic-angular';
import {StatusBar} from 'ionic-native';
import {HomePage} from './pages/home/home';
import {Data} from './providers/data/data';
@Component({
template: '<ion-nav [root]="rootPage"></ion-nav>'
})
export class MyApp {
constructor(platform: Platform) {
platform.ready().then(() => {
StatusBar.styleDefault();
});
}
}
ionicBootstrap(MyApp, [Data]);
It is also important to note that you must remove this line from checklist.html:
<ion-checkbox [checked]="item.checked"
(click)="toggleItem(item)"></ion-checkbox>
if you want to create an item and then go to its detail page. In a later lesson we will only include this if
there is item data available, but right now we arent doing that so an error will be caused since it is trying
We will get to all of this later anyway, so you only need to do these extra steps if you want to have a play
179
around with the app right now.
Checklist Items
Now that we can trigger the checklist detail page, wed best get some content in there and add a way
for people to create and modify individual checklist items. In this section we will be implementing our
Checklist Page, which is launched by the home page and is supplied some data regarding which checklist
is being viewed.
@Component({
templateUrl: 'build/pages/checklist/checklist.html'
})
export class ChecklistPage {
checklist: any;
addItem(): void {
180
toggleItem(item): void {
removeItem(item): void {
renameItem(item): void {
uncheckItems(): void {
}
}
Theres nothing much going on here that you are not already familiar with, the only thing out of the ordinary
is the use of NavParams. When we pass in data to another page, we can grab it by injecting NavParams
and using the get method. In this instance we are just passing in the checklist data that we want to view,
Just as we did before, we are going to go through implementing these functions one by one now. A lot of
these will be quite similar to what we just did for the home page.
addItem
addItem(): void {
181
let prompt = this.alertCtrl.create({
title: 'Add Item',
message: 'Enter the name of the task for this checklist below:',
inputs: [
{
name: 'name'
}
],
buttons: [
{
text: 'Cancel'
},
{
text: 'Save',
handler: data => {
this.checklist.addItem(data.name);
}
}
]
});
prompt.present();
This should all look very familiar, but notice the dierence in the handler. Since we created an addItem
help function on our data model, all we have to do is call that and pass in the name of the item we want to
182
renameItem
renameItem(item): void {
prompt.present();
183
Once again, almost the exact same idea except we are calling the renameItem helper function on our
data model in the handler, and we are also passing through a reference to the specic item that we are
renaming.
removeItem
removeItem(item): void {
this.checklist.removeItem(item);
}
This one is even simpler, we simply call the removeItem helper function on the data model and pass it a
toggleItem
toggleItem(item): void {
this.checklist.toggleItem(item);
}
This function is used to toggle the checkmarks on an individual item on and o, and once more we simply
pass through a reference to the item we want to toggle to the data model.
uncheckItems
uncheckItems(): void {
this.checklist.items.forEach((item) => {
184
if(item.checked){
this.checklist.toggleItem(item);
}
});
}
This function is tied to the reset button we added to our template, and will loop through every item we have
in the checklist and call the toggleItem function in the data model if the current item is checked. This
Now all we have left to do is update the template for our checklist page. Weve already set up most of the
structure for this template, but just like with the home page we will need to add a little bit more to handle
displaying data.
<ion-header>
<ion-navbar secondary>
<ion-title>
{{checklist.title}}
</ion-title>
<ion-buttons end>
<button (click)="uncheckItems()"><ion-icon
name="refresh-circle"></ion-icon></button>
<button (click)="addItem()"><ion-icon
name="add-circle"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
185
<ion-list no-lines>
<ion-item>
<ion-label>{{item.title}}</ion-label>
<ion-checkbox [checked]="item.checked" (click)="toggleItem(item)"
class="checklist-item">
</ion-checkbox>
</ion-item>
<ion-item-options>
<button light (click)="renameItem(item)"><ion-icon
name="clipboard"></ion-icon></button>
<button danger (click)="removeItem(item)"><ion-icon
name="trash"></ion-icon></button>
</ion-item-options>
</ion-item-sliding>
</ion-list>
</ion-content>
This should all look pretty similar to the home page template, but there are a few dierences. We are using
the checklist data from our class to display the title of the current checklist in the navbar. We are again
looping through data using ngFor but this time we are only looping through the items, which are a child
of the checklist. Also notice that as well as rendering data using double braces like this:
{{item.title}}
186
we can also set properties on elements using the square brackets like this:
[checked]="item.checked"
this will set checked to be the value of whatever item.checked evaluates to.
Summary
You should now be able to perform just about every function of the application, which includes creating
checklists, modifying them, viewing individual checklists, and adding items to individual checklists.
Try running the application in your browser and adding your own checklists and checklist items.
In the next lesson well work on saving data and then well look into prettying up the application (function
187
Lesson 6: Saving and Loading Data
You know what would be really annoying? If you create an entire checklist full of items for some task you
need to complete, come back to use it later and its just gone. Well thats exactly how the application
works right now, so we are going to need to add a way to save any data the user adds to the application
Weve already set up a lot of the structure for this, were subscribing to our Observables and calling the
save function every time some data changes, we just need to implement that function now.
save(): void {
this.dataService.save(this.checklists);
}
If you recall, earlier we already generated and imported a data service, so all we need to change here is to
add a call to it and pass in the current checklists data. Of course, we havent actually implemented that
data service yet so it wont do anything with the data, but we will get to that shortly.
One more thing we need to do before we can successfully use this data service is to declare it in the
providers array. You may remember that we also declared our Checklist Model in the providers array for
the home page, and we could do the same for the data service, but instead we are going to add it to the
> Import the Data provider in app.ts and modify the decorator to reect the following:
@Component({
188
template: '<ion-nav [root]="rootPage"></ion-nav>'
})
export class MyApp {
constructor(platform: Platform) {
platform.ready().then(() => {
StatusBar.styleDefault();
});
}
}
ionicBootstrap(MyApp, [Data]);
In this particular application it doesnt make a dierence if we add the provider to app.ts or home.ts
because the data service is only ever called from home.ts. But in some applications it would be likely that
the data service would be called from multiple dierent components, so by adding it to app.ts you dont
have to worry about declaring it in every component you are using it in. Although you dont need to declare
it as a provider in every class you want to use it in by doing it this way, you do still need to import it at the
Its important to keep in mind that if you add the provider declaration to the app.ts le, then it will create one
app wide instance of that provider. If you add the provider declaration to multiple dierent components,
then each of those components will get their own instance of the provider (so if you were looking to share
values across the application, you would want to declare the provider in app.ts.
Now all we have to do is make the data service do something with the data we are sending it.
189
Saving Data
Were going to add the code for data.ts now so that it will save into storage any data that it is passed. The
code for this service is actually surprisingly simple, so lets take a look at it rst and then talk through it.
@Injectable()
export class Data {
storage: Storage;
constructor(){
this.storage = new Storage(SqlStorage, {name:'checklist'});
}
getData(): Promise<any> {
return this.storage.get('checklists');
}
save(data): void {
//Remove observables
data.forEach((checklist) => {
saveData.push({
title: checklist.title,
190
items: checklist.items
});
});
Just like our Checklist Model, this is also an Injectable. Its really not any dierent to the model, its functions
just do dierent things - an Injectable / Provider / Service is just a way to create a generic thing that does
stu.
Storage is Ionics generic storage service, and SqlStorage is used to dene the type of storage we want
to use. In the case of SqlStorage that means we will be storing data using a native SQLite database
(remember how we added the SQLite plugin before?). The SQLite database will only be available when
running natively on a device, so SqlStorage also uses local browser storage store data if the SQLite
Alternatively, you could just use the LocalStorage service instead of **SqlStorage* which just stores data
in the browsers local storage, rather than hooking into the SQLite plugin. In general this is a bad idea (but
were going to use it a little later anyway just to prove a point), because the browser based local storage
is not completely reliable and can potentially be wiped by the operating system. Having your data wiped
randomly is obviously not ideal, so you should use SqlStorage whenever possible, which does not have
191
constructor(){
this.storage = new Storage(SqlStorage, {name:'checklist'});
}
We create a reference to our database by instantiating a new instance of Storage. We provide the storage
type we want to use, and provide the name of our database. If the database does not already exist, it will
be created.
getData(): Promise<any> {
return this.storage.get('checklists');
}
This function will allow us to retrieve the latest data that has been stored, and it will return it in the form of a
Promise. We are setting the return type for this function as a Promise that returns <any> type, this is one
of the more complicated types. Remember, adding types like this is not required so if you are confused by
this and would prefer to leave it out you could just do this instead:
getData(){
return this.storage.get('checklists');
}
Notice that we are not setting up the handler for when the promise nishes here, instead we just return the
result of the get method (which will be a promise which resolves with the data that is currently in storage).
Remember that this operation is not instant, and this allows us to set up the handler from wherever in the
code this method is being called, which makes more sense for the ow of the application (hopefully this
Then we have our saveData function, which handles actually saving the data into storage:
save(data): void {
192
//Remove observables
data.forEach((checklist) => {
saveData.push({
title: checklist.title,
items: checklist.items
});
});
As I mentioned, we are storing the data as a single JSON encoded string, so we call the JSON.stringify
function and then store the data using the set method on our storage object. Before we do that though,
we remove the observable stu from the data by only pushing the title and the items since it doesnt
play nice with JSON (it causes a circular object error), and well just be recreating them later anyway.
Thats all there is to saving the data, which isnt really all that complex. Now we just need to handle loading
Loading Data
We are going to want to load the checklists data from storage whenever the user opens the application,
so a good place to do this is the constructor on our home page. Weve already imported and set up a
reference to the data service in this page, so all we need to do is make use of it.
193
this.dataService.getData().then((checklists) => {
if(typeof(checklists) != "undefined"){
savedChecklists = JSON.parse(checklists);
}
if(savedChecklists){
savedChecklists.forEach((savedChecklist) => {
loadChecklist.checklist.subscribe(update => {
this.save();
});
});
});
Were making a call to the getData function that we just dened in our data service. As I mentioned, the
194
getData function returns a promise rather than the data directly, which allows us to handle the response
here once it has nished loading. If the getData function just returned the data directly, rather than a
promise, then the data would likely not have even been returned yet when we try to access it.
So we wait for the data to be retrieved, and then pass the checklists data into our handler. First we decode
the JSON string into an array that we can work with, and then we loop through every item in the array
and create a new Checklist Model based on its data. The reason we loop through the data and create
new models rather than just settings this.checklists to be savedChecklists directly is because
by converting the checklists into a JSON string when we store it we lose the ability to use the helper
functions we dened on the model. So we just use the title and items data to recreate new objects for all
the checklists.
Finally, we set up the listener for the Observable again so that the save function will be triggered whenever
Summary
Thats all there is to it, the data will now be saved to the SQLite database whenever changes are made,
and when the application is reopened all of the data will be loaded back in. Try adding some checklists or
modifying the state of your checklists and reloading the application to see if the changes stick around.
195
Lesson 7: Creating an Introduction Slider & Theming
In the last lesson for the Quick Lists application we are going to be adding a few nal touches to improve
the user experience. We will add a slideshow tutorial to show the user how to use the app (which will only
display on their rst time using the app) and we will also add some styles to make the application look a
bit prettier.
Slider Component
Its pretty common for mobile applications to display some instructions to the user through the use of a
sliding card style tutorial. Ionic has a slide component built-in so we are going to make use of that, and
to make sure the user doesnt have to go through the tutorial every time we will be keeping track of if they
The slider itself is going to be pretty simple, it will allow us to display a series of images and on the last
First, were going to build the slider component and then we are going to look at how to integrate it into
<ion-content>
<ion-slides [options]="slideOptions">
<ion-slide>
<img src="images/slide1.png" />
</ion-slide>
196
<ion-slide>
<img src="images/slide2.png" />
</ion-slide>
<ion-slide>
<img src="images/slide3.png" />
</ion-slide>
<ion-slide>
<ion-row>
<ion-col>
<button light (click)="goToHome()" style="margin-top:20px;">Start
Using Quicklists</button>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<img src="images/slide4.png" />
</ion-col>
</ion-row>
</ion-slide>
</ion-slides>
</ion-content>
The rst thing you might notice about this is that it doesnt contain a navbar, only the content area. Its
not necessary to include the navigation bar on every page, and we do not want to display it here. The rest
of the code is pretty simple, we use <ion-slides> with a options property so that we can congure it
with some options in our class denition in a moment, and we use <ion-slide> to dene each one of
197
our slides.
So in the code above, the user will rst see a slide containing the slide1 image, then when they swipe they
will see slide2 and so on until they reach the last slide which contains a button to go to the home page.
The last slide is a little more complicated because we are making use of <ion-row> and <ion-col>
so that we can position the button where we want it. These two directives make up Ionic 2s grid system,
where rows get placed underneath one another, and cols within those rows appear side by side. This
This is a very simple example because we just want the button to appear above the image, but you can
create quite complex layouts by supplying a width to the cols like this:
<ion-row>
<ion-col width-10></ion-col>
<ion-col width-50></ion-col>
198
</ion-row>
and you can include as much nesting as you like. The result of our grid layout will look like this:
199
Now we need to dene the class for our intro component.
@Component({
templateUrl: 'build/pages/intro/intro.html'
})
export class IntroPage {
slideOptions: any;
goToHome(): void {
this.nav.setRoot(HomePage);
}
}
Isnt this just the simplest class youve seen so far? All it does is import the home page, set the pager
option for our slider, and change the root page to it using the NavController when the goToHome function
is called. This allows us to tap the button on the last slide to go to our main home page view, but we will
have a bit of a problem. Every time the user opens the application they are going to have to go through
this tutorial to get to the main app. To solve this, we are going to make one more change to our home.ts
200
le.
this.local.get('introShown').then((result) => {
if(!result){
this.local.set('introShown', true);
this.nav.setRoot(IntroPage);
}
});
this.dataService.getData().then((checklists) => {
201
let savedChecklists: any = false;
if(typeof(checklists) != "undefined"){
savedChecklists = JSON.parse(checklists);
}
if(savedChecklists){
savedChecklists.forEach((savedChecklist) => {
loadChecklist.checklist.subscribe(update => {
this.save();
});
});
});
Were importing the Storage service again now, but this time we are also importing the LocalStorage service
instead of SqlStorage. Were going to use this to store a ag that will tell us whether or not the tutorial
has already been viewed. I could just as easily also use SqlStorage for this, but I wanted to show you that
this is an acceptable use case of where you could use local storage. As I mentioned before, local storage
202
isnt stable and could be wiped so usually its not suitable for storing data, but if the worst were to happen
and the data was wiped in this case it wouldnt really matter - the user would just have to go through the
tutorial again.
So we set up our new local storage object and we check for the existence of an introShown ag. If it
does not exist then we switch to our intro tutorial page and then set that ag to be true so it doesnt show
next time.
Theming
As far as functionality in the application goes, were 100% done. Now were just going to add a bit of
styling to the application to make it look quite a bit better than it currently does.
If you remember from the basics section, theres quite a few dierent ways we can add styles to the
application. We will be basically using all of these methods. We will be adding specic styles to each of
our components, we will be adding some generic styles in our core le, and we will be overriding some
SASS variables in the variables le. If you skipped over that part or are not entirely sure what Im talking
about here, Id recommend going back and reading about theming in the basics sections.
Since we just nished working on our intro component, lets add the styles for that rst:
ion-slide {
background-color: #32db64;
}
ion-slide img {
height: 85vh !important;
width: auto !important;
}
203
These styles will make the background colour of the slides green, and also set the images inside of the
The rest of our components are going to require us adding some classes into our template that we can
hook into, so from now on Ill post both the nalised template code, as well as the styles to go along with
it.
<ion-header>
<ion-navbar secondary>
<ion-title>
<img src = "images/logo.png" class="logo" />
</ion-title>
<ion-buttons end>
<button (click)="addChecklist()"><ion-icon
name="add-circle"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list no-lines>
204
</ion-item-content>
</button>
<ion-item-options>
<button light (click)="renameChecklist(checklist)"><ion-icon
name="clipboard"></ion-icon></button>
<button danger (click)="removeChecklist(checklist)"><ion-icon
name="trash"></ion-icon></button>
</ion-item-options>
</ion-item-sliding>
</ion-list>
</ion-content>
.home-sliding-item {
margin: 5px;
}
.home-item {
font-size: 1.2em;
font-weight: bold;
color: #282828;
padding-top: 10px;
padding-bottom: 10px;
}
.secondary-detail {
display: block;
205
color: #cecece;
font-weight: 400;
margin-top: 10px;
}
Were not doing anything too crazy here, just adding a few tweaks to the margins, padding and colours.
<ion-header>
<ion-navbar secondary>
<ion-title>
{{checklist.title}}
</ion-title>
<ion-buttons end>
<button (click)="uncheckItems()"><ion-icon
name="refresh-circle"></ion-icon></button>
<button (click)="addItem()"><ion-icon
name="add-circle"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list no-lines>
206
<ion-item>
<ion-label>{{item.title}}</ion-label>
<ion-checkbox [checked]="item.checked" (click)="toggleItem(item)"
class="checklist-item">
</ion-checkbox>
</ion-item>
<ion-item-options>
<button light (click)="renameItem(item)"><ion-icon
name="clipboard"></ion-icon></button>
<button danger (click)="removeItem(item)"><ion-icon
name="trash"></ion-icon></button>
</ion-item-options>
</ion-item-sliding>
</ion-list>
</ion-content>
.checklist-item {
font-size: 0.9em;
font-weight: bold;
color: #282828;
padding-top: 0px;
padding-bottom: 0px;
padding-left: 4px;
border: none !important;
}
207
ion-item-content {
border: none !important;
}
ion-checkbox {
border-bottom: none !important;
}
ion-checkbox .item-inner {
border-bottom: none !important;
}
Once again, just a few minor tweaks here. Now we are going to add the styles that will apply across the
entire application:
@import "../pages/home/home";
@import "../pages/checklist/checklist";
@import "../pages/intro/intro";
ion-content {
background-color: #32db64;
}
.logo {
max-height: 39px;
}
button {
border: none !important;
208
}
Here were making the entire application have a background colour of green, we set a maximum height
for the logo and remove borders from our buttons. Finally, we are going to override some of the SASS
variables.
$colors: (
primary: #387ef5,
secondary: #32db64,
danger: #f53d3d,
light: #f4f4f4,
dark: #222,
favorite: #69BB7B
);
$list-background-color: #fff;
$list-ios-activated-background-color: #3aff74;
$list-md-activated-background-color: #3aff74;
$checkbox-ios-background-color-on: #32db64;
$checkbox-ios-icon-border-color-on: #fff;
$checkbox-md-icon-background-color-on: #32db64;
$checkbox-md-icon-background-color-off: #fff;
$checkbox-md-icon-border-color-off: #cecece;
$checkbox-md-icon-border-color-on: #32db64;
Weve modied the colours for the application and set a few iOS and Android specic styles (the names
should make it pretty clear what is being changed). One of the coolest things about Ionic 2 is how well it
handles UI dierences between iOS and Android, for the most part it just works awlessly out of the box.
209
If you take a look at the application on iOS and on Android, you will see the dierences:
NOTE: A handy way to see iOS and Android side by side is to use Ionic Lab, which can be activated
by using the ionic serve -l command. You may notice that the applications have a scrollbar on the
edge when viewing through Ionic Lab, I believe this is a bug and it is not present when running on an actual
As you can see, Ionic 2 automatically conforms to the norms of whatever platform the application is running
on.
210
Summary
Thats it! The application should now be completely nished and it nally actually looks pretty nice too.
211
Conclusion
Congratulations on making it through the Quick Lists tutorial. This application is a great example for
beginners to start getting their feet wet, and the main take aways from it are:
Theres always room to take things further though, especially when youre trying to learn something. Fol-
lowing tutorials is great, but its even better when you gure something out for yourself. Hopefully you
have enough background knowledge now to start trying to extend the functionality of the application by
Retheme the application with your own styling, try dierent colours, padding, margins and so on
[EASY]
Add a Date Created eld to the data model that records when a checklist was created, and display
it in the template (dont forget to make sure it gets loaded from memory too!) [MEDIUM]
Figure out how many items have been marked as completed in a single checklist, and display a
Add the ability to attach notes to any specic checklist item [HARD]
Remember, the Ionic 2 documentation is your best friend when trying to gure things out.
What next?
You have a completed application now, but thats not the end of the story. You also need to get it running
on a real device and submitted to app stores, which is no easy task. The nal sections in this book will
walk through how to take what you have done here, and get it onto the app stores so make sure to give
212
that a read.
213
Chapter 4
Giist
214
Lesson 1: Introduction
Giist was the rst application I created with Ionic 2, and it was originally built on one of the really early
alpha versions of Ionic 2. Its been updated a bit since then, and more recently with the Ionic 2 beta for
this book. Even though it was the rst app I built its still my favourite, so Im excited to walk you through
building it.
I think its my favourite because its just a really fun app. Its a fun app to build because a lot of interesting
topics are covered like using the Reddit API and using HTML5 video, and its a fun app to use because
About Giist
Giist is a pretty simple application, the general idea is that a user can enter in any subreddit from reddit,
and the app will fetch and display GIFs from that subreddit.
Im going to assume if youre reading this book then you know what reddit is - but - I dont really like to
make any assumptions so if youre somehow not familiar with Reddit, basically its a site where people can
submit links to anything and the user base votes up stu that they think is cool or relevant. A subreddit
gifs
askreddit
worldnews
As well as just fetching GIFs from reddit, users will also be able to supply some settings to congure the
application to their preferences. Although the concept is pretty simple, there are a few interesting things
that you will learn through building it, this will include:
Data storage
Theming
215
Lists
Modals
Data models
HTML5 Video
An endless list of GIFs will be displayed as a list (assuming GIFs are available)
The user will be able to set the following options: default subreddit, sort order, GIFs per page
216
217
###Lesson Structure
218
1. Getting Ready
4. Settings
5. Theming
Ready?
Now that you know what youre in for, lets get to building it!
219
Lesson 2: Getting Ready
In this lesson we are going to prepare our application for the journey ahead. We are going to of course
generate the application, and we are also going to set up all of the components and Cordova plugins we
will need. At the end of this rst lesson we should have a nice skeleton application set up with everything
A good rule of thumb before starting any new application is to make sure you have the latest version of
Ionic and Cordova, so if you havent done it recently then make sure to run:
or
We will be using the blank starter template for this application which, as the name implies, is basically an
empty Ionic project. It comes with one page built in called home which we will repurpose as our list page
> Make the new project your current working directory by running the following command:
cd giflist
Your project should now be generated - now you can open up the project folder in your favourite editor.
You can take a look at how your application looks by running the following command:
220
ionic serve
221
###Create the Required Components
The structure of this application is pretty simple, so all were going to have is 2 pages: the list page and
the settings page. Weve already got a home component which we will use as our list page, so we only
Also remember that any time we generate a new component, we need to import its .scss le into our
app.core.scss le. The Ionic CLI automatically generates pages for us, but it doesnt automatically import
@import "../pages/home/home";
@import "../pages/settings/settings";
As well as our 2 pages, we are also going to create a data service to handle storing and retrieving the users
settings.
Before you can build for certain platforms, you need to add them to your project. This is something we
will be doing way later on in this course, but you may as well just set them up now.
> Run the following command to add the iOS platform to your application
222
ionic platform add ios
Run the following command to add the Android platform to your application
This application will use a few dierent Cordova plugins. Remember, Cordova plugins can only be used
when running on a real device. Ill explain each plugin as we run through the commands for adding them.
This plugin gives you access to native storage with an SQLite database. We are adding it to this application
because the Ionic local storage service can make use of this plugin to provide more stable data storage.
> Run the following command to add the In App Browser plugin:
This plugin makes a webview available to us that we can launch external websites in. We will be using this
plugin in this application to allow the users to view the original Reddit submission of a GIF in a browser.
> Run the following command to add the Status Bar plugin:
We will be adding this plugin to all projects to give us control over the status bar in our application (the bar
at the top of the devices screen that contains the time, battery information and so on).
> Run the following command to add the Splash Screen plugin:
223
This plugin allows us to control the splash screen (the fullscreen graphic that briey displays when you
open an app)
This plugin is required for all applications, and helps to dene what resources should be allowed to be
loaded in your application. Without it, resources you try to load will fail.
As well as adding the plugin, you also need to dene a Content Security Policy in your index.html
le. We will be adding a very permissive policy which will essentially allow us to load any resources.
Depending on your application, you may look into providing a more strict policy, but an open policy is
This is another plugin that we will add to every application, but you may decide you want to leave it out.
By adding this plugin, when you build for Android Crosswalk will be used. Android has a lot of issues,
especially with older devices, because there is so many dierent software versions out there and dierent
versions have dierent browsers (remember, since we are building HTML5 applications it is actually a
browser engine powering and running our application). What Crosswalk does is bundle a modern browser
into your application so no matter what device you are running on your app will be powered by the same
The only real downside to this is that it increases the size of your application by a signicant amount. In
general, I think its worth it and Id recommend you include it but you may leave it out if you like. For more
224
Set up Images
When building this application we are going to be making use of a few images. Ive included these in your
download pack but you will need to set them up in the application you generate.
> Copy the images folder in the download pack for this application from www/images to your own
www folder
Summary
Thats it! Were all set up and ready to go, now we can start working on the interesting stu.
225
Lesson 3: The List Page
In the last lesson we worked on getting everything set up correctly, and now were going to start actually
building stu. In this lesson well mostly be focusing on modifying the Home page to act as the list that
The Layout
Before we start building the layout, which will be dened in home.html, lets take a look at what were
building:
226
Its a reasonably simple layout, we have a toolbar at the top that contains a search bar and a settings
button (which will launch our Settings page later). Beneath that, we have a list that holds all of the GIFs
returned from Reddit. Not pictured is the Load More button which sits at the bottom of the list, when the
227
user taps this it will load in the next page of GIFS.
First were going to look at the entire template to see everything in context, then were going to break it
<ion-header>
<ion-navbar secondary>
<ion-title>
<ion-searchbar primary placeholder="enter subreddit name..."
hideCancelButton></ion-searchbar>
</ion-title>
<ion-buttons end>
<button (click)="openSettings()"><ion-icon
name="settings"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content class="home">
<ion-list>
<ion-item no-lines>
GIF GOES HERE
<ion-list-header>
TITLE GOES HERE
</ion-list-header>
228
</ion-item>
</ion-list>
</ion-content>
In the rst part of this code we are setting up the navigation bar:
<ion-navbar secondary>
<ion-title>
<ion-searchbar primary placeholder="enter subreddit name..."
[(ngModel)]="subredditValue" hideCancelButton></ion-searchbar>
</ion-title>
<ion-buttons end>
<button (click)="openSettings()"><ion-icon
name="settings"></ion-icon></button>
</ion-buttons>
</ion-navbar>
We add the secondary directive to the <ion-navbar> which will alter its style to use the secondary
Were using <ion-title>, which is usually used to supply a title to be displayed on the navbar, to position
229
our search bar in the middle of the navbar. Usually youd have a separate toolbar for a searchbar so that it
can take up the whole space, but I didnt want to clutter the screen too much so were going to bend the
rules a bit here with this. In order for it to display properly, well need to add a few custom styles later.
Notice that we are also supplying the primary directive to give the searchbar the primary colour, and we
also use hideCancelButton so that the cancel button doesnt display when the user is typing. The most
important part here is [(ngModel)] which will tie the input of this eld to a subredditValue variable
Then we use <ion-buttons> to place our settings button, which will launch our settings page in the
navbar. Using the end directive will place the button on the right, and if we wanted to place a button on
the left we could use the start directive. Weve also added a (click) listener to this button so that the
openSettings function will be called when the button is clicked. We havent created this function yet
so nothing will happen, but we will dene it later in home.ts.
<ion-list>
<ion-item no-lines>
GIF GOES HERE
<ion-list-header>
TITLE GOES HERE
</ion-list-header>
</ion-item>
</ion-list>
230
Lists are one of the most frequently used components in mobile applications. In Ionic you can create an
<ion-list> and supply it any number of <ion-item> tags to create a list. For now we just have a
single item, but later we will modify this to automatically loop over every GIF we want to display. Notice
that we are also using <ion-list-header> to create a header area where we will be able to display the
title of the GIF, and we also add no-lines to the item so that there is no borders around list items.
As well as our GIF items, we are going to add one additional item at the bottom of the list which will contain
a loading animation. This will be used to display a spinning animation when new GIFs are being fetched,
but since we only want it to display when loading is occuring we use the *ngIf directive to control when
it displays. In this case, the loading animation will only display when loading evaluates to true (we will
dene this in our class denition later, and toggle it on and o when we are loading).
The last bit of code we have in our template is the load more button:
Nothing too crazy happening here, we supply the light directive to again change the colour and we have
a (click) function set up that will eventually call the loadMore() function we will dene in our class
denition.
With the template dened we have our view sorted, now we need to create the class denition to handle
all the logic our list page will use. This is where we will dene all the functions that our template references,
Again, we are going to set up the code for the entire class rst and then we will discuss it bit by bit.
231
import {SettingsPage} from '../settings/settings';
import {Data} from '../../providers/data/data';
import {FORM_DIRECTIVES, Control} from '@angular/common';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
@Component({
templateUrl: 'build/pages/home/home.html',
providers: [Data]
})
export class HomePage {
settings: any;
loading: boolean = false;
posts: any = [];
subreddit: string = 'gifs';
page: number = 1;
perPage: number = 15;
after: string;
stopIndex: number;
sort: string = 'hot'
moreCount: number = 0;
subredditValue: string;
232
fetchData(): void {
console.log("TODO: Implement fetchData()");
}
loadSettings(): void {
console.log("TODO: Implement loadSettings()");
}
showComments(post): void {
console.log("TODO: Implement showComments()");
}
openSettings(): void {
console.log("TODO: Implement openSettings()");
}
changeSubreddit(): void {
console.log("TODO: Implement changeSubreddit()");
}
loadMore(): void {
console.log("TODO: Implement loadMore()");
}
233
Theres obviously still quite a bit of code that needs to be added to this class, but even the basic setup of
the class weve just added is looking pretty complicated. So lets start talking through it.
Theres a bunch of imports here because we are setting up everything we will use later, but this is still
probably quite a few more imports than you will likely use in most of your classes.
The rst couple we are importing from the Ionic library - ModalController and AlertController - are all
pretty standard. Weve discussed these before, but to recap: ModalController allows us to create a
modal page that can be displayed on top of the current page, and AlertController allows us to create on
We also import Http to allow us to send requests to the Reddit API, the SettingsPage we generated earlier
(but are yet to complete) and our Data provider that we also generated earlier but havent completed.
The rest of the imports relate to the Observable functionality we will be adding shortly, so I will leave most of
the discussion around that until then. However, FORM_DIRECTIVES imports all the form related directives
from the Angular library, and Control will allow us to create a Control for inputs (which will supply our
Observable). All of the rxjs imports are from the RxJS library - somewhat annoyingly, you have to import
any operator you want to use with an Observable, so were doing that here.
In the next section of code we have our constructor function. The constructor is an important part of the
234
class because it is the rst bit of code that will be executed when the component is created. It allows us
to inject and set up references to any components or services we are using and its also a good spot to
make any function calls that you need to execute right away.
settings: any;
loading: boolean = false;
posts: any = [];
subreddit: string = 'gifs';
page: number = 1;
perPage: number = 15;
after: string;
stopIndex: number;
sort: string = 'hot'
moreCount: number = 0;
subredditValue: string;
In the constructor we are setting up references for our injected services (http, dataService and nav) and
outside of the constructor we set up a bunch more member variables which will be used to store various
The current page (i.e. how many times the user has clicked Load More)
235
A reference to the last post retrieved from Reddit (so we know where to start for the next page)
The app will keep trying to load more posts from reddit until it has enough for a full page of GIFs,
moreCount is used to tell it when it should stop trying to load more (i.e. if not enough GIFs are found
A variable that references the Control we will create, which supplies our Observable
We also make a call to the loadSettings() function which will load the users settings from memory if
fetchData(): void {
console.log("TODO: Implement fetchData()");
}
loadSettings(): void {
console.log("TODO: Implement loadSettings()");
}
showComments(post): void {
console.log("TODO: Implement showComments()");
}
openSettings(): void {
console.log("TODO: Implement openSettings()");
}
236
changeSubreddit(): void {
console.log("TODO: Implement changeSubreddit()");
}
loadMore(): void {
console.log("TODO: Implement loadMore()");
}
The rest is just a bunch of functions. These functions will either be called from the template, or from
somewhere within the class itself (the constructor, or another function). Clearly these are all blank now but
Were going to get a bit fancy now and make use of Observables. Weve discussed what exactly an
Observable is in the basics section of this book so if you cant quite remember Id recommend going back
and taking a look at the Fetching Data, Observables and Promises section.
Most of the time when using Observables you will simply be subscribing to some Observable that is re-
turning values for you, like when using the Http service to fetch some data (which we will be doing for this
application in the next section). So you rarely have to create the Observables yourself. But, theres a lot
We will be using the Control service we imported before to create a Control that will supply our Observ-
able. Controls work very similarly to two way data binding with [(ngModel)] in that it ties a variable in
the class denition, to an input eld in the template. So we are going to have to make some modications
rst.
237
[formControl]="subredditControl"></ion-searchbar>
What weve done here is add [formControl] which is used by the Control we will be creating. Next
well have to make some changes in the constructor for our class.
settings: any;
loading: boolean = false;
posts: any = [];
subreddit: string = 'gifs';
page: number = 1;
perPage: number = 15;
after: string;
stopIndex: number;
sort: string = 'hot'
moreCount: number = 0;
subredditValue: string;
subredditControl: Control;
this.subredditControl.valueChanges.debounceTime(1500)
.distinctUntilChanged().subscribe(subreddit => {
238
this.changeSubreddit();
}
});
this.loadSettings();
First we create a new Control, and then we subscribe to the valueChanges Observable it supplies. If
youve read the section on Observables in the basics section of this book then most of this shouldnt look
too strange. We subscribe to the observable so that every time it emits a value we run some code. The
Basically, we can chain together as many of these operators as we want and they all do dierent things.
this.subredditControl.valueChanges.subscribe
Then we would run the code we have supplied above every time the value of the subredditControl input
this.subredditControl.valueChanges.debounceTime(1500).subscribe
We would only run the code when the input has changed, and when there hasnt been another change
within 1.5 seconds. This prevents us from sending out too many pointless requests to the API. If the user
was typing chemicalreactiongifs for example, the the code would be triggered for c, then ch, then che,
then chem and so on until the full string has been typed. Not only will this send o a bunch of useless
queries to the API, its going to be a bad experience for the user as well as the list is constantly ickering
and changing as they type. By adding debouncing, the code will only run once the full string has been
typed in (assuming the user doesnt take more than 1.5 seconds between typing each letter).
239
this.subredditControl.valueChanges.debounceTime(1500)
.distinctUntilChanged().subscribe
This will only run the code if the value is dierent to the last time it ran. So if a user typed gifs, hit the
backspace key to make it gif but then retyped the s to make it gifs again nothing would happen. We
dont need to reload the data for gifs because we are already there.
The end result is that the code will only trigger when the value has changed, the user is not currently typing
and the input value is dierent to what it was last time. The code that we are triggering simply checks if a
non empty value was supplied, and then changes the subreddit by setting the this.subreddit member
This will probably be one of the most confusing parts of this application, especially if youre completely
new to Observables. As you can tell this has allowed us to create some pretty useful functionality really
easily, but we could have just as easily used the normal ngModel approach and just used a button for
Summary
Weve got o to a really good start in this lesson, and were well on the way to getting some cool stu
happening. We have a really nice basic structure which will allow us to easily build on functionality in the
ionic serve
240
Pretty ugly right? And youre even going to get some errors in the console too. Trust me, itll all come
together in the end! In the next lesson were going to look at pulling in some real data from reddit.
241
Lesson 4: The Reddit API and HTML5 Video
One of the most interesting things about Giflist is that it wont ever contain any actual GIF les at all. The
GIF le format can be quite large and slow to load, which is especially a problem on mobile when people
So what we are going to do is make sure we only ever pull in GIFs that provide a .webm or .gifv format.
Were going to display those videos using the HTML5 <video> tag. Its important to remember when
building HTML5 mobile applications, we can use any HTML5 feature within our applications. A great
example of this is Geolocation - we can use the native API to access the devices GPS, but we can also
just use the plain old HTML5 Geolocation API available to any website. Basically, anything that you can
do on a website you can do in your mobile application (but obviously we can do a lot more as well, given
Before we get into building this functionality, lets talk about a few important issues with HTML5 video.
Using a framework like Ionic means a lot of the platform dierences are handled pretty seamlessly for us,
but theres still going to be problems with platform dierences that you will run into when creating cross
platform applications.
First, theres a few things you should know about the <video> element:
It has a poster attribute that can be used to display an image before the video is loaded
Whether controls are displayed, whether it autoplays video and whether it plays inline can all be
controlled
One of the most important things to know though is that the behaviour of the <video> element is dierent
242
depending on which platform the application is running on.
On iOS videos play in fullscreen by default, but can be forced to play inline by using the webkit-
playsinline attribute. However, this is not universal across all iOS devices. On smaller devices,
fullscreen will still be forced even if you specify the webkit-playsinline attribute (basically, its not
Given those behavioural dierences, we then need to gure out how we want to approach the problem.
1. Accept the default behaviour and maintain the same code for both platforms
2. Check for the platform we are running on and run dierent code to achieve the desired behaviour.
Personally, I think playing the videos inline achieves a more desirable result. But since its not possible to
achieve that on small iOS devices, I decided to just go with the default behaviour. This means that there will
be a dierence in behaviour between iOS and Android, but I think both solutions are perfectly acceptable
So, lets get to work. Were going to nish implementing a few more of the functions we dened in home.ts
Were going to start o with the most interesting and complicated function, which is pretty much the core
feature of the entire application: fetchData(). We will add the code rst and then walk through it
fetchData(): void {
//Build the URL that will be used to access the API based on the users
current preferences
243
let url = 'https://www.reddit.com/r/' + this.subreddit + '/' + this.sort
+ '/.json?limit='+ this.perPage;
//If we aren't on the first page, we need to add the after parameter so
that we only get new results
//this parameter basically says "give me the posts that come AFTER this
post"
if(this.after){
url += '&after=' + this.after;
}
//We are now currently fetching data, so set the loading variable to true
this.loading = true;
//Loop through all NEW posts that have been added. We are looping
through
//in reverse since we are removing some items.
for(let i = this.posts.length - 1; i >= stopIndex; i--){
244
post.showLoader = false;
/*
* Remove all posts that are not in the .gifv or .webm format and
convert the ones that
* are to .mp4 files.
*/
if(post.data.url.indexOf('.gifv') > -1 ||
post.data.url.indexOf('.webm') > -1){
this.posts[i].data.url = post.data.url.replace('.gifv', '.mp4');
this.posts[i].data.url = post.data.url.replace('.webm', '.mp4');
245
this.posts[i].data.snapshot = "";
}
}
else {
this.posts.splice(i, 1);
}
}
//We are done loading now so change the loading variable back
this.loading = false;
alert.present();
this.moreCount = 0;
}
else {
this.after = data.data.children[data.data.children.length -
1].data.name;
246
if(this.posts.length < this.perPage * this.page){
this.fetchData();
this.moreCount++;
}
else {
this.moreCount = 0;
}
}
}, (err) => {
//Fail silently, in this case the loading spinner will just continue to
display
console.log("subreddit doesn't exist!");
});
Thats a pretty big function. Ive added some inline comments to help clear things up a little bit, but lets
First we build the URL that we are going to use to fetch data from the Reddit API. We use whatever
subreddit, sort and perPage values the user currently has set. You can modify this URL with any values
for these that you like and it will spit out the relevant JSON. If we have an after value set then we supply
that as well. This is how the Reddit API does pagination, if the user has hit the Load More button three
times then we only want to return the posts for the third page, e.g results 10-15 if the user has a page size
of 5. With the reddit API you can supply a posts name as the after parameter and it will return only the
Then we use that URL to make a Http request and subscribe to the Observable returned after we map the
response to a JSON object, this converts the JSON string returned from the Reddit API into an object that
247
After that, we want to loop through all of the posts we loaded to perform some magic. We dont want
to loop through every single post we have stored on our this.posts variable though because a lot of
these have already been processed, we just want to loop through the new posts loaded - so we create
a stopIndex that is the length of the current this.posts array, and then we add the new posts to it.
When we are looping through the newly loaded posts we are doing a few things:
Removing any posts that are not in the .gifv or .webm formats
Creating a new showLoader property on the posts that will be used to toggle a loading animation
later
Once we have nished the loop we should have an array of posts in the format we need, ready to be
displayed in the list. Theres one more important issue we need to take care of though. If we are loading
in say 10 posts per page from the Reddit API, but then only 3 of those are suitable GIFS, our page size is
only going to be 3. This might not y too well with the user.
To solve this issue, we will recursively fetch more data by calling the fetchData() function from within
the fetchData() function. This will keep adding more and more posts to the posts array until we have
our full 10 (or whatever the page size currently is) GIFs. We need to set a limit though, because this function
could just run innitely, so if we still dont have enough GIFs after 20 tries then we give up and display an
Also notice that we have an error handler for the http.get request as well. If a successful request is
made, all of the code we just discussed will run, but if not (i.e. if the subreddit the user is trying to retrieve
results in a 404) then the error will be passed into the error handler and that will run instead.
Now that we have our list of GIFs loading into the application, we can display real data in our list. But rst,
248
<ion-header>
<ion-navbar secondary>
<ion-title>
<ion-searchbar primary placeholder="enter subreddit name..."
hideCancelButton [(ngModel)]="subredditValue"
[formControl]="subredditControl"></ion-searchbar>
</ion-title>
<ion-buttons end>
<button (click)="openSettings()"><ion-icon
name="settings"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content class="home">
<ion-list>
<div *ngFor="let post of posts">
<ion-item (click)="playVideo($event, post)" no-lines
style="background-color: #000;">
<img src="images/loader.gif" *ngIf="post.showLoader" style="width:
25px; position: absolute; left: 85%;"
[style.top]="post.loaderOffset" />
<video loop [src]="post.data.url" [poster]="post.data.snapshot">
</video>
<ion-list-header (click)="showComments(post)" style="text-align:
left;">
249
{{post.data.title}}
</ion-list-header>
</ion-item>
</div>
<ion-item *ngIf="loading" no-lines style="text-align: center;">
<img src="images/loader.gif" style="width: 50px" />
</ion-item>
</ion-list>
</ion-content>
As you can see above we are now using the URL of the post as the source of the video element, as well
as suplying the preview snapshot as the poster and the title in the header. Weve also added a few more
Weve added a click event that will call the playVideo function when the video is tapped, passing along
the click event (which we will make use of for reasons Ill explain later) and a reference to the post that was
clicked. We have a seperate click event for the <ion-list-header> that shows the GIFs title which will
We also add the loader GIF to the item in the list. When a user taps on a video it will still need some time
to load in, so we add the loading animation so that the user knows something is happening. Without it,
the user will likely just think the application isnt working.
Weve added a few standard style attributes around the place, but the one on the loader GIF looks a little
funny:
[style.top]="post.loaderOffset"
This code assigns the result of the post.loaderOffset expression directly to the style.top property,
250
which is used to dene how far away from the top the GIF should display. Youll see how we use this
Lets also add a bit of code to the loadSettings function so that the fetchData function gets called
when we run the application (since loadSettings is called from the constructor). This is just so we can
loadSettings(): void {
this.fetchData();
}
If you load up your application in the browser now, you should see something like this:
251
Its still not looking great, but its looking a lot better and we have some cool GIFs displaying in there now
252
Playing our GIFs (videos)
Our GIFs are sitting in our list now ready to go, we just need to get them movin and shakin. To do that,
} else {
video.pause();
}
253
}
If the user taps on the video then we want to play the video (or pause it if it is already playing). We use the
event to set the correct position for the loading gif to display whilst the video is loading. Once the video
Remember when we were setting up this application, we installed the In App Browser plugin? Well, we are
going to make use of that now. When the user clicks on the title of the video we want to launch a browser
showComments(post): void {
InAppBrowser.open('http://reddit.com' + post.data.permalink, '_system',
'location=yes');
}
Compared to the others, this is a pretty simple function. We simply grab the link from the posts data and
call the open method on InAppBrowser to launch the browser window. Its important to remember that
when running through the browser, this plugin will not be available. So if you try to launch the comments
while testing through the browser you will just get an error.
One important function we havent go to yet is the loadMore function, this is what is called when the
user hits the Load More button. What we want to do when this happens is load in the next page of GIFs.
254
Given the setup of our fetchData function, this is actually really easy to do.
loadMore(): void {
this.page++;
this.fetchData();
}
All we do is increase the page number and then call fetchData again, simple!
Changing Subreddits
The nal function we want to nish up for now (there is still a bit more to do later) is the changeSubreddit
function.
changeSubreddit(): void {
this.page = 1;
this.posts = [];
this.after = null;
this.fetchData();
We really got the hard stu out of the way early, as this is another pretty simple function. When the subreddit
changes we need to reset everything. If we were up to the 5th page of chemicalreactiongifs then we dont
want to load the 5th page when we switch to perfectloops. All we have to do here is reset the page, clear
the posts data, and clear the after value, then we just call the fetchData function again. Weve already
set the subreddit elsewhere, so calling the fetchData function will pull in data from whatever the current
subreddit is now.
255
Summary
This lesson was really the bulk of the whole application, theres still a bit more to do but you can take a
breather if youve made it this far. You should now have an app that pretty much works - itll still be pretty
ugly and doesnt have the ability to save settings, but it works. Well be getting to both of those things in
256
Lesson 5: Settings
We have everything working pretty nicely now - theres GIFs coming in from reddit, ltered to only include
the le format we want and we have them displaying nicely using <video> elements.
We currently have the gifs subreddit set as the default, which is certainly a good choice for this app, and
the user can change this if they like. When they leave the application and come back though, it is going
to get set back to the default gifs subreddit. This would get pretty annoying if they arent interested in
So what we are going to do is create a Settings page, where the user can set the following:
Default subreddit
Sort order
The cool thing about implementing a feature like this is that it also gives us a chance to cover how to
store data permanently so that the application will remember the settings upon the user returning to the
application.
To open the settings page we are going to use a Modal. Opening a page as a Modal is a bit dierent to
just opening a page normally in that it is completely separate to the navigation ow of the app. A Modal is
basically a single page that can be opened over the top of the current content and then closed (Im sure
youre familiar with other modal windows around the web like Facebooks photo viewer).
Any page can be opened as a Modal, so when we create our Settings page we are going to do it just like
we would any other page. Lets start o by dening the template for our Settings page.
<ion-header>
257
<ion-toolbar secondary>
<ion-title>Settings</ion-title>
<ion-buttons end>
<button (click)="close()"><ion-icon name="close"></ion-icon></button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-card>
<ion-card-header>
About
</ion-card-header>
<ion-card-content>
<p><strong>Giflist</strong> is a client for <strong>reddit</strong>
that will endlessly <strong>stream GIFs</strong> from <strong>any
subreddit that predominantly contains gifs</strong>. By default
it uses the popular <strong>gifs</strong> subreddit, but you can
change this to whatever you like, e.g: perfectloops, me_irl,
chemicalreactiongifs.</p>
<br />
<p>To play a GIF, just tap it. To view the original submission for
any GIF, just tap the title section and it will open in reddit.
You can configure some settings for the application below.
Enjoy!</p>
</ion-card-content>
</ion-card>
258
<h3>Subreddit</h3>
<h3>Sort</h3>
<h3>Per Page</h3>
259
15
</ion-segment-button>
</ion-segment>
</ion-content>
Theres nothing too crazy going on in this template. We dene our navbar as usual, and we add a button
to it which will call a close method we will dene shortly to dismiss the modal window.
We add an <ion-card> to display some information about the app to the user, and then we dene a
few dierent inputs. We use a simple text input for setting the subreddit, but we use segment compo-
nents to control the sort and perPage values. All of these inputs have two way data binding set up with
[(ngModel)] so we will easily be able to access these values in our class denition, which we will create
now.
@Component({
templateUrl: 'build/pages/settings/settings.html',
})
export class SettingsPage {
perPage: number;
sort: string;
subreddit: string;
260
constructor(public view: ViewController, public navParams: NavParams) {
this.perPage = this.navParams.get('perPage');
this.sort = this.navParams.get('sort');
this.subreddit = this.navParams.get('subreddit');
}
save(): void {
let settings = {
perPage: this.perPage,
sort: this.sort,
subreddit: this.subreddit
};
this.view.dismiss(settings);
}
close(): void {
this.view.dismiss();
}
}
The rst strange thing you might notice here is that we are using navParams to grab some values. We
will pass these values into the Settings page from the Home page when we launch the Modal because we
want to pre set the values on the settings page to whatever they are currently.
Another strange thing about this Page is that we are importing the ViewController - we need this in order
to allow us to dismiss this page from within its own class when it is launched as a Modal. In this case we
want to close the page both when the user hits the save button, and when the user hits the close button.
If the user hits the close button, then we wont save the settings they supplied.
261
We are going to be working on saving the settings in the next portion of this lesson, but you might be
wondering how exactly that is going to happen - shouldnt the save function here actually do something
with the data? With the way Modals work, we can return data from the Page that was launched as a Modal
when it is dismissed, so in this case when we call the dismiss function on the view we will be sending
the settings values back to the Home page, which will be the page that launched the Modal - this is where
Now that we have our Settings page dened, we just need to launch it as a Modal. Weve already set up a
button in the header to open the settings page, so all we need to do is nish dening the openSettings
function.
openSettings(): void {
settingsModal.onDidDismiss(settings => {
if(settings){
this.perPage = settings.perPage;
this.sort = settings.sort;
this.subreddit = settings.subreddit;
//this.dataService.save(settings);
262
this.changeSubreddit();
}
});
settingsModal.present();
As you can see above, we pass the Settings page we created into the Modal we are creating, and we also
pass through the data we are grabbing in the Settings page here.
We set up an onDidDismiss listener that will detect when the Modal is closed and pass in any data that
was sent back when the Modal was dismissed. Since we are only supplying data through the dismiss
function when the user hits save, this is the only time this code will run. If there are settings passed back
then we update the current values with the new values, and we also use our dataService to save the
values to storage (we still need to dene this service though). Since we have new settings values now we
also call changeSubreddit to reset everything and load new GIFs based on the new settings.
NOTE: Since we havent implemented the data service yet, we comment out the call to it for now.
Finally we call the present method to display the Modal to the user.
Saving Data
The nal piece of the puzzle is to create our Data service to handle saving our settings to memory, and
To save the data, we are going to use the SqlStorage service provided by Ionic. This is one of my favourite
additions in Ionic 2, because it makes the complicated process of storage extremely easy.
Basically, its very easy to store some data locally to retrieve later. Local storage is supported universally
263
by most browsers, and you can easily set a bit of data like this:
localStorage.setItem('myKey', 'myValue');
To deal with this, there is a bunch of dierent options available. One popular method though, and the one
we will be using, is to use the SQLite plugin. This provides access to a native SQLite database to store
your data, which removes the storage restrictions and cant be wiped from memory (the data sits outside
of the browser in this case). The reason we are using the SqlStorage service from Ionic is that it provides
us a nice interface to use this plugin, and if the plugin is not available it will fall back automatically to use
other local storage methods. Weve already installed the SQLite plugin in the rst lesson, so all we need
@Injectable()
export class Data {
storage: Storage;
constructor() {
this.storage = new Storage(SqlStorage, {name:'giflist-settings'});
}
getData(): Promise<any> {
return this.storage.get('settings');
264
}
save(data): void {
let newData = JSON.stringify(data);
this.storage.set('settings', newData);
}
First, in our constructor we set up a reference to our storage service. We create an instance of the generic
Storage service and then use SqlStorage to set it up with a database name of settings. By using Sql-
Storage the Storage service will make use of the SQLite plugin when running on a device, alternatively
we couldve used LocalStorage to just use the browser storage. Now we will be able to interact with this
Then we just have two functions, one for getting data and one for saving data. The getData() function
will return all of the data we have stored on settings in storage, and save() will update settings in
storage with a new set of data. Weve already set up the call to save() when the Settings Modal is
dismissed, now we just need to dene the loadSettings function that will handle loading previously
loadSettings(): void {
this.dataService.getData().then((settings) => {
if(typeof(settings) != "undefined"){
this.settings = JSON.parse(settings);
if(this.settings.length != 0){
265
this.sort = this.settings.sort;
this.perPage = this.settings.perPage;
this.subreddit = this.settings.subreddit;
}
this.changeSubreddit();
});
Here we are simply calling the getData function we dened in our data service, and when that returns
our settings data we parse the JSON into an object and read the values into our member variables. Then
we call the changeSubreddit function to trigger posts from Reddit to be fetched with the new settings.
Now that we have implemented the data service, we can uncomment the call to the data service we made
earlier.
openSettings(): void {
settingsModal.onDidDismiss(settings => {
266
if(settings){
this.perPage = settings.perPage;
this.sort = settings.sort;
this.subreddit = settings.subreddit;
this.dataService.save(settings);
this.changeSubreddit();
}
});
settingsModal.present();
Summary
Now that weve nished up our settings page and data service, the functionality of the application is
So we have one lesson remaining where we are going to add in a few styles to make everything look a lot
nicer.
267
Lesson 6: Styling
Were almost there, just a little bit of styling to go and well have a completed application. Before we dene
our styles we are going to add a few classes to our template les.
<ion-header>
<ion-navbar secondary>
<ion-title>
<ion-searchbar primary placeholder="enter subreddit name..."
hideCancelButton [(ngModel)]="subredditValue"
[ngFormControl]="subredditControl"></ion-searchbar>
</ion-title>
<ion-buttons end>
<button (click)="openSettings()"><ion-icon
name="settings"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content class="home">
<ion-list>
<div *ngFor="let post of posts">
<ion-item (click)="playVideo($event, post)" no-lines
style="background-color: #000;">
<img src="images/loader.gif" *ngIf="post.showLoader" style="width:
268
25px; position: absolute; left: 85%;"
[style.top]="post.loaderOffset" />
<video loop [src]="post.data.url" [poster]="post.data.snapshot">
</video>
</ion-item>
<ion-list-header (click)="showComments(post)" class="gif-title"
style="text-align: left;">
{{post.data.title}}
</ion-list-header>
</div>
<ion-item *ngIf="loading" no-lines style="text-align: center;">
<img src="images/loader.gif" style="width: 50px" />
</ion-item>
</ion-list>
</ion-content>
<ion-header>
<ion-toolbar secondary>
<ion-title>Settings</ion-title>
<ion-buttons end>
<button (click)="close()"><ion-icon name="close"></ion-icon></button>
</ion-buttons>
</ion-toolbar>
269
</ion-header>
<ion-card>
<ion-card-header>
About
</ion-card-header>
<ion-card-content>
<p><strong>Giflist</strong> is a client for <strong>reddit</strong>
that will endlessly <strong>stream GIFs</strong> from <strong>any
subreddit that predominantly contains gifs</strong>. By default
it uses the popular <strong>gifs</strong> subreddit, but you can
change this to whatever you like, e.g: perfectloops, me_irl,
chemicalreactiongifs.</p>
<br />
<p>To play a GIF, just tap it. To view the original submission for
any GIF, just tap the title section and it will open in reddit.
You can configure some settings for the application below.
Enjoy!</p>
</ion-card-content>
</ion-card>
<h3>Subreddit</h3>
<h3>Sort</h3>
270
<ion-segment secondary [(ngModel)]="sort">
<ion-segment-button value="hot">
Hot
</ion-segment-button>
<ion-segment-button value="top">
Top
</ion-segment-button>
<ion-segment-button value="new">
New
</ion-segment-button>
</ion-segment>
<h3>Per Page</h3>
</ion-content>
271
If youve ready the basics section then youll know that theres quite a few ways we can dene styles, if
you skipped over theming in the basics section then I highly recommend going back to read it now.
Were going to be using just about every method available to dene our styles, we will be adding some
to the component specic les, some to platform specic les, some to the generic le and some to the
variables le. Well start o with the styling that is specic to the Home component.
.gif-title {
background-color: #fff;
}
ion-label {
margin: 0px;
}
ion-list {
margin: 0px !important;
}
ion-content img {
width: 100%;
height: auto;
}
272
video {
max-width: 100%;
height: auto;
background-color: #000;
}
.load-more-button {
width: 100%;
margin: 0px;
}
These are all pretty basic styles, for the most part we are just changing some colours and removing padding
and margins. The most important styles here are the ion-item-content and video styles which serve
to make sure the video scales and sits nicely in the list. Ideally we want the video to take up the full width
of the item but in the case of portrait GIFs this isnt always possible, so in that case we would center the
Now we are going to dene just one style in the iOS specic style sheet. Our searchbar in the header is
a bit small since weve added it to the <ion-title> section, so we are going to make it a little bigger.
The searchbar already displays ne on Android though, so we dont want the style to apply there - which
ion-title {
padding: 0px 35px 1px 35px;
}
Next we are going to add a background colour to the body tag, which we will add to the generic .scss
le.
273
body {
background-color: #e74c3c;
}
Finally, we will dene some shared variables to be used in the application using the app.variables.scss
le.
$colors: (
primary: #ecf0f1,
secondary: #e74c3c,
danger: #f53d3d,
light: #e74c3c,
dark: #222222
);
$searchbar-ios-background-color: #e74c3c;
$searchbar-ios-input-background-color: #c94234;
$button-ios-border-radius: 0px;
Weve changed a few of the colours up here, made some modications to the search bar, and set the iOS
button border radius so that our Load More button is square instead of rounded.
If you load up the application should now look something like this:
274
Much better! If you take a look at the Android version (you can do this by setting the Emulator in Chrome
Dev Tools to emulate a Samsung or other Android device, or just use the ionic serve -l command)
275
(ignore the white bar on the right, this isnt present on actual devices)
It certainly looks a little dierent, but it still looks good. One of the best things about Ionic is that it au-
tomatically conforms to design conventions of the platform it is running on as best it can. So in general,
the less you mess with this the better. If you mainly use an iOS device, or if you mainly use an Android
device, the styling of the other platform may look a little weird to you, but it wont look weird to users of
that platform. Unless you have good reason to, you shouldnt modify the styling of one platform to look
276
Summary
This was a pretty short lesson compared to the others, but it shows how easy it can be with a little eort
277
Conclusion
Congratulations on making it through the Giist tutorial. Weve learned a lot through developing this appli-
Theres always room to take things further though, especially when youre trying to learn something. Fol-
lowing tutorials is great, but its even better when you gure something out for yourself. Hopefully you
have enough background knowledge now to start trying to extend the functionality of the application by
Create a Random button that will take you to a random subreddit from a predened array [EASY]
Allow users to save their favourite GIFs into its own list that can be viewed by typing favorites in
Remember, the Ionic 2 documentation is your best friend when trying to gure things out.
What next?
You have a completed application now, but thats not the end of the story. You also need to get it running
on a real device and submitted to app stores, which is no easy task. The nal sections in this book will
walk through how to take what you have done here, and get it onto the app stores so make sure to give
that a read.
278
Chapter 5
Snapaday
279
Lesson 1: Introduction
Snapaday is based around everybodys favourite native plugin, the camera. I dont actually have any data
to back that claim up, but the camera is denitely one of the coolest integrations. Its also a plugin people
often struggle with, its one of those things that looks easy on the surface but theres a few tricks to it.
In fact, Snapaday is pretty much the native plugin app in this book - as well as covering how to use the
camera well also be integrating local notications and social sharing (weve got to get those seles on
Facebook right?).
About Snapaday
Snapaday was actually the Ionic 1 example used in my Mobile Development for Web Developers course,
so I gured when transitioning from Ionic 1 to Ionic 2 it would be a great example to use - especially for
The general idea is that the user takes a photo with the application every day, and then they can play back
their photos in a fast slideshow to see how theyve changed over time (ever see this video?). To be more
280
How to use local notications
281
###Lesson Structure
282
1. Getting Ready
2. The Layout
7. Styling
Ready?
Now that you know what youre in for, lets get to building it!
In this lesson we are going to prepare our application for the journey ahead. We are going to of course
generate the application, and we are also going to set up all of the components and Cordova plugins we
will need. At the end of this rst lesson we should have a nice skeleton application set up with everything
A good rule of thumb before starting any new application is to make sure you have the latest version of
Ionic and Cordova, so if you havent done it recently then make sure to run:
or
283
sudo npm install -g ionic@beta cordova
We will be using the blank starter template for this application which, as the name implies, is basically
an empty Ionic project. It comes with one page built in called home which we will repurpose in the next
lesson.
Make the new project your current working directory by running the following command:
cd snapaday
Your project should now be generated - now you can open up the project folder in your favourite editor.
You can take a look at how your application looks by running the following command:
ionic serve
284
###Create the Required Components
The structure of this application is pretty simple, so all were going to have is 2 pages: the home page
285
which will contain the photos and a page for the slideshow. Weve already got a home component which
we will use as our list page, so we only need to create the Slideshow page.
Also remember that any time we generate a new component, we need to import its .scss le into our
app.core.scss le. The Ionic CLI automatically generates pages for us, but it doesnt automatically import
@import "../pages/home/home";
@import "../pages/slideshow/slideshow";
As well as our 2 pages, we are also going to create a data service to handle storing and retrieving the photos,
a data model to represent our photos and since well be using quite a lot of alerts in the application we will
Well also be creating our own custom Pipe in this application to handle converting the dates our photos
were taken into something more user friendly like 5 days ago. Lets create that now as well.
286
> Run the following command to generate the Days Ago pipe:
IMPORTANT: For some reason this pipe currently generates as DaysAgo.ts rather than days-ago.ts, so
Before you can build for certain platforms, you need to add them to your project. This is something we
will be doing way later on in this course, but you may as well just set them up now.
> Run the following command to add the iOS platform to your application
> Run the following command to add the Android platform to your application
This application will use a few dierent Cordova plugins. Remember, Cordova plugins can only be used
when running on a real device. Ill explain each plugin as we run through the commands for adding them.
This plugin gives you access to native storage with an SQLite database. We are adding it to this application
because the Ionic local storage service can make use of this plugin to provide more stable data storage.
> Run the following command to add the local notication plugin:
287
This plugin is what will allow us to create local notications for the application. Unlike push notications,
local notications are handled completely on the users device and dont require any external services.
This plugin adds the ability to access the users camera and return photos from it. As well as providing
access to the camera, it also allows for the retrieval of photos from the users photo library.
The File plugin allows us to interact with the le system on the device, which we will be using to move the
> Run the following command to add the Social Sharing plugin:
The social sharing plugin is generic and allows users to share to a whole bunch of dierent platforms (like
social media, but also email, SMS and more) or you can just trigger a share to one specic platform.
> Run the following command to add the Status Bar plugin:
We will be adding this plugin to all projects to give us control over the status bar in our application (the bar
at the top of the devices screen that contains the time, battery information and so on).
> Run the following command to add the Splash Screen plugin:
This plugin allows us to control the splash screen (the fullscreen graphic that briey displays when you
open an app)
288
> Run the following command to add the Whitelist plugin:
This plugin is required for all applications, and helps to dene what resources should be allowed to be
loaded in your application. Without it, resources you try to load will fail.
As well as adding the plugin, you also need to dene a Content Security Policy in your index.html
le. We will be adding a very permissive policy which will essentially allow us to load any resources.
Depending on your application, you may look into providing a more strict policy, but an open policy is
This is another plugin that we will add to every application, but you may decide you want to leave it out.
By adding this plugin, when you build for Android Crosswalk will be used. Android has a lot of issues,
especially with older devices, because there is so many dierent software versions out there and dierent
versions have dierent browsers (remember, since we are building HTML5 applications it is actually a
browser engine powering and running our application). What Crosswalk does is bundle a modern browser
into your application so no matter what device you are running on your app will be powered by the same
The only real downside to this is that it increases the size of your application by a signicant amount. In
general, I think its worth it and Id recommend you include it but you may leave it out if you like. For more
289
Set up Images
When building this application we are going to be making use of a few images. Ive included these in your
download pack but you will need to set them up in the application you generate.
> Copy the images folder in the download pack for this application from www/images to your own
www folder
Summary
Thats it! Were all set up and ready to go, now we can start working on the interesting stu.
290
Lesson 3: The Layout
I nd creating a basic layout is usually a good rst step when building an application - it even serves as a
sort of wireframing exercise, to help solidify the requirements of the application. Theres nothing too crazy
we need to do for the layout of this application, but there is one little tricky (and I think clever) user interface
We have two pages in this application, the home page and the slideshow page, and we will create both
of them in this lesson. The slideshow page is very simple though, so most of this will be about the home
page.
The home page in this application will contain a list of all of the photos that the user has taken with the
ability to delete them, an option to take a new photo (but only if they have not already taken one that day),
and an option to play a slideshow of all of their photos. Heres what it will look like:
291
The trickiest part of this layout is that SMILE! image at the top, which is actually a button that will serve
as our take a photo option, but again, only if the user has not already taken a photo. If they have already
292
taken a photo then we dont want to display that image.
Lets start o by creating the basic layout, and then we will look at how we can implement that take photo
button.
<ion-header>
<ion-navbar danger>
<ion-title>
<img src = "images/logo.png" />
</ion-title>
<ion-buttons end>
<button (click)="playSlideshow()"><ion-icon
name="play"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item-sliding>
<ion-item>
<img src="http://placehold.it/100x100" />
<ion-badge item-right light>0 days ago</ion-badge>
</ion-item>
293
<ion-item-options>
<button light (click)="removePhoto(photo)"><ion-icon
name="trash"></ion-icon></button>
</ion-item-options>
</ion-item-sliding>
</ion-list>
</ion-content>
Lets break this template down bit by bit now. The rst section of this template is our navigation bar:
<ion-navbar danger>
<ion-title>
<img src = "images/logo.png" />
</ion-title>
<ion-buttons end>
<button (click)="playSlideshow()"><ion-icon
name="play"></ion-icon></button>
</ion-buttons>
</ion-navbar>
The <ion-navbar> allows us to add a header bar to the top of our application that can hold buttons,
titles and even integrates directly with Ionics navigation system to display a back button when necessary.
We add the danger attribute to the navbar to style it with our danger colour, which is dened in
Inside of this navbar we use <ion-title>, which is typically used to display a text title for the current
page, to display our logo. We also use <ion-buttons> to create a button in the navbar. By specifying
294
the end attribute, the buttons will be placed on the right side, but if we were to specify the start attribute
Finally we have the button itself inside of <ion-buttons>. This button uses a play icon and has a click
handler attached to it, which will call the playSlideshow() function in our home.js le (which we have
The next part of our template is the content section, which includes a list:
<ion-content>
<ion-list no-lines>
<ion-item-sliding>
<ion-item>
<img src="http://placehold.it/100x100" />
<ion-badge item-right light>0 days ago</ion-badge>
</ion-item>
<ion-item-options>
<button light (click)="removePhoto(photo)"><ion-icon
name="trash"></ion-icon></button>
</ion-item-options>
</ion-item-sliding>
</ion-list>
</ion-content>
Before we get to the list, notice that everything is wrapped inside of <ion-content> - this is what holds
the main content for the page and in most cases everything except the navbar will be inside of here.
295
Much like a list is created with plain HTML, e.g:
<ul>
<li></li>
<li></li>
<li></li>
</ul>
<ion-list>
<ion-item></ion-item>
<ion-item></ion-item>
<ion-item></ion-item>
</ion-list>
Of course, ours looks a little more complicated than that so lets talk through it. The rst thing out of
the ordinary we are doing is adding the no-lines attribute to the <ion-list>. Just like the danger
attribute we added to navbar, this attribute also styles our list except that it will cause the items in the list
The next bit gets a little trickier, as it is where we set up our sliding item. An <ion-sliding-item>, op-
posed to a normal <ion-item>, has two sets of content - the item itself, and then the <ion-item-options>
The rst block of code inside of <ion-sliding-item> is the normal <ion-item> denition. Inside of
this item will be one of the photos the user has taken (eventually we will turn this into a loop for all photos),
but for now we are just using a placeholder image. We also add a badge that will be aligned to the right
of the item to display how many days ago the photo was taken. Were also just using dummy data for the
Finally, we have the second block of code, <ion-item-options>, which simply allows us to dene what
content we want to display when the user slides the item. In this case we are just adding a Delete button
which will also pass in a reference to the photo it was called on. The photo reference it is trying to pass
296
now wont actually work (nor will the function because we havent created it yet), later on when we create
the loop for all photos we will discuss how to create this reference.
Now were going to add in that conditional photo button I mentioned earlier.
<ion-content>
<ion-list no-lines>
<ion-item-sliding>
<ion-item>
<img src="http://placehold.it/100x100" />
<ion-badge item-right light>0 days ago</ion-badge>
</ion-item>
<ion-item-options>
<button light (click)="removePhoto(photo)"><ion-icon
name="trash"></ion-icon></button>
</ion-item-options>
</ion-item-sliding>
</ion-list>
</ion-content>
297
Notice now that we have included another item in our list now above the sliding item, but rather than
using <ion-item> we are using <button> with an ion-item directive. Visually, these two methods
are exactly the same, but on mobile everything that is not a <button> or <a> that has a click handler will
have a slight tap delay. We dont want to have this delay so we use the button instead.
We also add the detail-none attribute here since buttons come with some styling by default that we
dont want in this case. We of course also have a click handler set up to trigger the takePhoto function
that we will create later, but the most important thing here is the *ngIf directive.
*ngIf is one of the conditional directives provided by Angular 2, which allows you to change your template
based on some data. In the case of *ngIf it will display the element it is attached to, and everything that
*ngIf="!photoTaken"
the extra button we added will only display if the photoTaken value is false, since we are essentially
saying display this button if the photoTaken variable is not true. Later on, we will create a photoTaken
variable in home.js which will keep track of whether a user has already taken a photo on the current day
or not.
ionic serve
298
Obviously theres a lot left to do but thats all we need to do for now for the home page, so lets move on
299
The Slideshow Page
As I mentioned, the slideshow page is going to be super simple. It is just going to be a modal that pops
up to display all of the users photos in a quick slideshow. To do this, all we need is a container for the
photos, a button to restart the slideshow and a button to close the page.
<ion-header>
<ion-toolbar danger>
<ion-title>Play</ion-title>
<ion-buttons start>
<button (click)="playPhotos()"><ion-icon
name="refresh"></ion-icon></button>
</ion-buttons>
<ion-buttons end>
<button (click)="closeModal()"><ion-icon
name="close"></ion-icon></button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<div class="image-container">
<img #imagePlayer id="imagePlayer" src = "" />
</div>
</ion-content>
You already know how the navbar and buttons work, so the only new thing here is the image player. All
300
were doing is adding an image element inside of the content area. Later on, we will add some code that
will cycle through all the photos the user has taken and change the src property to briey display each
photo. To do this we will need to grab a reference to the <img> element, so we create a local variable of
#imagePlayer that we will be able to grab from within our class denition later.
But for now, thats all we have to do - told you it was going to be simple.
Summary
We now have the templates set up for both of the pages in our application, so in the next lesson we can
start diving into some more interesting stu. In fact, it will probably be one of the most interesting lessons
because we will be learning how to integrate with the native Camera API and take photos!
301
Lesson 4: Taking Photos with the Camera
The primary goal of this lesson is to integrate the Camera to allow users to take photos, but we are going
Of course we need to trigger the camera to take a photo, but we also need to:
So this is going to be a pretty large lesson. In the getting started section for this application we set up Ionic
Native, which if you remember from the basics section is a service created by Ionic that wraps Cordova
Were going to be using Ionic Native in the lesson to use the Camera plugin, and the File API plugin.
Before we start adding the photo functionality, we are going to create a model to represent our photo
object. When we create a photo we want to store a path to the where the photo is located, and the date
the photo was taken. Creating a data model will allow us to easily create photo objects, by doing this:
let photo = {
image: 'path/to/image',
date: newDate
};
302
The dierence isnt that great, and its not at all necessary for our application to work, but it is a nice pattern
to follow, especially when your data starts getting more complex. For an example of a more complex data
model, and a more detailed explanation about why you might want to create one, check out the Quick
This model is pretty straightforward, we just pass in the image and the date and set it in the constructor.
Now to make the model available in our home.ts le we will need to import it.
@Component({
templateUrl: 'build/pages/home/home.html'
})
export class HomePage {
303
}
Surprise! Ive got one more little thing for us to do before we get into the fun stu. Were going to be
triggering a lot of dierent alerts in this application because theres many dierent places where errors can
occur that the users needs to be notied about (taking the photo, moving the photo) and we will also be
alert.present();
Which is quite a bit of code, and if we are going to be calling this multiple times in the same le its going
to get pretty messy. So were going to create another service, just like our data model, which will handle
creating the alert for us. Once we have created it, we will instead be able to create an alert by doing this:
304
import {Injectable} from '@angular/core';
import {AlertController} from 'ionic-angular';
@Injectable()
export class SimpleAlert {
Weve imported the AlertController service here so that we can create an alert, and then we have just
created a single function that takes in a title and a message, and returns an alert using those values. This
just creates the alert for us, we will still need to display it ourselves by presenting it wherever we need it.
To make this service available throughout our entire application we are going to declare the provider in our
root component.
305
> Modify app.ts to reect the following:
@Component({
template: '<ion-nav [root]="rootPage"></ion-nav>'
})
export class MyApp {
rootPage: any = HomePage;
constructor(platform: Platform) {
platform.ready().then(() => {
StatusBar.styleDefault();
});
}
}
Weve both imported the SimpleAlert service here and weve added it to the providers array in the decorator
(as well as the Data service as, which we have done so that we can preview the application before nishing
implementing the data service). However, to use it in home.ts we will still need to import it there as well
306
import {NavController} from 'ionic-angular';
import {PhotoModel} from '../../providers/photo-model/photo-model';
import {SimpleAlert} from '../../providers/simple-alert/simple-alert';
@Component({
templateUrl: 'build/pages/home/home.html'
})
export class HomePage {
Notice that we are injecting SimpleAlert through our constructor just like we do with the NavController, but
we didnt with the photo model. This is because we are manually creating a new instance of PhotoModel
every time we use it, but with SimpleAlert we are just calling the same function from one instance over and
over again.
Ok, weve got our photo model and alert service ready to use - we can nally focus on the fun stu which
is actually taking the photo. Well be using Ionic Native to use the Camera plugin, which means we will
307
Now we can access the functionality through the Camera object that will be available in our component.
Im going to walk you through how to use it, but remember that you can nd the documentation for all
Before we add the code for taking a photo, we are going to add a few variables to our constructor and,
so we dont need to constantly modify our imports, import all of the things we will require for the rest of
the application. Were also going to be adding quite a few functions to this le, and some functions will
reference other functions. So that we dont run into any trouble with undened functions, were going to
add all of them now but just leave them blank. We will then step through implementing each function in
@Component({
templateUrl: 'build/pages/home/home.html',
pipes: [DaysAgo]
})
export class HomePage {
308
loaded: boolean = false;
photoTaken: boolean = false;
photos: PhotoModel[] = [];
this.loadPhotos();
document.addEventListener('resume', () => {
309
}, false);
loadPhotos(): void {
takePhoto(): any {
createPhoto(photo): void {
removePhoto(photo): void {
playSlideshow(): void {
sharePhoto(image): void {
save(): void {
310
}
In the code above, weve added a member variable loaded to keep track of whether the photos have
been loaded from storage yet (which is something we will do in the next lesson), photoTaken to tell if a
photo has already been taken today and photos to hold all of our photo data. Since photos can only be
added when running on a real device, weve added some test data to the constructor which will set some
dummy data on this.photos so that you can test through the browser. If you need to do any testing
with photos present, just uncomment that section (make sure to remove it later though!).
We have injected and set up references to all the services we will require, including the data service we will
Weve also added a weird resume listener here. The resume event will re whenever a person sends
your application to the background and then resumes using it again later. For example they have your
application open, switch to Facebook, and then back to yours. The reason were doing this is because
theres a bit of an edge case with our photoTaken variable. Imagine someone has taken their photo for
the day, but when they close the application they dont fully close it, it just goes to the background. When
they come back the next day the logic we will run later in our loadPhotos function wont run to determine
if the photo was taken or not, because the application is just being resumed not reloaded. So whenever
the application is resumed, we check if the date of the last photo taken is equal to todays date, and then
Now were going to implement the takePhoto function which will handle taking the photo, so lets create
NOTE: We have added declare var cordova here so that TypeScript does not complain about not
knowing what it is when we are developing through the browser (since cordova will only be available when
311
takePhoto(): any {
if(!this.loaded || this.photoTaken){
return false;
}
if(!this.platform.is('cordova')){
console.log("You can only take photos on a device!");
return false;
}
let options = {
quality: 100,
destinationType: 1, //return a path to the image on the device
sourceType: 1, //use the camera to grab the image
encodingType: 0, //return the image in jpeg format
cameraDirection: 1, //front facing camera
saveToPhotoAlbum: true //save a copy to the users photo album as well
};
Camera.getPicture(options).then(
(imagePath) => {
console.log(imagePath);
},
(err) => {
let alert = this.simpleAlert.createAlert('Oops!', 'Something went
wrong.');
alert.present();
312
}
);
Before we execute the Camera code, we check a few conditions rst. If the variables we just recently
created indicate that the data has not nished loading yet, or that a photo has already been taken, then
the rest of the function will not nish executing. We also check to see if we are running on the cordova
platform, i.e. on a real device, and if we are not then it will also exit the function. Since you can only access
this plugin on a real device, it will just throw errors when it is not running on a device.
Then we set up some options to pass to the Camera plugin, these congure what exactly we want to do
and what we want returned. These values can congure things like whether to use the camera or the users
photo library, the format to return the image in, whether to use the back facing or front facing camera and
so on. Ive added comments next to each option in the code to indicate what they do.
Once weve created that options object, we call the getPicture method on the Camera object and pass
in the options. This will return a promise which, when resolved, will give us a path to the image on the
users device. Right now we are just logging out that value but were about to do a lot more with it. If an
error value is returned then we use our SimpleAlert service to display a message to the user.
The image that is taken with the camera, and the path that is returned, is in a temporary storage folder. So
the image will display for a little while but if we want to keep the photo around for a while its not going to
work, because eventually it will just be deleted. To solve this issue, we are going to have to take that photo
and move it somewhere else, and then we are going to store a reference to that location for the photo.
In order to use the File API plugin, were going to modify the takePhoto function to do the following:
313
Rename and move it to our own snapaday folder on the device (which will be in permanent storage)
Create a new photo object in the application based on the new location of the photo
So the takePhoto function is about to get a whole lot more complicated. Since there is so much code
in this function, I will add detailed comments directly into the code, but I will also discuss it afterward as
well.
> Modify the getPicture call in the takePhoto function to reect the following:
Camera.getPicture(options).then(
(imagePath) => {
if(this.platform.is('ios')){
this.photoTaken = true;
this.createPhoto(success.nativeURL);
this.sharePhoto(success.nativeURL);
}, (err) => {
314
console.log(err);
let alert = this.simpleAlert.createAlert('Oops!', 'Something
went wrong.');
alert.present();
});
} else {
this.photoTaken = true;
this.createPhoto(imagePath);
this.sharePhoto(imagePath);
}
},
(err) => {
let alert = this.simpleAlert.createAlert('Oops!', 'Something went
wrong.');
alert.present();
}
);
Hopefully the comments in the code above make it reasonably easy to follow whats going on, but heres
a high level step-by-step of whats going on after the imagePath is returned from the camera:
1. Work out the current le name by removing everything before the last / in the imagePath
2. Create a new unique le name using the date so that we do not overwrite anything
3. If we are running on iOS then we move the photo out of the temp directory and into a permanent
directory.
4. Set the photoTaken variable to true, and pass the new path to the image to the createPhoto
315
and sharePhoto functions
After all of this we should have the photo stored in a permanent location, along with a path to the image
in its new location. Keep in mind that we havent actually created the createPhoto and sharePhoto
functions yet. Were going to create the createPhoto function now, but we will save the sharePhoto
function for a later lesson when we integrate the social sharing plugin.
The createPhoto function will take the path (the nativeURL) and keep a reference to it in the application.
createPhoto(photo): void {
let newPhoto = new PhotoModel(photo, new Date());
this.photos.unshift(newPhoto);
this.save();
}
As you can see its pretty simple. We pass in the path to the image on the device and the current date to
our Photo Model to create a new photo object, and then we add it to our this.photos array. Rather
than using the push method to add it to the array (which adds it to the end) we add it to the start of the
array with unshift. We do this because we want to display the photos in reverse order (most recent rst)
We also have a call to the save function, which will save our data into storage but we havent implemented
We have photos being added to our this.photos array now, so now we can update our template to
loop through and display all of them. Since youre likely testing through the browser and cant add photos
through the camera, feel free to uncomment the test data in the constructor so that you can see the results
316
Lets update the template now.
<ion-header>
<ion-navbar danger>
<ion-title>
<img src = "images/logo.png" class="logo" />
</ion-title>
<ion-buttons end>
<button (click)="playSlideshow()"><ion-icon
name="play"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list no-lines>
<ion-item>
<img [src]="photo.image" />
<ion-badge item-right light>0 days ago</ion-badge>
317
</ion-item>
<ion-item-options>
<button light (click)="removePhoto(photo)"><ion-icon
name="trash"></ion-icon></button>
</ion-item-options>
</ion-item-sliding>
</ion-list>
</ion-content>
Theres two main additions to the template here. First we are using *ngFor to loop through our array of
photos and create an <ion-item-sliding> entry for all of them. Using let photo in let photo
of photos allows us to reference the specic photo that is being rendered in the loop by using photo,
e.g: photo.image to access the image path stored on the photo.
Since we can access the path of each photo, we can now also set the <img> element to display it. No-
tice that we use square brackets around [src], this allows us to set the src property to the expression
photo.image on the image element. This means that photo.image will rst be evaluated to whatever
value it stores (i.e. /path/to/image), and then set as the src. If we didnt use the square brackets, then
We also have a delete button here that passes a reference to the photo (which we created by using let
photo) through to the removePhoto function. Lets implement that function now so that the photo is
removed when the delete button is clicked.
removePhoto(photo): void {
318
if(photo.date.setHours(0,0,0,0) === today.setHours(0,0,0,0)){
this.photoTaken = false;
}
The second part of this function is straight forward enough, we nd the photo in our photos array, remove
it, and trigger a save. The rst bit is a little trickier though. The issue we have is that if a user takes a photo,
and then deletes the photo they took, they should be able to take another photo (since now they dont
have a photo for that day). But if they delete an older photo, then they shouldnt get to take another photo.
So what we do is grab the date of the photo that was deleted, and compare it to todays date. If the photo
being deleted was taken today, then we set this.photoTaken to false to allow the user to take another
photo.
Summary
What a lesson! There was some pretty complex stu covered in this lesson but weve implemented the
core functionality of the application now. Theres still quite a bit left to do, but we can take photos with the
camera and see them displayed in a list which is pretty cool. To give it a try for yourself youll need to run
the application on a real device. If you dont know how to do that already, you can skip to the Testing and
Debugging section of this book to see how to get the app installed on your device, and then come back
319
to nish the rest of the application.
IMPORTANT: If you want to test taking a photo on a real device now, you will have to update the
loadPhotos(): void {
this.loaded = true;
}
You cant take a photo until the data has nished loading, so we can just fake that for now. In the next
lesson, well be looking at how to create a data service to store our photos data permanently, so that it
is available when the user comes back to the application the next time.
320
Lesson 5: Saving and Loading Photos
We can now take photos in the application and display those photos in a list, we can even delete them as
well. The problem is that the data stored in this.photos will be lost as soon as the application is closed.
Obviously the user isnt going to be very happy when they nd their sele missing the next time they try
to use the application, so in this lesson we are going to learn how to create a data storage service to both
Weve already got a lot of this process set up, weve already generated a Data provider which we have
imported into our home.ts le, and we even have a save function created that we are calling whenever
Modify the empty Data provider to provide save and load functionality
Load the photo data into the application when the application is opened
Although we have already imported the Data provider in home.ts, we still need to declare it as a provider.
We can do this in the @Page decorator for home.ts, but its often useful to declare a data service like this
in the root component so that you can more easily use it in other components as well.
@Component({
template: '<ion-nav [root]="rootPage"></ion-nav>'
})
export class MyApp {
321
rootPage: any = HomePage;
constructor(platform: Platform) {
platform.ready().then(() => {
StatusBar.styleDefault();
});
}
}
We now have providers set up for both our SimpleAlert service and our Data service.
Were going to add the code for data.ts now so that it will save into storage any data that it is passed. The
code for this service is actually surprisingly simple, so lets take a look at it rst and then talk through it.
@Injectable()
export class Data {
storage: Storage;
constructor(){
this.storage = new Storage(SqlStorage, {name:'photos'});
}
322
getData(): Promise<any> {
return this.storage.get('photos');
}
save(data): void {
let newData = JSON.stringify(data);
this.storage.set('photos', newData);
}
Theres a couple imports at the top of this code block that you may not be familiar with, so lets take a look
at those:
Storage is Ionics generic storage service, and SqlStorage is used to dene the type of storage we want
to use. In the case of SqlStorage that means we will be storing data using a native SQLite database
(remember how we added the SQLite plugin before?). The SQLite database will only be available when
running natively on a device, so SqlStorage also uses WebSQL which is browser based to store data if
Alternatively, you could use the LocalStorage service instead of **SqlStorage* which just stores data in
the browsers local storage memory, rather than hooking into the SQLite plugin. In general this is a bad idea
because the browser based local storage is not completely reliable and can potentially be wiped by the
operating system. Having your data wiped randomly is obviously not ideal, so you should use SqlStorage
whenever possible, which does not have this issue (it also allows you to store more data).
constructor(){
this.storage = new Storage(SqlStorage, {name:'photos'});
323
}
What were doing here is creating a reference to our database by instantiating a new instance of Storage.
We provide the storage type we want to use, and provide the name of our database. If the database does
getData(): Promise<any> {
return this.storage.get('photos');
}
This function will allow us to retrieve the latest data that has been stored. The get method of our storage
will return a promise, but notice that we are not setting up the handler for when the promise nishes here,
instead we just return the result of the get method directly. This allows us to set up the handler from
wherever in the code this method is being called, which makes more sense for the ow of the application
Then we have our save function, which handles actually saving the data into storage:
save(data): void {
let newData = JSON.stringify(data);
this.storage.set('photos', newData);
}
We are storing all the data as a single JSON encoded string, so we rst call the JSON.stringify function
and then store the data using the set method on our storage object.
Thats all there is to saving the data, which isnt really all that complex. Now we just need to handle loading
that data back into the application. Were going to do this by dening the loadPhotos function.
loadPhotos(): void {
324
this.dataService.getData().then((photos) => {
if(typeof(photos) != "undefined"){
savedPhotos = JSON.parse(photos);
}
if(savedPhotos){
savedPhotos.forEach(savedPhoto => {
this.photos.push(new PhotoModel(savedPhoto.image, new
Date(savedPhoto.date)));
});
this.loaded = true;
});
}
325
The rst thing we are doing here is calling the getData() function we just implemented in our data service.
This will return all of the photo data as a JSON encoded string, so the rst thing we do is decode it into an
If there are photos loaded from storage, we use the data to recreate each photo using our photo model.
Then, once again, we do a little trickery to determine whether or not the user is allowed to take a photo
today. We simply check the date of the photo in the rst position and compare it to the current date, if they
Once all this has nished executing, we set the this.loaded ag to true to indicate that the photos are
loaded and ready to go. Now all we have left to do is make our save function actually make a call to the
data service.
save(): void {
this.dataService.save(this.photos);
}
Now whenever the save function is called it will pass in all the current values in this.photos and save
Summary
That lesson was certainly a lot shorter, so hopefully you enjoyed a bit of a break after the last one. Saving
and loading data sounds like a pretty complex topic, but as you can see storing simple data is actually
quite easy.
In the next lesson were going to get back to doing something a little more fun, were going to nish of the
Slideshow page so that it shows a slideshow of all of the users photos, as well as implementing our own
326
Lesson 6: Creating a Custom Pipe and Flipbook of all Photos
In this lesson we will be creating our own custom pipe to display the dates photos were taken in a more
user friendly way, and we will also be nishing o a critical portion of our application which is the slideshow.
It wont be quite as complex as actually taking the photo was, but the slideshow is pretty much the point
Weve already covered what a pipe is in the basics section of this book, but to refresh your memory a pipe
The goal for us here is to create a label that displays how many days ago a photo was taken, e.g. 3 days
ago, 10 days ago. Right now, the only data we have stored on our photo that we can use is a date
which isnt the most user friendly format in the world. So a @Pipe is a perfect use case for this: we create
a pipe that takes in our ugly date format, converts it into the X days ago format, and then returns it.
Lets take a look at how to create the pipe rst, and then well go over how to use it.
@Pipe({
name: 'daysAgo'
})
@Injectable()
export class DaysAgo {
transform(value, args) {
327
let now = new Date();
let oneDay = 24 * 60 * 60 * 1000;
let diffDays = Math.round(Math.abs((value.getTime() -
now.getTime())/(oneDay)));
return diffDays;
}
}
In the @Pipe decorator we supply a name of daysAgo, this means that this pipe can be used by using
daysAgo as the keyword in the template. When using a pipe, you always pass data into it, and this data
gets sent to the transform function and is passed in as the value. We will be passing the Date object
from our photo into this pipe, so thats what we will be working with here.
First we do a little mathematics magic to work out the dierence in days between the current date and the
date the photo was taken (Stack Overow gets the credit for this one, my solution would have been way
uglier than this), and then we return it. Whatever value is returned is what will actually be rendered out to
If you recall from previous lessons, weve already imported this pipe into home.ts and weve declared it
as a pipe in the decorator for home.ts, so the only thing we need to do to use it is add it to the template.
<ion-item>
<img [src]="photo.image" />
<ion-badge item-right light>{{photo.date | daysAgo}} days
ago</ion-badge>
</ion-item>
328
{{photo.date | daysAgo}}
This will pass photo.date into the daysAgo pipe, and then whatever daysAgo returns will be displayed
to the user here. The end result will be a badge containing something like 5 days ago.
Weve already created the template for the Slideshow, so now we need to create some logic so that all of
the photos are cycled through and displayed, and we will also need to add a way to open the slideshow
Lets start o by dening the playSlideshow function so that we can actually open the page.
playSlideshow(): void {
We display a modal page to users very similarly to how we create alerts. First we create a Modal, and then
we present it using the NavController. In this case we create a Modal using the SlideshowPage that we
have already created, and we also pass in a data object with it that contains all of the photos. This will
329
allow us to grab that data from the Slideshow Page.
Notice that we only trigger the modal if the user has more than 1 photo (because a slideshow with 0 or 1
photos isnt really a slideshow, right?), and if they dont an alert is displayed.
Now we are going to dene the class for the Slideshow Page. The class is reasonably small so Im going
to post all of the code in one block and then we will step through it afterwards.
@Component({
templateUrl: 'build/pages/slideshow/slideshow.html',
})
export class SlideshowPage {
imagePlayerInterval: any;
photos: any;
ngAfterViewInit(){
this.playPhotos();
}
330
closeModal(){
this.viewCtrl.dismiss();
}
playPhotos(){
//Restart
this.imagePlayerInterval = setInterval(() => {
if(i < this.photos.length){
imagePlayer.src = this.photos[i].image;
i++;
}
else {
clearInterval(this.imagePlayerInterval);
}
}, 500);
}
Theres a couple things here you may not be familiar with. Were importing the NavParams service here
which will allow us to grab the photo data that we added when creating the modal. You can see we grab
this in the constructor by using this.navParams.get('photos');. The other weird thing here is the
use of ViewController. We need to include this so that we can dismiss the modal (weve dened the
331
closeModal function here as well which is called by a button in our template).
The most important function here is the playPhotos function, this is what will loop through all of the
photos and change the image element on our page accordingly. First we grab a reference to the image
element by using the @ViewChild reference we set up before (which allows us to grab any element from
the template using a #localvariable attached to it), we clear any intervals that are currently running
(in case a user restarts the slideshow when it is still currently playing) and then we start looping through
all of the photos in this.photos. If there are still photos left to display, the image elements src property
is updated with the next photo, and if there isnt photos left to display then the interval is cleared. In the
code above the interval is set to run every 500ms or every 0.5 seconds, so you could adjust this to make
the slideshow slower or faster if you want (or perhaps even add an option for the user to control that).
The playPhotos function is triggered automatically by the use of the ngAfterViewInit function, which
automatically executes after the view is intialised. The reason we trigger the call here and not in the
constructor is because if we call it from the constructor the DOM might not be ready yet, and when we try
to get the element by its ID, it may not even exist yet.
Summary
This was another reasonably simple lesson, but we covered some cool stu here. Most of the main func-
tionality for the application has been implemented, now we just need to add the bells and whistles. We
will of course make it look a lot prettier, but in the next lesson we are going to look at incorporating local
332
Lesson 7: Integrating Local Notications & Social Sharing
Weve nished creating a lot of the main functionality in the application: we can take photos and have them
displayed in a list, delete photos, load photos from storage, play a slideshow and so on. The features we
will be adding in this lesson are nice-to-haves that improve the user experience.
We will be adding local notications that will send a reminder to the user once a day to take their photo for
the day, and we will enable social sharing so that the user can share any photos they take with their friends.
Weve already installed both of these plugins in the getting started section so we just need to implement
the functionality - neither of these plugins are supported in Ionic Native at the time of writing this so we will
just be using the plugins directly like we did with the File API (fortunately, these plugins are much simpler!)
Local Notications
Before we implement the local notications, it might be worth highlighting the dierence between push
notications and local notications. They both look and behave very similar, they allow you to notify the
user of things whether they currently have your application open or not and look like this:
333
The dierence is that push notications require an external server that handles pushing a notication out
to the device, whereas local notications are completely handled on the device itself. This means local
notications are great for things like alarms, scheduled notications, or in the case of our application a daily
reminder to take a photo. Push notications are better for being notied of things that happen outside the
To implement this functionality we are just going to add a little bit of code to our root component.
334
import {Platform, ionicBootstrap} from 'ionic-angular';
import {StatusBar} from 'ionic-native';
import {HomePage} from './pages/home/home';
import {Data} from './providers/data/data';
import {SimpleAlert} from './providers/simple-alert/simple-alert';
import {LocalNotifications} from 'ionic-native';
@Component({
template: '<ion-nav [root]="rootPage"></ion-nav>'
})
export class MyApp {
rootPage: any = HomePage;
constructor(platform: Platform) {
platform.ready().then(() => {
if(platform.is('cordova')){
if(!scheduled){
LocalNotifications.schedule({
id: 1,
title: 'Snapaday',
text: 'Have you taken your snap today?',
at: firstNotificationTime,
335
every: 'day'
});
});
});
}
}
Since this constructor is one of the rst things that runs when we open the application, we put the call to
this plugin inside of the handler for platform.ready() to make sure that the plugin is ready to be used.
We then also check if we are running on the cordova platform like we did with the Camera, because this
Then we get into the using the plugin itself, which we can access through LocalNotifications from
Ionic Native. We rst check if a notication with the id of 1 is already scheduled, if it is we do nothing, but
if its not then we create a new notication using the schedule method.
We simply supply a title and and a message that we want to display on the notication, and we also supply
an id so that we can identify the notication later (like we just did when checking to see if it was already
scheduled). We need to describe when to display the notication as well, so we create a time that is 24
hours from now (so that the rst notication displays at the same time tomorrow) and set the frequency to
every day, so that it will continue to show every day (there are other options you can use like every week
etc.).
Thats all there is to using this plugin, or at least thats all we need to do with it, there are a lot more
336
options so make sure to check out the documentation for the plugin if youre looking to do something
more complex.
Social Sharing
Now were going to add the social sharing plugin. This is a great plugin for sharing little bits of data across
many dierent social media networks and even things like SMS and email. It can be quite a bit of hassle
integrating with various dierent APIs manually (e.g. Facebook, Twitter etc.) and this plugin simplies that
immensely.
You can either share something to a specic platform, or you can trigger a generic share pop up and
the user can share to wherever they want. To implement this functionality, we are going to dene our
sharePhoto(image): void {
337
handler: () => {
SocialSharing.share('I\'m taking a selfie every day with
#Snapaday', null, image, null)
}
}
]
});
alert.present();
Weve created a few alerts before and each time we have used the Simple Alert service that we created.
Were not doing that here though because were creating a more complex alert. This one is going to have
two buttons, and one of those buttons will trigger a share using the social sharing plugin (the other will just
As you can see in the handler for the Share button, all we do is call the share method through the plugin
(which is accessible through window.plugins.socialsharing) and supply a message and the image.
Theres a bunch of dierent congurations you can use with this plugin so again, make sure to check out
Now when the user clicks Share they will get a pop up like in the image above, and will be able to choose
Summary
Both of the features we added to this application will help with growth and user engagement in the appli-
cation. Even if someone wanted to take a sele every day through the application, they would likely forget
without some kind of reminder. The ability for them to easily share their photos to social media will also
help spread the word about the application and hopefully drive more downloads.
338
The only thing we have left to do now is style our application so that it doesnt look so ugly, we will be
339
Lesson 8: Styling
Were almost there, the application is basically completed now, but after this lesson the functionality will
If you remember from the basics section, theres quite a few dierent ways we can add styles to the
application. We will be basically using all of these methods. We will be adding specic styles to each of
our components, we will be adding some generic styles in our core le, and we will be overriding some
SASS variables in the variables le. If you skipped over that part or are not entirely sure what Im talking
about here, Id recommend going back and reading about theming in the basics sections.
Lets rst add some generic styles to the application by modifying the core .scss le and the variables le.
.logo {
max-height: 39px;
}
Were keeping this pretty simple here, all were doing is setting a max height for the logo so that it displays
nicely in the navbar. Next well do something a little more interesting by adding in some custom SASS
variables.
$colors: (
primary: #387ef5,
secondary: #32db64,
danger: #fa2d18,
light: #f4f4f4,
dark: #222,
favorite: #69BB7B
);
340
$item-ios-padding-left: 0;
$item-md-padding-left: 0;
$item-ios-padding-right: 0;
$item-md-padding-right: 0;
$item-ios-padding-top: 0;
$item-md-padding-top: 0;
$item-ios-padding-bottom: 0;
$item-md-padding-bottom: 0;
$list-ios-margin-bottom: 0px;
$list-ios-margin-top: 0px;
$list-md-margin-bottom: 0px;
$list-md-margin-top: 0px;
First, weve redened the default colors for the application which we are making use of in our templates.
Remember that these values can be added to the elements in our templates like this: <ion-navbar
danger *navbar>
Then we override a bunch the default SASS variables, which is basically just removing padding and margins
from the list and list item elements so that our photos take up all the available space.
Now lets move on to the component specic styles. We are going to be modifying our templates slightly
to include some custom classes, and dene those classes in the .scss le for each component. Lets
<ion-header>
<ion-navbar danger>
<ion-title>
<img src = "images/logo.png" class="logo" />
</ion-title>
341
<ion-buttons end>
<button (click)="playSlideshow()"><ion-icon
name="play"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list no-lines>
<ion-item>
<img [src]="photo.image" class="list-photo" />
<ion-badge item-right light class="date-badge">{{photo.date |
daysAgo}} days ago</ion-badge>
</ion-item>
<ion-item-options>
<button light (click)="removePhoto(photo)"><ion-icon
name="trash"></ion-icon></button>
</ion-item-options>
</ion-item-sliding>
342
</ion-list>
</ion-content>
.list-photo {
width: 100%;
height: auto;
}
ion-content {
background-color: #222222;
}
.item {
border: none !important;
background-color: #222222;
}
.date-badge {
position: absolute;
right: 10px;
}
Again, theres nothing too exciting here. Were making sure our photo takes up all the available width,
setting a dark background colour on the content area and on the items themselves, and using some
absolute positioning to get our days ago badge to display where we want it.
343
> Modify slideshow.html to reect the following:
<ion-header>
<ion-toolbar danger>
<ion-title>Play</ion-title>
<ion-buttons start>
<button (click)="playPhotos()"><ion-icon
name="refresh"></ion-icon></button>
</ion-buttons>
<ion-buttons end>
<button (click)="closeModal()"><ion-icon
name="close"></ion-icon></button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<div class="image-container">
<img #imagePlayer id="imagePlayer" src = "" />
</div>
</ion-content>
.image-container {
position: absolute;
top: 0;
bottom: 0;
left: 0;
344
right: 0;
display: flex;
justify-content: center;
align-items: center;
}
#imagePlayer {
width: 100%;
height: auto;
vertical-align: middle;
}
The only styling were adding to this page is to get the photos to display in the center, both horizontally
and vertically (which isnt always the easiest task!). Weve done a bit of trickery with exbox to get that to
happen. Flexbox is a little more advanced, but if youre looking for a bit of an intro to it then take a look at
this tutorial.
If you take a look at your application now, it should look like this:
345
As you can see, we havent even added that much styling to the application but it looks a whole lot better
now.
346
Conclusion
Congratulations on making it through the Giist tutorial. Weve learned a lot through developing this appli-
Theres always room to take things further though, especially when youre trying to learn something. Fol-
lowing tutorials is great, but its even better when you gure something out for yourself. Hopefully you
have enough background knowledge now to start trying to extend the functionality of the application by
Modify the Days Ago pipe to display today and 1 day ago instead of 0 days ago and 1 days
ago [MEDIUM]
Add settings to allow the user to control the playback speed of the slideshow [HARD]
Allow the user to congure whether or not they want daily notications and what time they want to
Remember, the Ionic 2 documentation is your best friend when trying to gure things out.
What next?
You have a completed application now, but thats not the end of the story. You also need to get it running
on a real device and submitted to app stores, which is no easy task. The nal sections in this book will
347
walk through how to take what you have done here, and get it onto the app stores so make sure to give
that a read.
348
Chapter 6
Camper Mate
349
Lesson 1: Introduction
Camper Mate is an interesting application to build because it doesnt really have one specic purpose like
the other applications have had, its a bit of a utility toolbelt kind of app. It provides a bunch of dierent
features to users that might be useful for them when going on caravan or camping trips.
This gives us a chance to play around with some things that we havent been able to in the other applica-
tions, and it also presents some interesting challenges - one important example being that we will have
lots of dierent bits of data we need to save, rather than just one set of data, so our data service is going
to be more complex than the ones we have been creating in other applications.
The two most important concepts well be covering in this application are the integration and use of Google
Maps and the use of forms for capturing user input. Also, the rst application in this book, Quicklists, would
make an excellent addition to this application, so were even going to look at how can add the entire Quick
A map that the user can set their camp location on. When they have left their camp site they will be
able to click a button which will then show them how to get back to their camp site.
A form that will allow the user to store personal details relevant to camping (car and trailer registration
etc.)
A form that will allow the user to store details about the camp they are staying at (access codes,
The ability to create, modify and delete custom checklists (the functionality provided by Quick Lists)
350
Heres a few screenshots to give you an idea of what it will look like in the end:
351
###Lesson Structure
352
1. Getting Ready
6. Reusing Components
7. Styling
Ready?
Now that you know what youre in for, lets get to building it!
353
Lesson 2: Getting Ready
In this lesson we are going to prepare our application for the journey ahead. We are going to of course
generate the application, and we are also going to set up all of the components and Cordova plugins we
will need. At the end of this rst lesson we should have a nice skeleton application set up with everything
A good rule of thumb before starting any new application is to make sure you have the latest version of
Ionic and Cordova, so if you havent done it recently then make sure to run:
or
We will be using the blank starter template for this application which, as the name implies, is basically
an empty Ionic project. It comes with one page built in called home which we will repurpose in the next
lesson.
> Make the new project your current working directory by running the following command:
cd campermate
Your project should now be generated - now you can open up the project folder in your favourite editor.
You can take a look at how your application looks by running the following command:
354
ionic serve
355
###Create the Required Components
Were going to be creating a few pages for this application, we are going to reuse the automatically gener-
ated home page to create our tab layout (which will allow us to switch between pages), but we still need
to add pages for the location, my details and camp details tab.
> Run the following command to generate the Camp Details page:
Also remember that any time we generate a new component, we need to import its .scss le into our
app.core.scss le. The Ionic CLI automatically generates pages for us, but it doesnt automatically import
@import "../pages/home/home";
@import "../pages/location/location";
@import "../pages/my-details/my-details";
@import "../pages/camp-details/camp-details";
As well as our tab pages, we are also going to create a few services. We will create a Data service for saving
and retrieving data, a Google Maps service to handle the Google Maps integration, and a Connectivity
356
> Run the following command to generate a Data provider:
Before you can build for certain platforms, you need to add them to your project. This is something we
will be doing way later on in this course, but you may as well just set them up now.
> Run the following command to add the iOS platform to your application
> Run the following command to add the Android platform to your application
This application will use a few dierent Cordova plugins. Remember, Cordova plugins can only be used
when running on a real device. Ill explain each plugin as we run through the commands for adding them.
357
The Geolocation plugin will allow us to grab the users current location, and it also provides other methods
which provide the ability to do things like track the users location over time.
> Run the following command to add the Network Information plugin:
This plugin will give us some information about the network the user is on, such as the type of connection
they have. This will allow us to more accurately determine if the user currently has an active Internet
connection or not.
This plugin gives you access to native storage with an SQLite database. We are adding it to this application
because the Ionic local storage service can make use of this plugin to provide more stable data storage.
> Run the following command to add the In App Browser plugin:
This plugin makes a webview available to us that we can launch external websites in.
> Run the following command to add the Status Bar plugin:
We will be adding this plugin to all projects to give us control over the status bar in our application (the bar
at the top of the devices screen that contains the time, battery information and so on).
> Run the following command to add the Splash Screen plugin:
This plugin allows us to control the splash screen (the fullscreen graphic that briey displays when you
open an app)
358
> Run the following command to add the Whitelist plugin:
This plugin is required for all applications, and helps to dene what resources should be allowed to be
loaded in your application. Without it, resources you try to load will fail.
As well as adding the plugin, you also need to dene a Content Security Policy in your index.html
le. We will be adding a very permissive policy which will essentially allow us to load any resources.
Depending on your application, you may look into providing a more strict policy, but an open policy is
This is another plugin that we will add to every application, but you may decide you want to leave it out.
By adding this plugin, when you build for Android Crosswalk will be used. Android has a lot of issues,
especially with older devices, because there is so many dierent software versions out there and dierent
versions have dierent browsers (remember, since we are building HTML5 applications it is actually a
browser engine powering and running our application). What Crosswalk does is bundle a modern browser
into your application so no matter what device you are running on your app will be powered by the same
The only real downside to this is that it increases the size of your application by a signicant amount. In
general, I think its worth it and Id recommend you include it but you may leave it out if you like. For more
359
Set up Images
When building this application we are going to be making use of a few images. Ive included these in your
download pack but you will need to set them up in the application you generate.
> Copy the images folder in the download pack for this application from www/images to your own
www folder
Summary
Thats it! Were all set up and ready to go, now we can start working on the interesting stu.
360
Lesson 3: Creating a Tabs Layout
In this lesson we are going to create the basic layout for the application, which will be a tabs layout. A tabs
page works a little dierently to most other pages you would have created, the page itself is pretty much
just a placeholder for the tabs, but the actual content of the tabs are other pages that you import into the
tabs page. Once you see it in action itll probably make more sense.
Initially, we are going to create three tabs to hold the various pages of our application, but eventually we
will be throwing in one extra tab which will hold the Quick Lists functionality.
Lets start o with our Home page which will be the page that holds all of our tabs.
<ion-tabs>
<ion-tab [root]="tab1Root" tabTitle="Location" tabIcon="navigate"></ion-tab>
<ion-tab [root]="tab2Root" tabTitle="My Details" tabIcon="person"></ion-tab>
<ion-tab [root]="tab3Root" tabTitle="Camp Details"
tabIcon="bookmarks"></ion-tab>
</ion-tabs>
As you can see, the layout here is pretty simple. Were using <ion-tabs> and setting up several tabs
with <ion-tab>. We are setting the root property on each of these tabs to an expression which we will
soon dene in our home.ts le. This expression will be the page that we want that tab to contain. We also
set a title and an icon which will be displayed on the tab interface.
361
@Component({
templateUrl: 'build/pages/home/home.html'
})
export class HomePage {
constructor(){
Were importing all the pages we want to display in the tabs here, and then we are setting those expressions
we were using in the template. The rst tab now knows that it should display the LocationPage as its
content.
Our tabs layout is basically set up now (hooray!), its not break time yet though - were also going to set
up the layouts and classes for each of the three tabs. Lets start with the Location page. The location
page will contain a map as well as some buttons that will allow the user to set their location, and launch
<ion-header>
<ion-navbar primary>
<ion-title><img src = "images/logo.png" class="logo" /></ion-title>
<ion-buttons start>
362
<button (click)="setLocation()"><ion-icon
name="pin"></ion-icon></button>
</ion-buttons>
<ion-buttons end>
<button (click)="takeMeHome()"><ion-icon
name="walk"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
</ion-content>
The <ion-navbar> allows us to add a header bar to the top of our application that can hold buttons,
titles and even integrates directly with Ionics navigation system to display a back button when neces-
sary. We add the primary attribute to the navbar to style it with our primary colour, which is dened in
Inside of this navbar we use <ion-title>, which is typically used to display a text title for the current
page, to display our logo. We also use <ion-buttons> to create some buttons in the navbar. By sup-
plying the start attribute, the buttons will be placed on the left side of the navbar, and by supplying the
end attribute the buttons will be placed on the right side. We place one button in each position to trigger
363
the set location, and take me home functions.
Finally, we have the content area which just contains a <div> which we have attached #map to - this will
allow us to grab a reference to this node later on. This <div> will also serve as a container for our Google
Map later on. Weve also added another <div> here which will show a connection message to the user if
they dont have an Internet connection available (we will implement this later).
Now lets take a look at the class denition for the location page.
@Component({
templateUrl: 'build/pages/location/location.html',
providers: [GoogleMaps]
})
export class LocationPage {
latitude: number;
longitude: number;
364
}
ngAfterViewInit(): void {
setLocation(): void {
takeMeHome(): void {
Weve set up quite a few things here but not much is going on yet. Were importing Geolocation from Ionic
Native which we will use later, as well as our Google Maps and Data services (which we will create later
as well). We add GoogleMaps to the list of providers here because it is specic to this class, but we dont
need to add Data because we will be adding it to the list of generic providers in app.ts instead.
We inject all of the services we require in the constructor and set up references to them by adding the
public keyword, as well as declaring two additional variables, latitude and longitude, which we will use
to hold the users location. Finally, we add the two functions that the buttons in the template call, but we
The weirdest thing here is the use of @ViewChild. You can use this to grab a reference to an element in
365
it will look in the template location.html template for an element that contains #map. If it nds it, it will
return an ElementRef which is a reference to that element. In this instance we are assigning the result of
that to mapElement. Later on we will pass this reference through to our Google Maps provider. It will be
important to do this inside of the ngAfterViewInit() method, as that function will only run once the
<ion-header>
<ion-navbar primary>
<ion-title><img src = "images/logo.png" class="logo" /></ion-title>
</ion-navbar>
</ion-header>
<ion-card>
<ion-card-header>
Camp Details
</ion-card-header>
<ion-card-content>
Update this form with the details of your current camp so you have an
easy reference for later.
</ion-card-content>
</ion-card>
<ion-list no-lines>
366
<ion-item>
<ion-label stacked>Gate Access Code</ion-label>
<ion-input type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Ammenities Code</ion-label>
<ion-input type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>WiFi Password</ion-label>
<ion-input type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Phone Number</ion-label>
<ion-input type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Departure Date</ion-label>
<ion-datetime displayFormat="DD/MM/YYYY"></ion-datetime>
</ion-item>
<ion-item>
<ion-label stacked>Notes</ion-label>
<ion-textarea type="text"></ion-textarea>
</ion-item>
367
</ion-list>
</ion-content>
The rst thing we do is create the navbar, but you already know how that works. We use an <ion-card>
to create a little header area for the page where we can describe what the page is for, but this is purely
decorational. Then we have a bunch of inputs inside of a list. Rather than using standard HTML like:
We instead use <ion-input> which Ionic provides, but it still allows for all the same types (and you will
also notice the last eld is actually a <ion-textarea>). We supply the stacked attribute which gives us
stacked labels on our input elds, but theres a whole bunch of dierent styles Ionic provides for inputs
by default. For now weve just added labels to the elds, but later on we will come back and add some
extra stu so that we can actually grab the values that a user inputs.
Also notice the use <ion-datetime> here, which is an in-built date and time picker that Ionic provides.
Theres not going to be much happening yet, but lets also create the class for this page now.
@Component({
templateUrl: 'build/pages/camp-details/camp-details.html',
})
export class CampDetailsPage {
368
constructor(public nav: NavController, public formBuilder: FormBuilder,
public dataService: Data) {
saveForm(): void {
The only thing were doing here thats out of the ordinary is importing FormBuilder and Validators from
Angular 2. Just above I mentioned that we will be editing the inputs we created later to actually do some-
thing, and this is where Form Builder (and ControlGroup) comes in. Form Builder will allow you to create
and manage forms, and Validators can be attached to specic elds to ensure a valid value is input (i.e. if
it needs to be an email address, phone number etc.). Well be getting to that a little later though.
Weve also added a saveForm function which we will use later to save the values the user enters in the
form. Now lets move on to the My Details page, which is almost exactly the same as this one.
<ion-header>
<ion-navbar primary>
<ion-title><img src = "images/logo.png" class="logo" /></ion-title>
</ion-navbar>
</ion-header>
<ion-card>
369
<ion-card-header>
My Details
</ion-card-header>
<ion-card-content>
Update this form with your details so you have an easy reference for
later.
</ion-card-content>
</ion-card>
<ion-list no-lines>
<ion-item>
<ion-label stacked>Car Registration</ion-label>
<ion-input type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Trailer Registration</ion-label>
<ion-input type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Trailer Dimensions</ion-label>
<ion-input type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Phone Number</ion-label>
370
<ion-input type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Notes</ion-label>
<ion-textarea type="text"></ion-textarea>
</ion-item>
</ion-list>
</ion-content>
Theres not much need to explain this one because, apart from having dierent input elds, it is exactly the
@Component({
templateUrl: 'build/pages/my-details/my-details.html',
})
export class MyDetailsPage {
371
saveForm(): void {
Once again, this is exactly the same as the Camp Details page. Sorry for the boring nish, but thats all
we need to for now for our layout, in the next lesson were going to take a closer look at Form Builder and
IMPORTANT: If you want to test your application right now, you will need to declare the Data and Con-
nectivity providers that we are importing and injecting. We will be doing this later in the app.ts le so that
we only need to declare it once for our whole application, but if you want to view your application now you
@Component({
template: '<ion-nav [root]="rootPage"></ion-nav>'
})
export class MyApp {
rootPage: any = HomePage;
constructor(platform: Platform) {
platform.ready().then(() => {
372
StatusBar.styleDefault();
});
}
}
373
Lesson 4: User Input and Forms
In this lesson were going to be looking at the functionality of the Camp Details and My Details pages,
which will involve making our forms functional. If youve completed some of the other applications in this
book, or have tried using inputs in Ionic 2 before, you may have used ngModel like this:
This sets up two way data binding on this input eld, which I discussed in more detail in the basics section,
this.myField
in the class denition for the page. If you change the value of this.myField it will be reected in the
input, and if you change the value of the input it will be reected in this.myField. This is a pretty easy
way to go about managing user input, but if you have more large and complex forms it starts to become
As well as making the code a bit nicer, by using Form Builder each of your inputs will become controls
which provide some more powerful functionality that you can hook into (if you check out the Giist appli-
cation you will see that we use a control to subscribe to changes on the input eld, which allowed us to
We can also make use of Validators with Form Builder, which allow us to tie a validation to a specic
input eld, which will check the input to see if it is allowed (e.g. a correctly formatted email address).
Lets jump right into implementing it so that you can see how it works. Well go through implementing this
functionality on the Camp Details page, and then replicate it on the My Details page.
<ion-list no-lines>
374
<ion-item>
<ion-label stacked>Gate Access Code</ion-label>
<ion-input formControlName="gateAccessCode"
type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Ammenities Code</ion-label>
<ion-input formControlName="ammenitiesCode"
type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>WiFi Password</ion-label>
<ion-input formControlName="wifiPassword" type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Phone Number</ion-label>
<ion-input formControlName="phoneNumber" type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Departure Date</ion-label>
<ion-datetime formControlName="departure"
displayFormat="DD/MM/YYYY"></ion-datetime>
</ion-item>
<ion-item>
<ion-label stacked>Notes</ion-label>
375
<ion-textarea formControlName="notes" type="text"></ion-textarea>
</ion-item>
</form>
</ion-list>
Our inputs are mostly the same except for a couple of important dierences. First, we have wrapped the
We dene the formGroup property as campDetailsForm which we will use with the Form Builder very
shortly. We also listen for the (change) event and trigger the saveForm function when it is detected,
this means that the saveForm function will trigger every time a user changes and switches away from a
specic input, but it wont trigger after every single character they type (although we could do that if we
wanted to). Usually on a form you would instead listen for the (submit) event and handle the data then,
but we dont want the user to have to hit a Save button or anything like that, we want it to just save as
The other important thing we have changed is that we have added formControlName to each of our
inputs, giving it a name (similar to what we would do with ngModel). Again, we will use this with the Form
Now were going to take a look at the class denition. First, we are going to modify the constructor:
campDetailsForm: ControlGroup;
376
this.campDetailsForm = formBuilder.group({
gateAccessCode: [''],
ammenitiesCode: [''],
wifiPassword: [''],
phoneNumber: [''],
departure: [''],
notes: ['']
});
Since we dened the formGroup as campDetailsForm in the template, we can assign a new Form
Builder group to it here. To create the group, we supply all of the formControlName names we added to
the inputs. Notice that we also supply an array that contains an empty string, this is used to initialse the
gateAccessCode: ['54321']
would set the gateAccessCode input value to 54321. You can also supply a validator here if you like,
this will make the gateAccessCode eld a required eld. Thats all there is to setting up our form, now we
saveForm(): void {
let data = this.campDetailsForm.value;
//this.dataService.setCampDetails(data);
}
377
NOTE: We have commented out the call to the data service since we havent implemented it yet, otherwise
We can grab all the values from our form at any time by using this.campDetailsForm.value. This
will return an object that contains all of the values, which is exactly how we would eventually like to store
them. So we pass this object of values through to our data service to save (which we havent implemented
yet of course).
Remember, the saveForm function gets triggered every time the user makes a change to any input eld,
so whenever they make a change we read the values and send them o for saving.
Now we just need to reect this functionality in our My Details page. Again, its pretty much exactly the
same so Ill just paste the code below rather than explaining it step by step.
<ion-header>
<ion-navbar primary>
<ion-title><img src = "images/logo.png" class="logo" /></ion-title>
</ion-navbar>
</ion-header>
<ion-card>
<ion-card-header>
My Details
</ion-card-header>
<ion-card-content>
Update this form with your details so you have an easy reference for
later.
378
</ion-card-content>
</ion-card>
<ion-list no-lines>
<ion-item>
<ion-label stacked>Car Registration</ion-label>
<ion-input formControlName="carRegistration"
type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Trailer Registration</ion-label>
<ion-input formControlName="trailerRegistration"
type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Trailer Dimensions</ion-label>
<ion-input formControlName="trailerDimensions"
type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Phone Number</ion-label>
<ion-input formControlName="phoneNumber" type="text"></ion-input>
</ion-item>
379
<ion-item>
<ion-label stacked>Notes</ion-label>
<ion-textarea formControlName="notes" type="text"></ion-textarea>
</ion-item>
</form>
</ion-list>
</ion-content>
myDetailsForm: ControlGroup;
this.myDetailsForm = formBuilder.group({
carRegistration: [''],
trailerRegistration: [''],
trailerDimensions: [''],
phoneNumber: [''],
notes: ['']
});
saveForm(): void {
380
let data = this.myDetailsForm.value;
//this.dataService.setMyDetails(data);
}
Forms arent exactly the most exciting thing in the world (well, for most people at least), but they are a
critical component of many mobile applications so its important to understand how they work. Being able
to use Form Builder can make your forms a lot more manageable and powerful, but sometimes a simple
In the next lesson well be jumping into something a little more fun, and quite a bit more complicated, when
381
Lesson 5: Implementing Google Maps and Geolocation
Google Maps and mobile apps are a perfect match. The Google Maps API is an awesome piece of tech
by itself, but when you couple it with a device that is meant to be mobile, as in not stationary, it opens up
a wide range of possibilities. Theres a ton of cool apps out there today that utilise Google Maps to do all
kinds of things.
Even if maps arent the core feature of your application, they are often quite useful as supplementary
In this lesson we will be implementing Google Maps on our Location page. Essentially what we want to
do is:
Display a map
Enable the user to launch directions to take them back to their set location
Getting the Google Maps SDK set up in an application, and even using it, is pretty easy. You simply load
the Google Maps SDK script and then start interacting with the API. It gets a bit more complicated than
Its not unreasonable to make the maps unavailable if the user does not have an Internet connection,
but how do we handle that gracefully? We dont want an error occurring and breaking the application
(because the Google Maps SDK hasnt been loaded) or otherwise causing the maps not to work, so we
What if the user does not have an Internet connection initially but does later?
What if the user does have an Internet connection initially but doesnt later?
382
Wait until a connection is available before loading the Google Maps SDK, rather than straight away
If the connection becomes available again, enable the Google Maps functionality
To make our code a little bit cleaner we are going to abstract a lot of this functionality into the Google Maps
provider we generated earlier. This will also make it easier to reuse the same code in another applications
as well.
NOTE: We will be using the Google Maps JavaScript SDK in this lesson, but you should also be aware
that you can use their native SDK as well through this Cordova plugin: https://github.com/mapsplugin/
cordova-plugin-googlemaps
This lesson is going to be pretty big so lets jump right into it. Were going to start o by implementing a
Connectivity Service
This is going to be a quick and simple little service that will allow us to easily check if the user has an
Internet connection or not. If they are on a device we will be able to make use of the network information
plugin we installed earlier (which is more accurate), but if the app is just running through a normal browser
then we use the onLine property to check for an Internet connection (which is less accurate).
@Injectable()
export class Connectivity {
383
onDevice: boolean;
isOnline(): boolean {
if(this.onDevice && Network.connection){
return Network.connection !== Connection.NONE;
} else {
return navigator.onLine;
}
}
isOffline(): boolean {
if(this.onDevice && Network.connection){
return Network.connection === Connection.NONE;
} else {
return !navigator.onLine;
}
}
}
This service is reasonably straight forward. We create a onDevice variable that uses Platform to check
if the application is running on a device. We then use that onDevice variable to see if we should check
for navigator.connection.type which makes use of the network information plugin, or just check
Weve dened two functions isOnline and isOffline so we will be able to call these from other classes
that import this service. Technically you could just dene one of these functions (if isOnline returns false
384
then we know they are oine obviously), but I think its nice to have the option to be able to use either of
the two.
Thats all there is to this service, so lets move on to the Google Maps service.
This service is going to hold most of the logic for our maps functionality. Its a pretty big service so well
@Injectable()
export class GoogleMaps {
mapElement: any;
pleaseConnect: any;
map: any;
mapInitialised: boolean = false;
mapLoaded: any;
mapLoadedObserver: any;
currentMarker: any;
apiKey: string;
385
constructor(public connectivityService: Connectivity) {
loadGoogleMaps(): void {
initMap(): void {
disableMap(): void {
enableMap(): void {
addConnectivityListeners(): void {
386
}
Notice that weve imported the Connectivity service we just created and added it as a provider in the
decorator. We also import the Geolocation plugin from Ionic Native, and Observable from the RxJS
library. We also added declare var google just after the imports - this is so that the TypeScript
compiler leaves us alone. Since were dynamically loading the Google Maps SDK the compiler doesnt
know what google is and will throw errors at us if we dont declare the variable.
If youve read the basics section and completed the Giist application then you would know quite a bit
about Observables, but to recap an Observable is something that can emit multiple values over time, and
we can subscribe to it to listen for those values. Were going to use an Observable to indicate when
Google Maps has nished loading. To do this we will simply create an Observable which is accessible
from outside of this service, and we will manually make it emit a value when weve nished loading.
Finally we have a bunch of functions that we have created, lets go through implementing these one by
one.
this.mapElement = mapElement;
this.pleaseConnect = pleaseConnect;
this.loadGoogleMaps();
387
return this.mapLoaded;
We will be able to call this init function to trigger the map loading process from wherever we import
the service. First we create our observable and then we trigger the next function in this process which is
loadGoogleMaps.
Since we will call this function from location.ts we are able to pass in the references to the map and
pleaseConnect elements that we retrieved earlier with @ViewChild. We accept those as parameters
here and them up as member variables so we can access them from anywhere within the class.
Notice that this function returns this.mapLoaded which is our Observable. This means that we can
assign this function to a variable when we call it, and then subscribe to that variable to be notied when
the map has nished loading (you will see this in action later). A little later on in this loading process we
loadGoogleMaps(): void {
if(this.connectivityService.isOnline()){
window['mapInit'] = () => {
this.initMap();
this.enableMap();
388
}
if(this.apiKey){
script.src = 'http://maps.google.com/maps/api/js?key=' +
this.apiKey + '&callback=mapInit';
} else {
script.src = 'http://maps.google.com/maps/api/js?callback=mapInit';
}
document.body.appendChild(script);
}
}
else {
if(this.connectivityService.isOnline()){
this.initMap();
this.enableMap();
}
else {
this.disableMap();
}
this.addConnectivityListeners();
389
}
This function looks a bit more complicated, but its reasonably straightforward. First we check if Google
Maps is loaded by checking for the existence of google and google.maps which would be available if
If the SDK has not been loaded yet, we trigger the loading process. Since the SDK hasnt loaded yet, we
rst call the disableMap function, which will indicate to the user that the map is not available. Then we
check if the user is online using our connectivity service, and if they are we inject the Google Maps SDK by
adding a script element to the application. Notice that there is a &callback=mapInit parameter in the
URL string. This allows us to trigger a function in our application once the Google Maps SDK has nished
loading, and in our case we call the initMap and enableMap functions once the load has nished (which
If the SDK has already been loaded then we check if the user is online. If they are online we initialise and
enable the map, and if they arent online we disable the map.
The last line in this function calls the addConnectivityListeners function which we will also imple-
ment soon. This will listen for online and oine events so that we know when to enable and disable the
map, and also when to try loading the SDK again if the user was initially oine when they opened the
application.
initMap(): void {
this.mapInitialised = true;
Geolocation.getCurrentPosition().then((position) => {
390
position.coords.longitude);
let mapOptions = {
center: latLng,
zoom: 15,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
});
The Google Maps SDK is loaded now, and this function will handle setting up a new map using the SDK.
We want to center the map at the users current location, so we rst make a call to getCurrentPosition
function of the Geolocation plugin. Once the promise this returns resolves it will pass through a position
object which will contain the users current latitude and longitude. We use these values, along with some
additional settings (zoom level and the map type) to create a new map instance.
The map will be created inside of the element that we passed the reference in for (#map). So after this
code runs, the Google Map will be added to our Location pages template.
At this point our map is ready to be interacted with, so we trigger the next function on our observer which
will cause it to emit a value. As I mentioned before, we will be subscribing to this observer so we will be
Although weve got the map loading now, theres still a few more functions we need to create.
> Modify the disableMap and enableMap functions to reect the following:
disableMap(): void {
391
if(this.pleaseConnect){
this.pleaseConnect.style.display = "block";
}
enableMap(): void {
if(this.pleaseConnect){
this.pleaseConnect.style.display = "none";
}
What we want to do here is show and hide an overlay on the Google Maps section so that when the user
is not connected to the Internet they will not be able to use the maps, and a message that says Please
connect to the Internet will display. The code above handles hiding and showing an element that will
display a message when the user gains or loses their Internet connection.
We will need to style the element so that it will overlay the map.
#please-connect {
position: absolute;
background-color: #000;
opacity: 0.5;
width: 100%;
height: 100%;
z-index: 1;
}
392
#please-connect p {
color: #fff;
font-weight: bold;
text-align: center;
position: relative;
font-size: 1.6em;
top: 30%;
}
Now when the user does not have a connection, you should see a screen like this:
393
We still need to nish up a few things before we reach that point though. Next up we have the
addConnectivityListeners function.
394
> Modify the addConnectivityListeners function in google-maps.ts to reect the following:
addConnectivityListeners(): void {
document.addEventListener('online', () => {
console.log("online");
setTimeout(() => {
this.enableMap();
}
}, 2000);
}, false);
document.addEventListener('offline', () => {
console.log("offline");
this.disableMap();
395
}, false);
As I mentioned, this function will handle what to do when the users connection status switches from online
to oine or vice versa. Were listening for both the online and oine events here, which trigger when
When the user comes online we check if Google Maps has already been loaded, and if it hasnt we load it.
Otherwise we check if the map has already been initialised and initialise it if it hasnt and then we enable
the map. Notice that we use a setTimeout here which will cause this code to run 2 seconds after the
user comes online - in the past Ive had trouble with triggering this code instantly, so I give the connection
When the user goes oine, all we do is disable the map. The last function we have to implement is the
changeMarker function.
if(this.currentMarker){
this.currentMarker.setMap(null);
}
396
this.currentMarker = marker;
All the other functions have been pretty generic and could be used in just about every implementation of
Google Maps you use in your applications, but this one is specic to this application. Rather than having
an addMarker function, this function will add a marker and remove the previous marker. We only ever
want the user to have one camp location set so this makes sense in this case.
All we do is pass in the latitude and longitude of where we want to add a marker and then create a new
marker using those values. If there is an existing marker then we remove it rst and then set the current
Now that we have our Google Maps service completely set up, we just have to use it!
Weve done quite a bit of work to get maps working already, but we still have a little way to go. Now we
are going to modify our Location page class denition to make use of the Google Maps service. Weve
already imported the service, added it to the providers array and created a reference to it so we can just
ngAfterViewInit(): void {
mapLoaded.subscribe(update => {
//this.maps.changeMarker(this.latitude, this.longitude);
397
});
First we trigger the map loading process by calling this.maps.init but we also assign that to the
mapLoaded variable because it returns the observable that we created. We then subscribe to that ob-
servable so that we can listen for when the map has nished loading, and when it does we call the
changeMarker function.
This wont work just yet because the latitude and longitude values will be done, so Ive commented it out.
Later though, we will be loading the latitude and longitude values that are saved in memory.
The map should be loaded and visible on the screen at this point, but it wont display correctly just yet.
First, you will need to add a couple of styles to your .scss le to display it properly.
#please-connect {
position: absolute;
background-color: #000;
opacity: 0.5;
width: 100%;
height: 100%;
z-index: 1;
}
#please-connect p {
color: #fff;
font-weight: bold;
text-align: center;
position: relative;
font-size: 1.6em;
398
top: 30%;
}
.scroll {
height: 100%;
}
#map {
width: 100%;
height: 100%;
}
Next we are going to implement the setLocation function, which will set the users camp location to
setLocation(): void {
Geolocation.getCurrentPosition().then((position) => {
this.latitude = position.coords.latitude;
this.longitude = position.coords.longitude;
this.maps.changeMarker(position.coords.latitude,
position.coords.longitude);
let data = {
latitude: this.latitude,
longitude: this.longitude
};
399
//this.dataService.setLocation(data);
alert.present();
});
NOTE: Once again, we have commented out the call to the not yet implemented data service in this code
Once again, we are using the Geolocation plugin to grab the users current position. Once we get that we
change the this.latitude and this.longitude values to the users current location, and we also
call the changeMarker function with that position. We want this location to be remembered when the
user opens the application again so we create a data object for the position and send it o to our data
Once this process is complete, we trigger an alert to let the user know that their location was succesfully
set and what that means. Now we just have one more function to dene:
takeMeHome(): void {
if(!this.latitude || !this.longitude){
400
let alert = this.alertCtrl.create({
title: 'Nowhere to go!',
subTitle: 'You need to set your camp location first. For now, want to
launch Maps to find your own way home?',
buttons: ['Ok']
});
alert.present();
}
else {
if(this.platform.is('ios')){
window.open('maps://?q=' + destination, '_system');
} else {
let label = encodeURI('My Campsite');
window.open('geo:0,0?q=' + destination + '(' + label + ')',
'_system');
}
The goal of this function is to show the user directions from their current location to their camp sites location.
First we check that the this.latitude and this.longitude values are set, and if they are not we
simply display an alert letting the user know they need to set their location rst.
If they do exist then we use the Google Maps and Apple Maps URL schemes to launch the maps application
401
with directions to the coordinates we supply. If we are running on iOS then we launch Apple Maps using
the maps:// scheme, and if we are running on Android then we launch Google Maps using the geo:
scheme. Now when the user triggers this function, a map should pop up with directions back to their
Thats it! Weve nished our maps functionality. A user should now be able to view the map, set their
location, and trigger directions back to their camp site. We still need to take care of one more important
thing and thats saving the data so that it is available when the user comes back to the application. We
will be handling that, as well as saving the data from the forms, in the next lesson.
402
Lesson 6: Saving and Retrieving Data
If youve been through some of the other applications, then you will be pretty familiar with creating a Data
service. This one is a little dierent though because in the other applications we were only ever storing
one set of data, but in this application we are going to have to save:
Camp Coordinates
Camp Details
My Details
The idea is still more or less the same, but its going to look a little dierent. We already have our forms and
our maps page sending data to our Data service so we just need to handle saving it, and we also need to
make a few modications to load that data back in again. Lets start out by implementing the Data service.
@Injectable()
export class Data {
storage: Storage;
constructor(){
this.storage = new Storage(SqlStorage, {name:'campermate'});
}
403
setCampDetails(data: Object): void {
let newData = JSON.stringify(data);
this.storage.set('campdetails', newData);
}
getMyDetails(): Promise<any> {
return this.storage.get('mydetails');
}
getCampDetails(): Promise<any> {
return this.storage.get('campdetails');
}
getLocation(): Promise<any> {
return this.storage.get('location');
}
First, in our constructor we set up a reference to our storage service. We create an instance of the generic
Storage service and then use SqlStorage to set it up with a database name of campermate. By using
SqlStorage the Storage service will make use of the SQLite plugin when running on a device, alternatively
we couldve used LocalStorage to just use the browser storage (but remember, local storage is volatile
404
In other applications we would usually just have two functions, one for getting data and one for saving
data. In this application though we are setting three dierent sets of data, so we create three set functions
and three get functions. The set functions take in the data we pass it and store it (after converting it to a
JSON string), and then the get functions will retrieve those values from the database. The get functions
will return a promise which resolves with the data, rather than the data directly, so we will need to set up
Now that we have our Data service set up, we just need to load in the data that is saved when we reopen
the application. So now we are going to make changes to the Location, My Details and Camp Details
pages as they all have some data they need loaded in.
ngAfterViewInit(): void {
this.dataService.getLocation().then((location) => {
if(typeof(location) != "undefined"){
savedLocation = JSON.parse(location);
}
if(savedLocation){
this.latitude = savedLocation.latitude;
this.longitude = savedLocation.longitude;
405
mapLoaded.subscribe(update => {
this.maps.changeMarker(this.latitude, this.longitude)
});
});
What were doing here is rst grabbing the users location from memory, and then triggering the map load.
If there was a location stored in memory then we set the this.latitude and this.longitude values
to be those values, and we supply them to the changeMarker function call which will be triggered once
the map has nished loading. If there is no stored location values we just load the map.
Next lets take care of loading in the form data for our Camp Details page.
@Component({
templateUrl: 'build/pages/camp-details/camp-details.html',
})
export class CampDetailsPage {
campDetailsForm: ControlGroup;
406
public dataService: Data) {
this.campDetailsForm = formBuilder.group({
gateAccessCode: [''],
ammenitiesCode: [''],
wifiPassword: [''],
phoneNumber: [''],
departure: [''],
notes: ['']
});
this.dataService.getCampDetails().then((details) => {
if(typeof(details) != "undefined"){
savedDetails = JSON.parse(details);
}
if(savedDetails){
formControls.gateAccessCode.updateValue(savedDetails.gateAccessCode);
formControls.ammenitiesCode.updateValue(savedDetails.ammenitiesCode);
formControls.wifiPassword.updateValue(savedDetails.wifiPassword);
formControls.phoneNumber.updateValue(savedDetails.phoneNumber);
formControls.departure.updateValue(savedDetails.departure);
formControls.notes.updateValue(savedDetails.notes);
}
407
});
saveForm(): void {
let data = this.campDetailsForm.value;
this.dataService.setCampDetails(data);
}
You can see weve added in another call to retrieve some data from the data service here and then were
doing some stu with it. Remember before how I said you an supply an initial value when creating your
you might expect that you could just load in the data and then build your form using the saved values
as the initial values. Unfortunately, it doesnt quite work like this. The data being retrieved from mem-
ory is asynchronous, so although the data is retrieved very quickly there is some wait time involved.
The Form Builder group needs to be created instantly, otherwise you will receive errors. So instead
we create the Form Builder group right away, and then we grab a reference to the forms controls us-
408
@Component({
templateUrl: 'build/pages/my-details/my-details.html',
})
export class MyDetailsPage {
myDetailsForm: ControlGroup;
this.myDetailsForm = formBuilder.group({
carRegistration: [''],
trailerRegistration: [''],
trailerDimensions: [''],
phoneNumber: [''],
notes: ['']
});
this.dataService.getMyDetails().then((details) => {
if(typeof(details) != "undefined"){
savedDetails = JSON.parse(details);
}
if(savedDetails){
409
formControls.carRegistration.updateValue(savedDetails.carRegistration);
formControls.trailerRegistration.updateValue(savedDetails.trailerRegistration);
formControls.trailerDimensions.updateValue(savedDetails.trailerDimensions);
formControls.phoneNumber.updateValue(savedDetails.phoneNumber);
formControls.notes.updateValue(savedDetails.notes);
}
});
saveForm(): void {
let data = this.myDetailsForm.value;
this.dataService.setMyDetails(data);
}
Weve uncommented the calls to the data service in both the Camp Details and My Details classes above,
setLocation(): void {
Geolocation.getCurrentPosition().then((position) => {
this.latitude = position.coords.latitude;
this.longitude = position.coords.longitude;
this.maps.changeMarker(position.coords.latitude,
410
position.coords.longitude);
let data = {
latitude: this.latitude,
longitude: this.longitude
};
this.dataService.setLocation(data);
alert.present();
});
Were also going to declare both our Connectivity and Data providers globally inside of our root compo-
nent.
411
import {Data} from './providers/data/data';
import {Connectivity} from './providers/connectivity/connectivity';
@Component({
template: '<ion-nav [root]="rootPage"></ion-nav>'
})
export class MyApp {
rootPage: any = HomePage;
constructor(platform: Platform) {
platform.ready().then(() => {
StatusBar.styleDefault();
});
}
}
and thats it! Any data that is entered in the application should remain there when the application is reloaded
now.
Were getting pretty close to nishing this application, we obviously still need to add some styling (although
it looks pretty groovy already), but in the next lesson were going to be adding one more little bonus feature
to this application.
412
Lesson 7: Reusing Components
I heard you like apps, so Im going to put an app in your app. Up until this point our application has only
had three tabs, but were about to add a whole new tab now. It just so happens that the rst application
in this book, QuickLists, would be a perfect companion to this application. Checklists can come in very
What were going to do in this lesson is essentially drag and drop the functionality from Quick Lists into
this application. Given the modular nature of Ionic 2 & Angular 2, its pretty easy to do. If youve already
created the Quick Lists application you can copy the code from there, but if not you can just copy the code
Its not going to be quite as easy as dragging and dropping, there will be a few conicts we run into but
it is pretty straightforward. Lets start out by copying over the les we need.
> Copy the checklist-model folder from QuickLists into the providers** folder in CamperMate**
> Copy the checklist page folder from QuickLists into the pages folder in CamperMate
Those two are easy enough, but now we run into a bit of trouble. We also want to copy over the HomePage
from QuickLists, but we already have a HomePage in CamperMate, so were going to make a bit of a
modication.
> Create a new folder called quicklistshome inside of the pages folder in CamperMate
> Copy the contents of the home folder from QuickLists (home.js, home.html, home.scss) into the
and quicklistshome.scss
Hopefully that made sense, the end result is that you will now have also copied over the home page folder
from QuickLists, but everything will be called quicklistshome instead of just home so that we dont run
into any trouble with our existing home page. If all of that was a little confusing, just check the folder
413
structure in the example source code that came with this book.
Since weve made some changes to the le names, were also going to have to edit the code a little bit
too. Well have to change the class name of the QuickLists Home Page we just copied over to reect its
new name, and well also have to change where it gets its template from:
@Component({
templateUrl: 'build/pages/quicklistshome/quicklistshome.html'
})
export class QuickListsHomePage {
this.dataService.getData().then((checklists) => {
if(typeof(checklists) != "undefined"){
savedChecklists = JSON.parse(checklists);
414
}
if(savedChecklists){
savedChecklists.forEach((savedChecklist) => {
loadChecklist.checklist.subscribe(update => {
this.save();
});
});
});
Notice that both the class name and the templateUrl have changed. Theres one important thing we ne-
glected to copy over and that is the Data service from the QuickLists application. This is because we
already have one for the CamperMate application, so instead of using a whole new data provider we are
getData(): Promise<any> {
return this.storage.get('checklists');
415
}
//Remove observables
data.forEach((checklist) => {
saveData.push({
title: checklist.title,
items: checklist.items
});
});
These two functions have a bit of a dierent naming style than the rest of the functions in data.ts but
keeping it this way saves us from having to make more code changes to the QuickLists functionality so
There isnt going to be much we need to in terms of styling, but our QuickLists application is using the
secondary colour on its navbars, but CamperMate uses primary so we will need to change that.
<ion-navbar primary>
<ion-navbar primary>
416
Were also going to make another little code change that is in the general styling for the QuickLists appli-
cation.
button {
border: none !important;
}
With that, we have all of the QuickLists functionality we need ready to use in our CamperMate application.
<ion-tabs>
<ion-tab [root]="tab1Root" tabTitle="Location" tabIcon="navigate"></ion-tab>
<ion-tab [root]="tab2Root" tabTitle="My Details" tabIcon="person"></ion-tab>
<ion-tab [root]="tab3Root" tabTitle="Camp Details"
tabIcon="bookmarks"></ion-tab>
<ion-tab [root]="tab4Root" tabTitle="Checklists"
tabIcon="checkbox"></ion-tab>
</ion-tabs>
This will use tab4Root as the tab which we will of course also have to dene in our class denition.
@Component({
417
templateUrl: 'build/pages/home/home.html'
})
export class HomePage {
tab1Root: any;
tab2Root: any;
tab3Root: any;
tab4Root: any;
constructor(){
this.tab1Root = LocationPage;
this.tab2Root = MyDetailsPage;
this.tab3Root = CampDetailsPage;
this.tab4Root = QuickListsHomePage;
}
Now if you load up your application you should see a whole new tab:
418
As you can see, its pretty easy to reuse code youve created for other application with this modular style
project structure, and it would be even easier if we werent using components with the same name like we
did here.
419
Our application is looking pretty good already, but were going to apply a few more styles in the next lesson
420
Lesson 8: Styling
This application is pretty stock standard, so theres not going to be too much customisation going on here
and this lesson is going to be super quick. But we do want to add a few little nal touches.
$colors: (
primary: #5b91da,
secondary: #32db64,
danger: #f53d3d,
light: #f4f4f4,
dark: #222,
favorite: #69BB7B
);
and then we just want to apply some general styles to our whole application.
@import "../pages/home/home";
@import "../pages/location/location";
@import "../pages/my-details/my-details";
@import "../pages/camp-details/camp-details";
@import "../pages/quicklistshome/quicklistshome";
@import "../pages/checklist/checklist";
.logo {
max-height: 39px;
margin-top: 6px;
}
421
ion-input, ion-textarea {
background-color: #F3F3F3;
border: 1px solid #cecece;
padding-left: 10px;
}
ion-textarea {
height: 200px;
}
textarea {
height: 180px;
}
button {
border: none !important;
}
The main thing we are tring to achieve here is to add a bit of styling to our input elds. Before doing this,
the input elds were white and so was the background, so you couldnt even see where the input elds
were. Now they will have a grey background colour and a bit of extra styling. Weve also added a bit
of styling to the textarea to expand its default height, and to also workaround a bug that exists currently
422
423
This is the single smallest lesson in the entirety of this book, but, were done! The CamperMate application
424
Conclusion
Congratulations on making it through the Camper Mate tutorial. Weve learned a lot through developing
Theres always room to take things further though, especially when youre trying to learn something. Fol-
lowing tutorials is great, but its even better when you gure something out for yourself. Hopefully you
have enough background knowledge now to start trying to extend the functionality of the application by
Use Validators on the Phone Number and Date input elds to ensure valid data is input [MEDIUM]
Use local notications (see the Snapaday application for help) to notify the user the day before their
Add a eld to Camp Details that allows the user to take a photo and display it there (see Snapaday
Remember, the Ionic 2 documentation is your best friend when trying to gure things out.
What next?
You have a completed application now, but thats not the end of the story. You also need to get it running
on a real device and submitted to app stores, which is no easy task. The nal sections in this book will
walk through how to take what you have done here, and get it onto the app stores so make sure to give
that a read.
425
Chapter 7
Camper Chat
426
Lesson 1: Introduction
This one is the big one, the pice de rsistance, the crme de la crme, its the last application we will
be building and I think it ts the bill. As with all the other applications in this book, there is no need to
complete the other applications before doing this one as everything will still be expalined, even if the same
thing has been explained in other applications, but the complexity is cranked up a notch in this one and I
wont be spending as long on the basics. So, if youre not that comfortable with Ionic 2 yet you might nd
In this section we will be building Camper Chat, which will essentially be a live chat application. Users will
be able to log in with their Facebook account and chat all things caravan and camping with anybody else
who is using the application. The coolest thing about this application is the integration with PouchDB for
storing local data, and Cloudant for syncing that PouchDB data to a remote backend. This means that
the data can be available to users when they are oine, and when they come back online again the latest
To give you a more precise denition, the exact features of the application will be:
Users can leave messages that all other users can see and respond to (in real time)
The users Facebook display picture and name will be used in the application
Navigation
427
and heres some screenshots to help you get an idea of what the application will look like:
428
###Lesson Structure
429
1. Getting Ready
6. Styling
Ready?
Now that you know what youre in for, lets get to building it!
430
Lesson 2: Getting Ready
In this lesson we are going to prepare our application for the journey ahead. We are going to of course
generate the application, and we are also going to set up all of the components and Cordova plugins we
will need. At the end of this rst lesson we should have a nice skeleton application set up with everything
A good rule of thumb before starting any new application is to make sure you have the latest version of
Ionic and Cordova, so if you havent done it recently then make sure to run:
or
We will be using the blank starter template for this application which, as the name implies, is basically
an empty Ionic project. It comes with one page built in called home which we will repurpose in the next
lesson.
> Make the new project your current working directory by running the following command:
cd camperchat
Your project should now be generated - now you can open up the project folder in your favourite editor.
You can take a look at how your application looks by running the following command:
431
ionic serve
432
Create the Required Components
This application is going to have a few dierent pages, we are going to be using a login page, the main
home page and an about page. The home page has already been generated for us, so we will just need
Also remember that any time we generate a new component, we need to import its .scss le into our
app.core.scss le. The Ionic CLI automatically generates pages for us, but it doesnt automatically import
@import "../pages/home/home";
@import "../pages/login/login";
@import "../pages/about/about";
As well as our pages, we are also going to create a data service which will handle storing and retrieving
our message data, as well as storing a few values that we will grab from the Facebook API.
433
Add Required Platforms
Before you can build for certain platforms, you need to add them to your project. This is something we
will be doing way later on in this course, but you may as well just set them up now.
> Run the following command to add the iOS platform to your application
> Run the following command to add the Android platform to your application
Install PouchDB
Since we will be using PouchDB in this project, we rst need to install it. You can do that in a similiar way
to how you install Ionic Native, simply run the following install command:
We will also need to install typings for PouchDB so that the TypeScript compiler doesnt complain (since
it doesnt know what PouchDB is). If you do not already have the typings package installed, rst run:
and then:
to install the PouchDB typings. If you are wondering how you might nd typings for other libraries, you
can run:
434
and it will return a table of any matching typings that are available. Then you just install them in this format
This application will use a few dierent Cordova plugins. Remember, Cordova plugins can only be used
when running on a real device. Ill explain each plugin as we run through the commands for adding them.
This plugin gives you access to native storage with an SQLite database. We are adding it to this application
because the Ionic local storage service can make use of this plugin to provide more stable data storage.
> Run the following command to add the In App Browser plugin:
This plugin makes a webview available to us that we can launch external websites in. We will be using
this plugin in this application as it is a dependency of the Facebook plugin we will be using. Adding the
Facebook plugin itself actually requires a bit more work than just running a simple command, so we will
> Run the following command to add the Status Bar plugin:
We will be adding this plugin to all projects to give us control over the status bar in our application (the bar
at the top of the devices screen that contains the time, battery information and so on).
> Run the following command to add the Splash Screen plugin:
435
This plugin allows us to control the splash screen (the fullscreen graphic that briey displays when you
open an app)
This plugin is required for all applications, and helps to dene what resources should be allowed to be
loaded in your application. Without it, resources you try to load will fail.
As well as adding the plugin, you also need to dene a Content Security Policy in your index.html
le. We will be adding a very permissive policy which will essentially allow us to load any resources.
Depending on your application, you may look into providing a more strict policy, but an open policy is
This is another plugin that we will add to every application, but you may decide you want to leave it out.
By adding this plugin, when you build for Android Crosswalk will be used. Android has a lot of issues,
especially with older devices, because there is so many dierent software versions out there and dierent
versions have dierent browsers (remember, since we are building HTML5 applications it is actually a
browser engine powering and running our application). What Crosswalk does is bundle a modern browser
into your application so no matter what device you are running on your app will be powered by the same
The only real downside to this is that it increases the size of your application by a signicant amount. In
general, I think its worth it and Id recommend you include it but you may leave it out if you like. For more
436
Set up Images
When building this application we are going to be making use of a few images. Ive included these in your
download pack but you will need to set them up in the application you generate.
> Copy the images folder in the download pack for this application from www/images to your own
www folder
Summary
Thats it! Were all set up and ready to go, now we can start working on the interesting stu.
437
Lesson 3: Login Page and Sliding Menu Layout
Were going to implementing a really common page ow in this application, and thats a log in page that
leads to the main application. In pretty much any application that requires users to be authenticated, there
is going to be some authentication screen shown rst, and only once the user successfully logs in will they
In our case we are going to have a login page that will have a login with Facebook button, which will then
lead to a page with a sliding menu layout. Our login page is pretty simple (for now at least) so lets get
<ion-content padding>
<ion-row>
<ion-col>
<img src="images/logo.png" />
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<p>Camper Chat allows you to talk to other <strong>caravaners,
RV'ers, roadtrippers and campers</strong> near you. Use
Camper Chat to give and receive tips, ask for help with your
flat tyre, or just have a friendly chat.</p>
438
<ion-row>
<ion-col>
<a (click)="login()"><img src="images/facebook-button.png" /></a>
</ion-col>
</ion-row>
</ion-content>
This denes the template for our login page, and if this isnt the rst application youve built with Ionic 2
(and hopefully it isnt) then you might notice weve left the navbar out of the template. We have no need
to display a title and we dont need to display a back button for navigation or anything like that, so its
Were not doing anything too crazy here, but we are using Ionics grid system, which consists of columns
and rows, to set up our layout. When using the grid system with <ion-row> and <ion-col> rows get
placed underneath one another, and cols within those rows appear side by side. This diagram should help
439
This is a very simple example because we are just using the grid to display three sections underneath
each other, but you can also include multiple columns within rows and even dene how wide they are like
this:
<ion-row>
<ion-col width-10></ion-col>
<ion-col width-50></ion-col>
</ion-row>
Weve also attached a (click) handler to a Facebook login button image. Eventually we will have this
login function trigger the authentication with Facebook, but for now we are just going to have it trigger
a page change to the main page so that we can nish setting up our layout. Lets set up the class for this
440
import {Component} from '@angular/core';
import {Platform, NavController, MenuController, AlertController} from
'ionic-angular';
import {Facebook} from 'ionic-native';
import {HomePage} from '../home/home';
import {Data} from '../../providers/data/data';
@Component({
templateUrl: 'build/pages/login/login.html',
})
export class LoginPage {
login(): void {
this.getProfile();
}
getProfile(): void {
this.menu.enable(true);
this.nav.setRoot(HomePage);
}
As you can see in the code above, weve injected and set up our NavController and were using it to set
441
the rootPage to be the HomePage when the login function is called. This code is being run through
the getProfile() function because later we will be grabbing some information about the user from
Facebook after they have authenticated, but before we send them to the next page, but since we havent
implemented that yet we just want the code to ow right through to the success scenario.
The MenuController is used to programmatically interact with the sliding menu we will be adding to the
application. We will discuss that in more detail in a moment, but we dont want the menu to be used on
the login page so we use the enable function on the menu to disable it for this page. When we switch to
Weve also imported the Facebook plugin from Ionic Native which we will use later. Remember that to use
a a Cordova plugin you do not need to import anything, not even for the Facebook plugin, but if you want
to use it through Ionic Native then you will need to import it.
We want this login page to display rst, but if you take a look at the root component in app.ts you will see
that the home page is currently being set as the root page:
So of course we want to change that, and we are also going to need to import a few things into our root
@Component({
442
templateUrl: 'build/app.html'
})
export class MyApp {
platform.ready().then(() => {
});
openPage(page): void {
logout(): void {
ionicBootstrap(MyApp, [Data]);
443
As you can see we have set the root page to be the Login Page and of course we need to import the Login
Page as well. Weve also done quite a bit more than that though. Were importing and setting up references
to our Data provider, which we will use for saving and accessing data later, the Menu Controller, which will
allow us to interact with the sliding menu we are creating. Also notice the use of @ViewChild here, this
allows us to grab a direct reference to our Nav component (the <ion-nav> that we will be adding in the
next step). This is necessary because you cant use the NavController inside of the root component, but
we need to be able to change the rootPage from here later so we instead grab the Nav directly.
Weve declared our data provider in the ionicBootstrap function, and have set up a couple of functions
that we will nish dening later. The openPage function will handle switching between dierent selections
from the sliding menu (which will be tied to the this.homePage and this.aboutPage variables we
have set up here), and the logout function will handle logging the user out of the application. Weve
imported Facebook from Ionic Native so that the logout function can interact with the Facebook API.
Also notice that we are using a templateUrl in the decorator which links to an app.html le. A lot of the
time we dont worry about creating a separate template le for the root component because it is so simple
(usually just the nav). Since we are using a sliding menu we are going to be dening the template for the
menu in its own app.html le, since it is going to be a bit bulkier than usual. Lets create that now.
> Create a le called app.html inside of your app folder and add the following code:
<ion-menu [content]="content">
<ion-content>
<ion-list no-lines>
<button ion-item (click)="openPage(homePage)">
<ion-icon name="chatbubbles"></ion-icon> Chat
</button>
<button ion-item (click)="openPage(aboutPage)">
<ion-icon name="information-circle"></ion-icon> About
</button>
<button ion-item (click)="logout()">
444
<ion-icon name="power"></ion-icon> Logout
</button>
</ion-list>
</ion-content>
</ion-menu>
Theres some really interesting stu going on here. This part of the code above will likely look pretty familiar
to you:
Except for one bit, and thats #content. The # syntax is used to dene local variables in the template, so
what we are doing here is assigning the <ion-nav> component to the local variable content. We then
<ion-menu [content]="content">
So what were doing here is essentially telling the menu what content it should attach itself to, which in
this case is our entire application. Everything else inside of <ion-menu> is just the content that the menu
will contain, and all were doing is creating a list of buttons that will serve as tabs for the user to click on.
Each of these tabs will call the functions that we created in our root component.
The nal piece of this layout puzzle is to create the home page, lets start o by dening the template.
<ion-header>
<ion-navbar primary>
<button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>
445
<img src = "images/logo.png" class="logo" />
</ion-title>
</ion-navbar>
</ion-header>
<ion-list no-lines>
<ion-item>
<ion-avatar item-left>
<img src="">
</ion-avatar>
<h2>Name here</h2>
<p>message here</p>
</ion-item>
</ion-list>
</ion-content>
<ion-footer>
<ion-toolbar>
<ion-input [(ngModel)]="chatMessage" type="text" placeholder="enter
message..."></ion-input>
<button (click)="sendMessage()" style="position:absolute; right: 0;
top: 0; margin: 0;"><ion-icon
name="chatbubbles"></ion-icon></button>
</ion-toolbar>
</ion-footer>
446
We have our navbar added back in this template and weve added a button with a menuToggle attribute
which will add a button to allow the user toggle the menu on and o (the menu can also be opened and
closed by swiping, which is why we needed to disable it on the login page even though we didnt add a
menu button).
Notice that we have created a local variable using #chat which is the <ion-content>, this is because
we are going to need to grab a reference to it later using @ViewChild just as I mentioned we would be
Inside of the content area we create a list with just a single <ion-item> for now (later we will use *ngFor
to display all of the available messages), and were also using <ion-avatar> which allows us to attach a
nicely styled picture to the item. Eventually we will also add the name of the user who posted the message,
Were also using an <ion-toolbar> which is pretty much the same as the navbar minus all the navigation
magic. Its a bar that we can add a title, buttons or whatever else we want to it, and in this case we are
aligning it to the bottom of the screen instead of the top. This allows us to have an area for our message
input which will stay stuck at the bottom of the screen even as the list scrolls.
Inside of the toolbar weve added an input eld which is has two way data binding set up on chatMessage
by using [(ngModel)]. This means that when this value changes, the value for this.chatMessage in
home.ts (which we are about to create) will also be updated (and vice versa). Then we just add a button
which will eventually send the message that is entered, and weve added a bit of inline styling to make it
Now lets create the class denition for our home.ts le.
447
@Component({
templateUrl: 'build/pages/home/home.html'
})
export class HomePage {
sendMessage(): void {
}
}
Pretty simple stu here, were importing and injecting our data service again and were also importing Face-
book from Ionic Native. We set up the this.chatMessage variable that our input eld from the template
links to and we also dene an empty array of messages which will eventually contain the messages to be
displayed on screen.
ionic serve
you should have two pages now that looks like this:
448
449
As is usually the case, it looks pretty ugly right now but the basic structure for the layout we need is there.
We have a login page with a login option, which leads to the main page with a sliding menu.
450
In the next lesson were going to start integrating the Facebook API so that we can have users logging in
for real (which will also provide us some juicy data to use in the application like the users name and prole
picture).
451
Lesson 4: Using Facebook for Authentication
In this lesson well be integrating the Facebook plugin from Ionic Native which will allow us to authenticate
users in our application, and it will also allow us to do a few other things like grabbing the users prole
information. Theres a ton more you can do with the Facebook API that I wont be covering though, so
Using social authentication like the Facebook API provides has a lot of advantages. It saves us from having
to create our own backend with an authentication system (which would involve creating a database, storing
user credentials securely, handling password resets and so on), it saves the user from having to create
another new account to use the application (they can just log in with one tap), and it provides a bunch of
useful data and integrations. This isnt to say that social login is always the best authentication method,
Lets walk through how to set up the Facebook plugin and then we can look at integrating into our appli-
cation.
IMPORTANT: The Facebook connect plugin will only work when running on a real mobile device, if you try
to run it through your desktop browser you will just receive errors. Instead of testing by running ionic
serve, you should instead use ionic run ios or ionic run android. If you are unsure how to get
an application on your device, make sure to read the Testing and Debugging section of this book
As I mentioned in the previous lesson, this plugin is a little trickier than just running the normal plugin install
command. We are going to need to supply the plugin with an App Name and an App ID when we install
it. To do that, we need to create an application on Facebooks developers platform, so lets walk through
doing that.
First you will need to go to developers.facebook.com and sign up if you havent already. Once youve done
452
Click on My Apps and then Add a New App. Select the iOS platform, and then on the next page just
click the Skip and Create App ID button in the top right corner. Fill in a Display Name (the name of your
application, which for now we will assume is CamperChat) and choose a category. Then click Create
App ID.
453
Now you should nd yourself on a screen like this:
454
and you may notice a eld there with an App ID, make note of that because we will be using it shortly.
Weve still got a bit more setting up to do rst though. Click on the Settings tab on the left, on this screen
we are going to add the platforms we are using which will be iOS and Android.
We can add the iOS platform now, but the Android platform requires a key hash before it will allow you
to save it. So we are going to leave that for now, but make sure to come back and add it later because
your Android version will not work without it. We will be covering how to create a keystore le and a key
hash for Android in the build chapters later on, so if you do want to do it right now feel free to skip ahead
Click the Add Platform button and choose iOS. You will need to add a Bundle ID which should be in the
455
Youll need to make sure the Bundle ID you supply matches the one in the cong.xml le in your Ionic
project, so make sure the following line in your cong.xml le matches your Facebook Apps Bundle ID:
You will also need to supply the same Bundle ID when you add the Android platform to your Facebook app
later as well (it should be added under Google Play Package Name).
Thats all there is to it for now, with our App Name and App ID in hand we are ready to install the plugin in
our application.
IMPORTANT: When your app is ready to go live, remember to switch the Facebook application out of
development mode by going to the App Review tab and switching Make Your App public? to Yes
To install the plugin in the application you will need to run the following command from your project direc-
tory:
making sure to replace the APP_ID and APP_NAME values with the ones from your own Facebook appli-
IMPORTANT: There is currently a build issue with this plugin on Android. This may or may not eect you
depending on which version you are using, but if when building for Android later the build fails you will
cordova.system.library.1=com.facebook.android:facebook-android-sdk:4.4.0
456
Setting up Authentication
Weve got everything we need set up now, so we can jump into some more coding in our application.
Integrating the Facebook API with Ionic Native is actually really easy, but before we do that we are going
to set up our data service so that it can store some values from Facebook temporarily. The data service
will primarily be used for handling storing and retrieving our message data later, but since we just need to
store a couple of values from Facebook its easy to just tack them on here.
@Injectable()
export class Data {
fbid: number;
username: string;
picture: string;
constructor() {
Now were going to modify our login page to authenticate with Facebook when the user clicks the login
login(): void {
Facebook.login(['public_profile']).then((response) => {
457
this.getProfile();
}, (err) => {
alert.present();
});
Since we have already imported Facebook from Ionic Native we can access the Facebook object now.
We call the login method and pass in an array of permissions we require. Were only after basic user infor-
mation so we only request permission for the users public prole, but there are many more permissions
User friends
User interests
User bio
User location
and a whole lot more, take a look at this page for a full list. The login method returns a promise, so we
set up a handler for the response and if it is successful we trigger the getProfile function we set up
previously which will launch the home page. If it is not successful then we create and display an alert to
458
the user.
We now know that a user has successfully authenticated with Facebook and that they should have access
to our application, but we still need to nd out a few details about them. In order for them to use the
application we will also need their name and prole picture, so to grab this we are going to modify the
getProfile function.
getProfile(): void {
Facebook.api('/me?fields=id,name,picture', ['public_profile']).then(
(response) => {
console.log(response);
this.dataService.fbid = response.id;
this.dataService.username = response.name;
this.dataService.picture = response.picture.data.url;
this.menu.enable(true);
this.nav.setRoot(HomePage);
},
(err) => {
console.log(err);
459
subTitle: 'Something went wrong, please try again later.',
buttons: ['Ok']
});
alert.present();
);
Now we are making a call to the api method that the Facebook plugin provides. This allows us to interact
with Facebooks Graph API which is how you do just about everything with the Facebook API - you can get
into some really complex stu but we just want to use it to grab the users id, full name and display picture.
The rst parameter that needs to be provided to the api call is the endpoint you want to hit, which for
us is /me because we want data about the currently logged in user, and we add a query string containing
all the data elds we want returned. We also provide an array of permissions we require again and then
If the response is successful we store those values we retrieved using our dataService, then we enable the
sliding menu and switch to the home page. If the response is not successful then we again show the user
The only other bit of functionality we need to use from the Facebook API is the logout method. When a
user logs out of our application we want to terminate that session with Facebook as well (this wont log
them out of Facebook, it just means that they will need to reauthenticate with Facebook when using our
application again). To do that we are going to modify our own logout function.
logout(): void {
460
this.menu.close();
this.menu.enable(false);
this.nav.setRoot(LoginPage);
this.dataService.fbid = null;
this.dataService.username = null;
this.dataService.picture = null;
Facebook.logout();
}
First we handle stu on our end. We close the menu and disable it, and then switch back to the login page.
We reset all the Facebook data that was stored in our data service and then we call the logout method on
the Facebook API. The user would now be back on the login page in our application and unauthenticated.
Since there is a bit of waiting time in the application whilst the user is being authenticated (like when we
fetch the users prole) we want to indicate to the user that something is happening, and that the app isnt
just frozen. To do this we will use Ionics LoadingController service. This will allow us to stop interaction
@Component({
461
templateUrl: 'build/pages/login/login.html',
})
export class LoginPage {
loading: any;
this.loading = this.loadingCtrl.create({
content: 'Authenticating...'
});
this.menu.enable(false);
}
login(): void {
this.loading.present();
Facebook.login(['public_profile']).then((response) => {
this.getProfile();
}, (err) => {
462
buttons: ['Ok']
});
this.loading.dismiss();
alert.present();
});
getProfile(): void {
Facebook.api('/me?fields=id,name,picture', ['public_profile']).then(
(response) => {
console.log(response);
this.dataService.fbid = response.id;
this.dataService.username = response.name;
this.dataService.picture = response.picture.data.url;
this.menu.enable(true);
this.loading.dismiss();
this.nav.setRoot(HomePage);
},
(err) => {
463
console.log(err);
this.loading.dismiss();
alert.present();
);
Notice that we are now importing the LoadingController service and we set up an Authenticating
message in the constructor. We then to present the loading message (just like we would with an Alert or
a Modal) when the login method is called. Then we dismiss the loading message when either the users
prole information has been successfully retrieved, or when the login process fails.
Thats all there is to the Facebook integration in this application, but theres a ton more you can do with
the Facebook API. Id recommend looking at the plugin documentation and the Facebook Graph API to
In the next lesson, well be looking at how we can add some messages to our chat screen!
464
Lesson 5: Creating Messages & Navigation
This is obviously one of the most critical bits of functionality in our application, but its going to be pretty
straightforward to implement. The hard part will be the integration with PouchDB and Cloudant which we
will tackle later, for now we just want to create the ability for the logged in user to add messages to the
screen.
We will also be tieing up a few loose ends with our navigation - we have a menu with some options that
Adding Messages
Weve already set up an input eld tied to the this.chatMessage variable in our home page class
denition and a button that triggers the sendMessage function, so all we have to do to enable creating a
message is to dene that sendMessage function. Well also have to modify the template to loop through
sendMessage(): void {
let message = {
'_id': new Date().toJSON(),
'fbid': this.dataService.fbid,
'username': this.dataService.username,
'picture': this.dataService.picture,
'message': this.chatMessage
};
this.messages.push(message);
this.chatMessage = '';
465
}
All we are doing here is creating a message object with a few dierent properties. We add all of the
information we just recently grabbed from Facebook, along with the message the user entered. We also
add an id eld which is for the PouchDB & Cloudant integration well be working on in the next lesson,
Once weve created the message we just push it directly to the messages array and reset the chat message
input eld (so that the input eld is ready for the user to type their next message). We will be modifying
this in the next lesson so that the message is sent to our remote backend, not just pushed directly to the
messages array (nobody apart from the user who created it would see it otherwise!).
Now that we have the ability to push some data into our messages array, lets modify the template to
display it.
<ion-list no-lines>
</ion-list>
Now were looping through and displaying all of our messages in their own <ion-item>. Remember that
the syntax is a shortcut for creating embedded templates, and ngFor will create an embedded template
466
for every message in messages and stamp it out with that specic messages values. Using the data that
is attached to each message we are now adding the users display picture and name to the message, along
If you run the application now, you should be able to add some test messages to the application and see
them displayed (remember, you must test this on a device because Facebook login will not work through
ionic serve):
467
###Creating the About Page
Weve got our sliding menu set up, but the only button that actually works now is the Logout button. We
468
still need to create our about page and link to it, as well as linking the Chat button back to the main chat
<ion-header>
<ion-navbar primary>
<button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>
<img src = "images/logo.png" class="logo" />
</ion-title>
</ion-navbar>
</ion-header>
This is a really simple page and theres not really anything exciting here. Weve implemented the menuTo-
ggle button just as we did on the home page, and weve just added a bit of descriptive content.
Now we just need to set up the click handlers for the about page and the chat page in the menu. If you
take a look in app.html you will see that we have both of these buttons calling the openPage function:
469
<ion-icon name="information-circle"> About</ion-icon>
</button>
one passes in a homePage parameter and the other aboutPage. Were going to set up our openPage
openPage(page): void {
this.menu.close();
this.nav.setRoot(page);
}
First we close the menu since the user has made a selection (it would be annoying if the menu stayed
open) and then we use our nav component to change the root page. We set it to the page parameter that
was passed in, and since we have already set up homePage and aboutPagereferences:
the appropriate page will be switched to when the function is called. Now the user can switch back and
forth freely between the about page and the chat page, and then they can also log out to go back to the
log in page.
Weve now got all the basic functionality of the application set up, the last big thing we need to tackle is
the integration of PouchDB and Cloudant, which we will be doing in the next lesson, and then we just need
470
Lesson 6: Local and Remote Backend with PouchDB and Cloudant
Although weve already created most of the functionality for the application, this lesson is going to be the
biggest and hardest of them all. Rather than pushing the users message straight to the messages array,
PouchDB is an in browser NoSQL database that was inspired by the CouchDB project. Its biggest feature
is that it allows for data storage oine, and it can automatically sync to a remote database when the
application comes back online. Like using the SqlStorage service that Ionic provides, using PouchDB will
ensure that your local data wont be randomly wiped out like it can be when using plain local storage.
Teaching NoSQL is far beyond the aims of this book, but to give you a really quick background, NoSQL
databases usually store data in a JSON like key value style format, instead of the relational style tables
that traditional SQL uses. The two approaches are completely dierent, the way you use them is dierent
and the way you need to think about them is dierent. So when it comes to using NoSQL you have to drop
a lot of the preconceived notions you would have from SQL (assuming youve used that in the past). The
data were storing is super simple though, so we dont need to worry about learning how to use a NoSQL
database properly.
So PouchDB will handle storing our data locally, but we are also going to be using Cloudant to create a
remote database. We will be syncing the local PouchDB database to the remote Cloudant database, so
that whenever the application is online it can fetch new data from Cloudant, and any new local data will
also be pushed to Cloudant. Fortunately, these two tools are designed to work with each other and setting
Cloudant is a DBaaS (Database as a Service) so we are going to have to create an account (which is free
for low usage) to use it, as well as set up the database. Using something like Cloudant is really easy and
scales very well (since all the backend architecture is handled for you), but if you prefer you can easily use
something like CouchDB or Couchbase installed on your own server to sync with PouchDB. I wont be
covering how to set those up, but its not all that dierent.
471
Creating a Cloudant Database
Before we start jumping into the code, were going to set up our backend on Cloudant through IBM Bluemix.
Bluemix gives you access to IBMs Open Cloud Architecture and can be used to create, deploy and manage
cloud based applications. It provides a bunch of services you can use and one of those is Cloudant.
First you will need to create an IBM Bluemix account. Once you have created your account and logged in,
Choose the Create App option, choose Mobile, name your application and then click Finish. After that
472
Choose Cloudant NoSQL DB from the Services menu on the left and then click View your Data on the
Cloudant Dashboard. You should now be inside of the Cloudant dashboard. Click the Create Database
option in the top right to create a new database and call it camperchat or whatever you prefer.
Now select the newly created database to view more details, you should see a screen like this:
Click the API link in the top right to get a reference to your Cloudant Database URL, this is what we will
supply to PouchDB later so make a note of it. You should also go to the Permissions section and generate
an API key (making sure to give it Write and Replicate permissions) this can be used with PouchDB to
access your database, and its a better idea to use an API key which is revokable rather than the actual
username and password for your account. Make sure to take note of the password because once you
Theres just one more thing we need to do in here. We need to enable CORS (Cross Origin Resource
473
Sharing) so that we are able to make requests to the database from our application. To do that go to
Account in the left menu, select CORS and then choose the All Domains (*) options.
You should now have everything you need set up on Cloudant, ready to be used with PouchDB.
Integrating PouchDB
Earlier on we created a Data provider which so far we are only using to store a few values from the Facebook
API. Now were going to extend that Data service to handle storing and retrieving data with PouchDB.
Lets get the basics set up rst and talk through it.
@Injectable()
export class Data {
fbid: number;
username: string;
picture: string;
db: any;
data: any;
cloudantUsername: string;
cloudantPassword: string;
remote: string;
constructor(){
474
this.cloudantUsername = 'YourAPIUsernameHere';
this.cloudantPassword = 'YourAPIPasswordHere';
this.remote = 'https://YOUR-URL-HERE-bluemix.cloudant.com/camperchat';
//Set up PouchDB
let options = {
live: true,
retry: true,
continuous: true,
auth: {
username: this.cloudantUsername,
password: this.cloudantPassword
}
};
this.db.sync(this.remote, options);
addDocument(message){
getDocuments(){
handleChange(change){
475
}
Weve already installed PouchDB with npm so to make it available in this service we have just added the
following line:
In the constructor we are just handling the set up of PouchDB and the sync to the remote Cloudant
database. First we create a new PouchDB, or get a reference to an already existing one:
and then we dene a few variables which will be used to connect to the Cloudant database:
this.cloudantUsername = 'YourAPIUsernameHere';
this.cloudantPassword = 'YourAPIPasswordHere';
this.remote = 'https://YOUR-URL-HERE-bluemix.cloudant.com/camperchat';
Make sure to replace these values with your own from the Cloudant dashboard. Next we create an options
object to congure our connection to the Cloudant database and then we call the sync method:
this.db.sync(this.remote, options);
This will set up replication from our PouchDB database to the Cloudant database, and it will also set up
replication from the Cloudant database to the PouchDB database. So now if we add some data to our
PouchDB database it will automatically be reected in the remote Cloudant database, and if we change
or add some data in the remote Cloudant database it will automatically be reected in our local database.
After that we have just created three empty functions, which we are going to go through implementing
now.
addDocument
476
addDocument(message) {
this.db.put(message);
}
To add a document (a data object in NoSQL terms is called a document, so think of a document as
a bit of data, not something like a Word document) to our PouchDB database we simply call put on our
database. So we will be able to pass this function any object and it will add it to the database.
getDocuments
getDocuments(){
this.db.allDocs({
include_docs: true,
limit: 30,
descending: true
}).then((result) => {
this.data = [];
this.data.reverse();
477
resolve(this.data);
}).catch((error) => {
console.log(error);
});
});
This function handles retrieving all of the documents from our database (remember, a document is just
what a data object is called). This is an asynchronous operation, so we wrap it in a promise which resolves
when the data is returned. This will allow us to use the getDocuments().then() syntax elsewhere in
the application. By calling allDocs every document will be retrieved, but we are supplying a few options
here:
include_docs: true,
limit: 30,
descending: true
The include_docs option here may seem a little confusing, but we need to specify this so that all the
data in our documents are returned (message, picture etc.). If this option is left out then only the id of
the document is returned. We also set a limit of 30 and a descending order so that only the 30 latest
478
documents (chat messages) are returned.
We pass the result of that operation through to our handler, and for each row that is returned we push it
into a this.data array. We want these results to be in reverse order though because we want to show
the latest message at the bottom so we ip the array with reverse. After this we resolve the promise we
created and pass back the data, and then we set up a changes listener.
The changes listener will call the this.handleChange function every time a change is detected in the
database (when another user has added a chat message for example), and the change itself will be passed
handleChange
handleChange(change) {
});
479
}
else {
What we want to do with the change that is passed in is reect it in our this.data array, but it gets a
little bit tricky. The change object that is sent back could either be a document that has been updated, a
Detecting a deleted document is easy enough because it will contain the deleted property. But to see if
it is an update, we need to check if we already have a document with the same id (and update that one if
we nd it), if we dont then we know it is a new document (and need to add it to the array).
We have our Data provider completely set up now, but theres still a couple more things we need to do
to make use of it. We will need to store new data using the provider, rather than adding it directly to the
messages array like we are doing now, and we will also need to load in the latest messages data when the
480
import {AlertController} from 'ionic-angular';
import {Component, ViewChild} from '@angular/core';
import {Facebook} from 'ionic-native';
import {LoginPage} from '../../pages/login/login';
import {Data} from '../../providers/data/data';
@Component({
templateUrl: 'build/pages/home/home.html'
})
export class HomePage {
this.dataService.getDocuments().then((data) => {
this.messages = data;
this.chat.scrollTo(0, 99999, 0);
});
sendMessage(): void {
let message = {
481
'_id': new Date().toJSON(),
'fbid': this.dataService.fbid,
'username': this.dataService.username,
'picture': this.dataService.picture,
'message': this.chatMessage
};
this.dataService.addDocument(message);
this.chatMessage = '';
In the constructor we are now calling the getDocuments() function to load in the messages data, and
once we do we grab a reference to the scroll content using @ViewChild and scroll it to the bottom.
Finally, in the sendMessage function the only change weve made is to call the addDocument function
Thats it! You should now have a fully functional chat application. It works, but its still pretty ugly. In the
next lesson we are going to make it look prettier and even set up some animations!
482
Lesson 7: Styling & Animations
In this lesson were going to modify our templates a bit to add some classes and we will create some
custom styles, as well as overwriting some of the app wide SASS variables. If youve completed some of
the other applications then youll probably know that theres usually not too much work to be done in these
lessons, and this one isnt that dierent, but we will be doing something a little bit dierent by including
Animations, when done right, can do a lot to help make your application look and feel more high quality.
When done poorly they can look tacky and can also cause a big performance hit.
Basic Styling
Lets start o by adding some basic styling throughout the application to make it look a little nicer. First
were going to modify the variables le to make some application wide changes.
$colors: (
primary: #5b91da,
secondary: #32db64,
danger: #f53d3d,
light: #f4f4f4,
dark: #222,
favorite: #69BB7B
);
$list-ios-border-color: #fff;
$list-md-border-color: #fff;
$button-ios-border-radius: 0px;
483
Were not doing a whole lot here, just dening the colours we want to use, setting the default border colour
for Android and iOS and also setting the border radius for buttons on iOS to 0px (buttons on Android are
already square).
We also want to dene a few application wide styles in the core style le as well.
@import "../pages/home/home";
@import "../pages/login/login";
@import "../pages/about/about";
.logo {
max-height: 39px;
margin-top: 6px;
}
ion-menu ion-content {
background-image: url('../../images/login-background.png');
background-size: cover;
background-position-x: 40%;
}
ion-menu scroll-content {
margin-top: 44px;
}
ion-menu .item {
background-color: transparent;
color: #fff;
}
484
Were modifying the position of the logo a bit, setting a background image that will cover the whole login
page, we add a margin to the menu so that the content doesnt overlap the status bar when running on a
Next we are going to modify our login page and home page to include some custom styling.
.home ion-label {
white-space: normal;
}
.home ion-row {
padding: 0;
margin: 0;
}
.home ion-col {
padding: 0;
margin: 0;
}
.home toolbar-content {
padding: 0;
margin: 0;
}
Nothing exciting going on here, were just modifying the positioning of some of the layout elements so they
485
<ion-row>
<ion-col>
<img src="images/logo.png" class = "login-logo"/>
</ion-col>
</ion-row>
<ion-row class="login-description">
<ion-col>
<p>Camper Chat allows you to talk to other <strong>caravaners,
RV'ers, roadtrippers and campers</strong> near you. Use
Camper Chat to give and receive tips, ask for help with your
flat tyre, or just have a friendly chat.</p>
<ion-row>
<ion-col>
<a (click)="login()"><img src="images/facebook-button.png"
class="login-button" /></a>
</ion-col>
</ion-row>
</ion-content>
.login {
background-image: url('../../images/login-background.png');
486
background-size: cover;
background-position-x: 40%;
text-align: center;
}
.login-description {
height: 60%;
line-height: 2em;
}
.login-description p {
color: #fff;
}
.login-button {
-webkit-box-shadow: 0px 6px 20px 0px rgba(0,0,0,0.18);
-moz-box-shadow: 0px 6px 20px 0px rgba(0,0,0,0.18);
box-shadow: 0px 6px 20px 0px rgba(0,0,0,0.18);
}
Weve had to add a couple of extra classes to the login pages template this time. In the .scss le we are
again setting a background image (the same one used on the login screen) but this time it will be applied
to the menu. We position the description content and also add a bit of a shadow to the Facebook login
button.
Thats it for the basic styling, the pages in your application should now look something like this:
487
488
We still have the most fun bit left to do though, and thats adding a couple of animations.
489
Creating Animations
One important thing to remember when animating elements is to use the translate3d property (even though
we are only animating through 2D space). By using this property rather than animating the left property
for example, the devices GPU (Graphics Processor Unit) is invoked. This is called a hardware accelerated
animation and is a lot smoother. You may nd that animations that run ne on your desktop browser dont
work so well on mobile devices, so its usually important to use hardware acceleration (although relying on
the GPU too much can also be disadvantageous, as it can lead to battery drain).
To create our animations we are going to dene our own keyframes. Basically, this allows you to specify
what certain properties should be at a specic stage during an animation. For example:
@-webkit-keyframes slideInSmooth {
0% {
-webkit-transform: translate3d(-100%,0,0);
}
100% {
-webkit-transform: translate3d(0,0,0);
}
}
What we are saying here is that at the start of the animation (0%) the element should be displaced 100%
to the left (the three parameters in translate3d represent the x, y, and z axis), so it will be just o screen.
Then at the end of the animation (100%) the element should be back to its normal starting position.
@-webkit-keyframes slideInSmooth {
0% {
-webkit-transform: translate3d(-100%,0,0);
}
50% {
490
-webkit-transform: translate3d(50%,0,0);
}
100% {
-webkit-transform: translate3d(0,0,0);
}
}
Now the element would start o to the left, then go to the right and then nally get back to its normal
position. You can dene as many of these intermediate steps as you like, the only important thing is that
you always have a 0% and 100% (alternatively, you can use from and to) otherwise the animation will be
invalid.
We can attach these animations to any element simply by creating a class that uses the animation and
attaching the class to any element. We arent going to be using these animations, but we will create some
similar ones to animate in both the users display picture and their message.
@-webkit-keyframes animateInPrimary {
0% {
-webkit-transform: translate3d(-100%,0,0);
}
100% {
-webkit-transform: translate3d(0,0,0);
}
}
@-webkit-keyframes animateInSecondary{
0% {
491
opacity: 0;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.animate-in-primary {
-webkit-animation: animateInPrimary;
animation: animateInPrimary;
-webkit-animation-duration: 750ms;
animation-duraton: 750ms;
}
.animate-in-secondary {
-webkit-animation: animateInSecondary ease-in 1;
animation: animateInSecondary ease-in 1;
-webkit-animation-duration: 750ms;
animation-duraton: 750ms;
}
Weve created two dierent animations here. The animateInPrimary animation will make the element start
o outside of the left bounds of the screen, and will slide in to its normal position evenly over the course
of the animation. The animateInSecondary animation will start fading in from being fully transparent to
492
We assign these animations to two separate classes, both of which set an animation time of 750ms. Now
to use these, all we have to do is apply the animate-in-primary and animate-in-secondary classes to an
element.
<ion-list no-lines>
</ion-list>
Notice that weve added the primary animation to the avatar, and the secondary animation to the message
content area. If you load up the application now you should be able to see these elements animate in!
493
Conclusion
Congratulations on making it through the Camper Chat tutorial. Weve learned a lot through developing
Navigation
Theres always room to take things further though, especially when youre trying to learn something. Fol-
lowing tutorials is great, but its even better when you gure something out for yourself. Hopefully you
have enough background knowledge now to start trying to extend the functionality of the application by
Make the users device vibrate when a message is received by using a Cordova plugin [EASY]
Add an Invite Friends option using App Invites from the Facebook API [MEDIUM]
Add Channel Categories that will allow the user to switch between dierent chat rooms, e.g: Ve-
Add the ability for a user to privately message another user [VERY HARD]
Remember, the Ionic 2 documentation is your best friend when trying to gure things out.
What next?
You have a completed application now, but thats not the end of the story. You also need to get it running
on a real device and submitted to app stores, which is no easy task. The nal sections in this book will
walk through how to take what you have done here, and get it onto the app stores so make sure to give
that a read.
494
Chapter 8
495
Testing & Debugging
When creating your application you are, without a doubt, going to make some mistakes and run into some
errors. Its not always obvious where youve gone wrong either, so its important to know how you can go
This lesson is going to cover how best to debug Ionic 2 applications, but it wont cover what debugging
is in general or how to use debugging tools (e.g. viewing the source code, setting breakpoints, looking
at network requests and so on). If you are not familiar with browser based Javascript debugging, then I
Browser Debugging
Your desktop browser will usually be your rst point of call when developing your application. Its great to
be able to debug directly through the browser because, especially with live reload, you can quickly see the
But its important to keep in mind that it is not representative of how the application will run on an
actual device. For the most part, how it behaves in the browser will be the same as the way it works on
a real device, but there can be dierences and in the case of any Cordova plugins, they wont work when
A good approach is to do the majority of your testing in the browser, and once youre getting close to being
happy with how everything is working switch to testing on a real device to make sure everything still works
correctly.
iOS Debugging
In order to debug on an actual iOS device you will rst need to run the following command:
496
This will install both the iOS Simulator (which will allow you to emulate an iOS device on your computer)
and ios-deploy will allow you to deploy the application directly to your device when it is attached via
USB.
from within your project directory and the application will be deployed to your device (or to the emulator
if a device is not available). If it is the rst time you have run this command, you may need to run it twice
Once the application is running on your device, you can debug it using the Safari Dev Tools on your
If the Develop menu does not appear for you, you may rst have to enable it by going to Safari >
497
Once you open up index.html in Safari you should see a debugging screen like this:
IMPORTANT: This method will only work if you have a Mac. If you do not have a Mac then you can use
GapDebug to install and debug iOS applications. In order to do this you will need to generate an .ipa of
your application rst (we will discuss how to do this in the Building for iOS lesson) and install that directly.
Its also a good idea to test the nal build of your application using Test Flight.
Android Debugging
Debugging on Android is as simple as attaching the device you want to debug on and then running:
on your desktop browser (in Chrome) you will now be able to go to the following address:
chrome://inspect/#devices
498
and then click Inspect on the device you want to debug on, and you will see the debugging tools:
499
IMPORTANT: You will need to have USB debugging enabled on your device for this to work. Enabling
USB debugging is dierent for dierent devices, so just Google [YOUR DEVICE] enable usb debugging.
The ease of which you can debug applications usually comes from the debugging prowess you develop
over years of facing frustrating errors, but there are a few tips and gotchas I can oer that may help you
500
Facing a white or blank screen at launch?
This one happens a lot and the reason is usually a startup error when running on a device. Sometimes you
will launch your application and just see a blank screen, and even if you open your debugging tools you
might not see any errors. This can be because an error occurs before the debugger has launched, which
is breaking the application. So if youre running into a problem like this, make sure to get the debugger up
and then hit the refresh button from the debugger, which in Safari looks like this:
This will reload the application with the debugger already active, which means youre not going to miss
any errors that occur right at the start. Nine times out of ten you will discover there is some Javascript
501
Debugger
Hopefully you know about how useful breakpoints are for debugging your application, but if not Ill reiterate
You can just open up the Sources tab and put some breakpoints in manually, but you can also simply
debugger;
anywhere in your code, and it will cause your application to pause at that point (as long as you have the
404 Errors
Is your app working ne through the browser but youre getting 404 errors when running on a device?
and that you have an appropriate Content Security Policy (CSP) meta tag dened in your index.html le:
Of course the rst thing to make sure youve done is to run the install command for the plugin:
It might sound silly but make sure to double check with the following command:
ionic plugin ls
502
this will list all plugins that are currently installed, and I still even forget to install plugins sometimes. If the
plugin is installed then you should also make sure that you are not trying to access the plugin too early. If
you try to access plugin functionality before the device is ready, i.e. before this res:
platform.ready().then(() => {
});
then it will not work. Finally, if youve exhausted all other options and it is still not working then you should
Debugging applications can be a dicult and frustrating task, especially when debugging on a real device,
but over time you tend to develop a good sense of where things may have gone wrong and the process
becomes a lot easier. If youre stuck on a particular error, you can always head over to the Ionic Forum for
help, just make sure to provide as much detail about the error and what youve tried as you can.
If you are using PhoneGap Build and have a .ipa or .apk le you want to test then you will want to use
GapDebug. There are other ways to install these les - you could use iTunes to install the .ipa le or you
could use adb to install the .apk le - but it is so much easier with GapDebug and GapDebug also provides
you with some awesome debugging tools (although, if youre looking for some more advanced debugging
I would recommend using adb as well). To get GapDebug just head over to the website and download it.
After you have installed GapDebug, make sure you read through the GapDebug First-Time Conguration
and Setup - there is a few things youll need to do before GapDebug will work properly with your iOS or
503
Android device.
Once you have everything set up you will be able to connect your device to your computer, open up
GapDebug, then just drag and drop the application le (.ipa or .apk) into the device through GapDebug.
Not only is this a super quick way to get the application on your device but it also provides you with
some extremely useful debugging tools (which are essentially the same as the browser debugging tools
you may already be familiar with). Once you have installed your application with GapDebug, just click the
504
Chapter 9
505
Preparing Assets
At this point you should have your application built, working, and ready for submission to the app store.
Theres just a couple things we are going to do beforehand though to make sure our application is in ship
You may be following along and building your own application whilst reading this book, but chances are
youve been building one of the example applications. In that case, Id still recommend you go through
these steps, or at least read them, so that you learn how the submission process works. I do however ask
that you stop short of actually submitting the application to app stores, Im happy for you to use bits and
pieces of code as you see t in your own applications, but you cant submit an exact copy of one of the
Enabling production mode is an Angular 2 concept. This will disable some features of Angular 2 which are
useful for development, but not required in production. All you have to do is add the prodMode ag to the
ionicBootstrap(MyApp, [], {
prodMode: true
});
Theres so many dierent devices out there with dierent screen sizes and resolutions, and we need to
cater to all of these when creating our applications icon and splash screen.
Nobody in their right mind would be excited by the chance to make 50 dierent versions of the same
le. Luckily theres a very easy, almost completely automated, option provided by Ionic. We can use the
506
ionic resources command to generate these for us. Although Ionic does a lot for you but it cant quite
yet design your graphical resources for you, so theres a little set up work we need to do.
The Ionic resources tool only requires a single 192 x 192 icon as a base, but since the app store requires
a larger icon anyway its best to design it at 1024 x 1024 rst (or even 2048 x 2048). For the most part,
scaling down large images into small icons works well, but if you want a really crisp looking icon it may be
> Create a 192 x 192 icon called icon.png** and save it inside of the resources folder (overwriting the
existing icon)**
Icons are great because they are always square. Splash screens unfortunately come in all sorts of shapes
and sizes. This means that youre either going to have to make your design exible, or make dierent
Whats a exible design? Well, thats just what Ive decided to call a design that will play nicely with
Ionics resource tool. If you design the splash screen at 2208 x 2208 then all of your splash screens should
The problem is though that there is always going to be a degree of cropping that occurs - some screens are
tall and some screens are wide. To avoid important parts of your design getting chopped o you should
make sure to place the important parts of the design near the center of the image, and the bits near the
pack. Create your own 2208 x 2208 splash screen based on this template and save it as splash.png
in the resources folder along with the icon.png le you created previously.
507
Run the resources tool
Now that you have the base icon and splash screen created, you can create all of the rest of the splash
ionic resources
An important step before building is to set your App Name and the Bundle ID in your cong.xml le. If
you take a look at the rst few lines of this le you will see this:
You should update these details to reect your own, including the id which is currently io.ionic.starter.
This will need to match the values you use to sign your application in the next lessons, and should be set
to something like com.yourname.yourproject. You will likely also want to set the version to 1.0.0 when
This is not an essential step but you can also provide some preferences in your cong.xml le which will
aect how it is built. There are some defaults included in the cong.xml le like this:
508
<preference name="UIWebViewBounce" value="false" />
<preference name="DisallowOverscroll" value="true" />
<preference name="android-minSdkVersion" value="16" />
<preference name="BackupWebStorage" value="none" />
But there are a ton more preferences you can specify, take a look here for more information.
Minify Assets
This is also an optional step but an important one I think. You want to keep your nal build sizes down
as much as possible and one way to help do that is to minify all of your images using a tool like TinyPNG.
Just run your images through the tool and save them back into your project.
509
Signing iOS Applications on a Mac or PC
iOS and Android applications need to be signed. The idea is similar to a normal signature, in that it
proves you authorize the action being taken and that you have the authority to do so. Signing works
iOS applications will require a .p12 le which is made up of a key and either a development or distribution
certicate. As well as the .p12 le, you will also need to create a provisioning prole. The purpose of
the .p12 le is to identify you as an iOS developer, and the provisioning prole identies the application,
services and devices that are allowed to install the application. It is quite a process to get all these things
in order, but if you just follow this guide step by step you shouldnt have too many problems.
If you have a Mac you can use XCode to handle some of this process automatically, but I will be going
through everything step by step so that you can use alternate methods to build if you do not have a Mac.
Before you get started you will need to be enrolled in the iOS Developer Program.
If you have a Mac you should follow these steps, if not skip this section and start reading Signing iOS
Applications on Windows. You should make sure you have XCode installed before you begin.
510
* Go to the Certicates tab and make sure both options are switched o * Now go to Keychain Access
> Certicate Assistant > Request a Certicate From a Certicate Authority * Fill in the details in this
window and select Saved to disk and Let me specify key pair information:
511
Click continue and choose a location to save the le on your machine On the next screen choose 2048
The certicate signing request will now be saved in the folder you specied and you will also notice that a
Create a Certicate
To complete these next steps you will need to log in to the Apple Member Center and go to the Certicates,
512
* Choose Certicates under iOS Apps * Click the + button in the top right corner:
Next you must choose what type of certicate you want to generate. If you are creating a certicate
for testing applications then you should choose iOS App Development but if you are preparing an
application for distribution on the App Store then you should choose App Store and Ad Hoc.
513
You will also notice some other options on this screen. You will need to create separate certicates if
you want to use things like the Apple Push Notications service or WatchKit, but we wont require any of
those.
It will now ask you for the certicate signing request you just created with Keychain Access, click
continue and then upload the signing request. Once you have selected the .certSigningRequest
le click Generate.
You will now be able to download your certicate. Download it to somewhere safe and then open it to
install it.
514
Create an Identier
If youre managing your app using XCode then you dont necessarily need to worry about these step as
XCode can use a Wildcard App ID, but you can also create your own App ID manually (the Bundle ID
created here will match what is in your cong.xml le). Heres how you do it:
* Fill in the App ID Description and then supply an Explicit App ID like the following:
This should match the id that you supply in your cong.xml le.
515
The App ID should now be registered and available to you to use in provisioning proles, which we will do
Before you create a provisioning prole you will need to add devices that the application can run on.
Click on Devices and then click the + button in the top right:
* Supply the name and UDID (you can nd this for your device in iTunes) of the device you want to register
*Go to the provisioning proles screen and click the + icon in the top right
* Choose iOS App Development if you are creating a provisioning prole for testing, or App Store if you
are creating a provisioning prole for distribution. Click Continue. * Select the App ID you just created
516
* Select the certicate you wish to use and click Continue:
* Select all the devices you want to run the application on and click Continue: * Provide a name for the
517
You should now be able to download your Provisioning Prole.
Generating a .p12
A .p12 le (along with the provisioning prole) is required along with the provisioning prole for services
like PhoneGap Build. Since you have a Mac this step is not really relevant to you, however you can still
use a service like PhoneGap Build if you would like (I wouldnt recommend it). If you do need to create a
Back in the rst step when we created the certicate signing request, I mentioned that a public and
private key pair would be generated in Keychain Access. Find this in the Keys section:
You will see now that the private key also has the certicate you created in the iOS Member Center attached
to it.
518
Highlight both the private key and the certicate, right click and choose Export 2 items:
* You will then be asked where you would like to save the le and you may choose to enter a password for
the .p12 le or leave it blank. You may also be asked to enter in the Admin password for your computer.
To complete the following steps you will need to download and install OpenSSL. Visit the OpenSSL website:
https://www.openssl.org/source/
Once you have OpenSSL set up we are going to use it to generate a Certicate Signing Request. This will
be used in the iOS Member Center to create either our development or distribution certicate.
Change your directory to the OpenSSL bin directory in the command prompt, e.g:
cd c:/OpenSSL-Win64/bin
519
Make sure to replace the email address, name and country code with your own.
Create a Certicate
To complete these next steps you will need to log in to the Apple Member Center and go to the Certicates,
* Choose Certicates under iOS Apps * Click the + button in the top right corner:
Next you must choose what type of certicate you want to generate. If you are creating a certicate
for testing applications then you should choose iOS App Development but if you are preparing an
application for distribution on the App Store then you should choose App Store and Ad Hoc.
520
You will also notice some other options on this screen. You will need to create separate certicates if
you want to use things like the Apple Push Notications service or WatchKit, but we wont require any of
those.
It will now ask you for the certicate signing request you just created with OpenSSL, click continue
and then upload the signing request. Once you have selected the .certSigningRequest le click
Generate.
You will now be able to download your certicate. Download it to somewhere safe and then open it to install
it (for convenience, you should place it in your OpenSSL bin folder (e.g. OpenSSL-Win64/bin because
521
Create an Indentier
* Fill in the App ID Description and then supply an Explicit App ID like the following:
This should match the id that you supply in your cong.xml le.
The App ID should now be registered and available to you to use in provisioning proles, which we will do
522
Create a Provisioning Prole
Before you create a provisioning prole you will need to add devices that the application can run on.
Click on Devices and then click the + button in the top right:
* Supply the name and UDID (you can nd this for your device in iTunes) of the device you want to register
Go to the provisioning proles screen and click the + icon in the top right
* Choose iOS App Development if you are creating a provisioning prole for testing, or App Store if you
are creating a provisioning prole for distribution. Click Continue. * Select the App ID you just created
523
* Select all the devices you want to run the application on and click Continue * Provide a name for the
You should now be able to download your Provisioning Prole (again, download it into that same OpenSSL
524
Generating a .p12
Finally, to create the .p12 le we will use the certicate we downloaded from the Member Center and a
Use the key you generated right back at the beginning and the PEM le you just created to create
Theres a lot of things that can go wrong here and its easy to mess up. So if you have any trouble I
would suggest just restarting the whole process and slowly and carefully make sure you type everything
out correctly.
Congrats! You now have your .p12 le. Since you have both your provisioning prole and .p12 le now, if
you are using PhoneGap Build you will be able to attach these les to your application when you upload
it. Once you have done that you should be able to generate a signed .ipa le that can be installed on your
device (as long as you added your device and have followed all these steps correctly!).
525
Signing Android Applications on a Mac or PC
As I mentioned before, Android is a little dierent to iOS (and a bit easier fortunately). iOS requires that
you create certicates and provisioning proles for both development and production applications.
Android however, will let you install a debug application on a testing device without having to go through
this process. You will only need to sign your application when you want to release it on Google Play.
Android requires that you create a keystore le to sign your application with.
It is considerably easier to sign an Android application and, as I mentioned before, you dont even need
to sign the application if you just want to test it (only if you are releasing it to Google Play). To sign our
To sign applications we will be using a tool called keytool that comes with the Android SDK. If you do not
have the Android SDK set up on your machine already you can follow this guide:
http://ionicframework.com/docs/ionic-cli-faq/#android-sdk
Once you have the Android SDK set up, you can follow these steps to create a keystore le.
Run the following command to generate a keystore le with an alias of alias_name (you should
change this)
When you enter this command you will be prompted for a password - choose one and make sure to store
or remember it. You will then be asked a series of questions, most of which you can leave blank if you
You will now have a keystore le that you can use to sign your application (we will discuss how that works
526
later).
IMPORTANT: In order to update your application on the app store later you will need both the keystore
le, along with the alias name and password. If you lose any of these you will not be able to update the
application.
If you are using Facebook functionality that requires creating a Facebook application which you would have
done if you completed the CamperChat application in this book (only available in the Expert package) then
you will need to create a Key Hash from your keystore le to supply to Facebook for Android.
Doing that is simple enough, all you need to do is run the following command:
Make sure to replace alias_name with your own keystore les alias name and my-release-key.keystore
Once you have done this your Key Hash will be output to the terminal, and then you can simply copy it
527
Building for iOS & Android Using PhoneGap Build (without MAC)
There are two ways you can incorporate Cordova / PhoneGap into your applications, you can either use a
locally installed version through the command line or you can use the PhoneGap Build cloud service.
If you do not have a Mac and want to create iOS applications: use PhoneGap Build
The PhoneGap Build service will allow you to create 1 private application for free. You can keep deleting
and reusing this allowance to build multiple applications if you like, but if you ever want to manage multiple
applications in PhoneGap Build at the same time then you will need to purchase a subscription.
IMPORTANT: Unless you t into that 3rd option (No Mac but want to build for iOS) you should SKIP
THIS LESSON
PhoneGap Build is a great service, but you should always use a local installation of Cordova where possible.
Build times are quicker since you dont need to the PhoneGap Build server and then download the
You dont need to use your data to download and upload the les and you wont require an Internet
You have more control over the application (you have direct access to the native wrapper, rather
PhoneGap Build requires payment if you want more than 1 private repository
I did mention that PhoneGap Build can be used to build iOS applications without a Mac. It can also be
used to create Android applications without the Android SDK. This is why it doesnt matter what operating
system you are using, since all of the compilation happens on PhoneGap Builds servers.
528
When using PhoneGap Build you are able to upload your application code (purely web based code, i.e:
HTML, CSS and JavaScript) and have compiled application les returned for iOS and Android (as well as
for Windows Phone). The general process looks something like this:
2. Create a cong.xml le that tells PhoneGap Build how your application should be built
Compilation and the integration of SDKs are handled on their end, which makes this approach much
easier initially than getting everything set up correctly on your machine (but harder in the long run). Most
By this point you should have an application nished and ready to build, so lets walk through how to do
When uploading to PhoneGap Build we only want to include the built web les, that is everything inside of
the www folder in your project. All the rest of the les and folders are mainly for conguring Cordova and
the build process which we dont need when using PhoneGap Build.
Theres a couple of little changes we need to make before uploading to PhoneGap Build.
Create a cong.xml le
As you may already be aware, your project contains a cong.xml le that is used for conguring how your
application is built with Cordova. We still need this le, but the one required by PhoneGap Build is a bit
dierent.
529
WARNING: The next code block is huge and hard to read in this format, so if you prefer you can just copy
the code from the example PhoneGap Build application that came with your download pack.
<name>App Name</name>
<description>
Description
</description>
530
<preference name="prerendered-icon" value="true" />
<preference name="target-device" value="universal" />
<preference name="android-windowSoftInputMode" value="stateAlwaysHidden"
/>
<icon src="resources/android/icon/drawable-ldpi-icon.png"
gap:platform="android" gap:qualifier="ldpi"/>
<icon src="resources/android/icon/drawable-mdpi-icon.png"
gap:platform="android" gap:qualifier="mdpi"/>
<icon src="resources/android/icon/drawable-hdpi-icon.png"
gap:platform="android" gap:qualifier="hdpi"/>
<icon src="resources/android/icon/drawable-xhdpi-icon.png"
gap:platform="android" gap:qualifier="xhdpi"/>
<icon src="resources/android/icon/drawable-xxhdpi-icon.png"
gap:platform="android" gap:qualifier="xxhdpi"/>
<icon src="resources/android/icon/drawable-xxxhdpi-icon.png"
gap:platform="android" gap:qualifier="xxxhdpi"/>
531
<icon src="resources/ios/icon/icon-60.png" gap:platform="ios" width="60"
height="60"/>
<icon src="resources/ios/icon/icon-60@2x.png" gap:platform="ios"
width="120" height="120"/>
<icon src="resources/ios/icon/icon-60@3x.png" gap:platform="ios"
width="180" height="180"/>
<icon src="resources/ios/icon/icon-72.png" gap:platform="ios" width="72"
height="72"/>
<icon src="resources/ios/icon/icon-72@2x.png" gap:platform="ios"
width="144" height="144"/>
<icon src="resources/ios/icon/icon-76.png" gap:platform="ios" width="76"
height="76"/>
<icon src="resources/ios/icon/icon-76@2x.png" gap:platform="ios"
width="152" height="152"/>
<icon src="resources/ios/icon/icon-small.png" gap:platform="ios"
width="29" height="29"/>
<icon src="resources/ios/icon/icon-small@2x.png" gap:platform="ios"
width="58" height="58"/>
<gap:splash src="resources/android/splash/drawable-land-ldpi-screen.png"
gap:platform="android" gap:qualifier="land-ldpi"/>
<gap:splash src="resources/android/splash/drawable-land-mdpi-screen.png"
gap:platform="android" gap:qualifier="land-mdpi"/>
<gap:splash src="resources/android/splash/drawable-land-hdpi-screen.png"
gap:platform="android" gap:qualifier="land-hdpi"/>
<gap:splash src="resources/android/splash/drawable-land-xhdpi-screen.png"
gap:platform="android" gap:qualifier="land-xhdpi"/>
<gap:splash
src="resources/android/splash/drawable-land-xxhdpi-screen.png"
gap:platform="android" gap:qualifier="land-xxhdpi"/>
532
<gap:splash
src="resources/android/splash/drawable-land-xxxhdpi-screen.png"
gap:platform="android" gap:qualifier="land-xxxhdpi"/>
<gap:splash src="resources/android/splash/drawable-port-ldpi-screen.png"
gap:platform="android" gap:qualifier="port-ldpi"/>
<gap:splash src="resources/android/splash/drawable-port-mdpi-screen.png"
gap:platform="android" gap:qualifier="port-mdpi"/>
<gap:splash src="resources/android/splash/drawable-port-hdpi-screen.png"
gap:platform="android" gap:qualifier="port-hdpi"/>
<gap:splash src="resources/android/splash/drawable-port-xhdpi-screen.png"
gap:platform="android" gap:qualifier="port-xhdpi"/>
<gap:splash
src="resources/android/splash/drawable-port-xxhdpi-screen.png"
gap:platform="android" gap:qualifier="port-xxhdpi"/>
<gap:splash
src="resources/android/splash/drawable-port-xxxhdpi-screen.png"
gap:platform="android" gap:qualifier="port-xxxhdpi"/>
<gap:splash src="resources/ios/splash/Default-568h@2x~iphone.png"
gap:platform="ios" width="640" height="1136"/>
<gap:splash src="resources/ios/splash/Default-667h.png" width="750"
gap:platform="ios" height="1334"/>
<gap:splash src="resources/ios/splash/Default-736h.png" width="1242"
gap:platform="ios" height="2208"/>
<gap:splash src="resources/ios/splash/Default-Landscape-736h.png"
gap:platform="ios" width="2208" height="1242"/>
<gap:splash src="resources/ios/splash/Default-Landscape@2x~ipad.png"
gap:platform="ios" width="2048" height="1536"/>
<gap:splash src="resources/ios/splash/Default-Landscape~ipad.png"
gap:platform="ios" width="1024" height="768"/>
533
<gap:splash src="resources/ios/splash/Default-Portrait@2x~ipad.png"
gap:platform="ios" width="1536" height="2048"/>
<gap:splash src="resources/ios/splash/Default-Portrait~ipad.png"
gap:platform="ios" width="768" height="1024"/>
<gap:splash src="resources/ios/splash/Default@2x~iphone.png"
gap:platform="ios" width="640" height="960"/>
<gap:splash src="resources/ios/splash/Default~iphone.png"
gap:platform="ios" width="320" height="480"/>
<access origin="*"/>
</widget>
This le is specic for the Quicklists application - notice how weve got a few lines that include plugins?
The way plugins are included with PhoneGap Build is dierent to Cordova. You may remember at the start
to set up the plugins. This sets up the plugin locally in the project, but to use a plugin with PhoneGap Build
534
Although most are, not all plugins are available for PhoneGap Build. When adding the plugins for a project,
just search for the PhoneGap Build installation instructions, most plugins will include information on what
you need to include for PhoneGap Build. So if you are building any of the applications in this book with
PhoneGap Build, you should look in the Getting Ready section and make sure to include any plugins we
Also make sure you remember to replace the id at the top with your own Bundle ID, i.e: com.yourproject.yourname.
The other main dierence is the way the resources (splash screens and icons) are included. We also need
to add references to these to our cong.xml le and we need to copy over our resources because currently
they live outside of the www folder (so they wouldnt be included in our upload)
As well as copying over the resources, you should also save a blank le with the name .pgbomit inside
of the resources folder. This tells PhoneGap Build that the les in this folder should only be used for the
splash screens and icons, and shouldnt be made available in the application (which would take up a lot
of space).
Now that weve got everything ready, you just need to zip up the contents of the www folder (NOT the
www folder itself) and upload it to PhoneGap Build. If you do not already have an account you will have
Once you have an account simply create a new app and upload the .zip archive you just created and click
Ready to Build. The iOS build will fail initially but the Android version should succeed. This is because
you dont need to sign an Android application during development, but you do for iOS.
When youre building your release version, or if you want to download the iOS version, you will have to
535
upload the signing keys you created in the last chapters. This means you will need to attach the .p12 le
and the Provisioning Prole to your iOS build, and the keystore le to your Android build.
Once the applications have nished building, you will be able to download your .ipa le for iOS and a .apk
le for Android. We will discuss how to get these on the app stores in just a moment.
536
Submitting to the Apple App Store
The time has nally come to send our application o to apple! Before submitting your application make
sure that you familiarise yourself with the App Store Review Guidelines - if you dont comply with these
NOTE: Do not follow these steps until you have created your own application - You should not upload any
To submit an application to the App Store you will need to create an App Store Listing and of course upload
your application.
Lets start by walking through how to create an App Store Listing. Theres quite a few things you need to
Go to My Apps
Select the + icon on the left and then choose New iOS App
Fill out the details on this prompt and then click Create:
537
If youre using XCode to submit your application you can use Xcode iOS Wildcard App ID, or otherwise
you can choose the specic Bundle ID you created for your application in the iOS Certicates lesson. The
SKU does not need to be anything specic, it is just for your own reference and the Bundle ID Sux
538
* Open your new application in iTunes Connect and you should see a dashboard like this:
539
* Fill out all the information on this page (including multiple screenshots for the dierent sized devices)
NOTE: You will notice a Build section on this page. You will have to come back to this section after you have
uploaded your application (which we will do in the next section) and select the build you have uploaded.
540
If you want to release a paid application you will need to have accepted additional agreements within
iTunes Connect.
Theres a few ways to do this and the way you do it will depend on what format your application is in and
what operating system you are using. To submit an iOS application for distribution through the app store
you will need your app signed with a distribution certicate, and again, the way you do this will depend on
Once you upload your application you should be able to see it in the Build section in iTunes Connect and
If you have a Mac then you can use XCode to submit the application, which is a pretty straightforward way
to do it. If you do not have a Mac and instead have a .ipa le generated from using PhoneGap Build you
Before continuing you should run the following command within your project directory:
If you have a .xcodeproj le (which will be generated when you run the build command) you will rst need
to generate a .xcarchive le from it. To do that then you will need to follow these steps:
clicking it
Go to Product > Scheme > Edit Scheme and make sure that the archive is set to a Release
conguration:
541
* Make sure you have iOS Device or Generic iOS Device selected in the top toolbar, not an emulator:
Uploading a .xcarchive le
If you want to submit a .xcarchive rst double click it to open this screen in XCode (this screen should
542
First you will want to choose your archive and then click the Validate button to make sure that everything
is set up correctly. You should be given the option to choose your account:
and then you will see your application displayed. Click Validate and if everything is set up correctly you
543
If validation does not work, make sure:
Once you have successfully validated your project click Done and then choose Submit to App Store
You will now go through that same process again, except this time you will choose Submit. Once you click
544
Submitting an app using Application Loader
If you do not have a Mac then your only option to submit an application is to submit the built and signed
.ipa le. Remember, this .ipa le should be signed with a distribution certicate instead of a development
If you do not already have a signed .ipa le, make sure to read the PhoneGap Build lesson.
You can use a program called Application Loader to submit a .ipa le to iTunes Connect, but unfortunately
this program is only available on Macs. Literally the only thing you cant do when building iOS applications
There are ways around this though, and my two favourites are:
Borrow a friends Mac. You will only need it for about 5 minutes, so if you know anyone who has a
Mac just shove your .ipa le on a USB stick, download Application Loader on their Mac and upload
545
your application
Macincloud.com allows you to log in remotely to a Mac. This service does cost, but if you just buy
some prepaid credits its pretty cheap and since you will only ever be on there for a few minutes at
a time the credit will last ages (this is what I did before I got a Mac).
Once youve gured out how youre going to access Application Loader, open it up and log into your iOS
* Upload the .ipa le that is signed with a distribution certicate and then click Next
546
Submit for Review
Once you have uploaded your application, either through XCode or Application Loader, you will need to
nish up your app store listing in iTunes Connect. Go back to your application in iTunes Connect and go
You should now see a + icon as shown above. Click this, select the build you just uploaded and hit Done:
547
Double check everything in your listing, then go back to the top of the page, hit Save and then Submit
for Review:
to submit your application to Apple. Now you just have to cross your ngers and wait! The Apple review
process usually takes around 5-10 days, which is a frustratingly long time. Theres nothing you can do
about it though but sit back and wait. Just make sure that you are complying with all of Apples rules and
guidelines so that your app does not get rejected (otherwise you will have to x it and wait another 5-10
days!).
548
Submitting to Google Play
If youve been through the nightmare of signing an iOS application and submitting it to the Apple App store
then you are in for a treat. Submitting to Google Play is super easy by comparison. Before you get started,
Remember, before submitting to Google Play you must sign your .apk with a keystore le.
IMPORTANT: If you are using the Crosswalk plugin, then you will have two .apk les generated when you
build. The process for submitting is still mostly the same, but make sure you read the note at the end of
this lesson about how you can upload both .apk les with the same submission.
Unlike with iOS, it doesnt matter whether you have a Mac or PC, you will be able to use the same method
for both. However, if you are using a PC and are also building an iOS application then that means you will
have used PhoneGap Build to do that, since you are already using PhoneGap Build then it makes sense to
use it for Android to. So I would recommend using PhoneGap Build to build for Android if you dont have
If you are using PhoneGap Build then you will already have a signed .apk le, in which case you can skip
to the next section Submitting your Application to Google Play. If you do not already have a signed .apk
storeFile=snapaday-release.keystore
keyAlias=snapaday
This le tells the build process how we want the application to be signed. Here you will supply the keystore
le you generated in the signing lesson, as well as the alias of the keystore. The rst line should be the
path to where the keystore le is stored, for simplicity I move the keystore le to the same location as this
le, however you can also specify a dierent path if you want. The second line is the alias name.
549
Once you have that le in place, all you need to do is run the following command:
and your .apk le (or les if you are using Crosswalk, more on that soon) will be generated for you at:
platforms/android/build/outputs/apk/
* Fill in the information on this prompt and then click Upload APK
550
* You should see a page like the following:
* Click Upload your rst APK to Production and upload the signed .apk le you created in Lesson 2
551
* Now click on Store Listing and ll in all the information including screenshots and promotional graphics
* Next go to Content Rating and create a content rating for your application then click Save Draft
552
* Go to Pricing & Distribution and ll out the information there then click Save Draft
Once you have lled out all of the screens just hit Publish App and your application will be submitted!
553
Unlike the Apple App store, your application should be available on Google Play within hours.
The interesting thing about using Crosswalk is that it creates two separate .apk les, which .apk le is
required for each user depends on which device they have. If you take a look in your platforms/an-
droid/build/outputs/apk/ folder, you will nd the following two release .apk les:
android-armv7-release.apk
android-x86-release.apk
This leaves us with a bit of a dilemma - which one do you submit to the app store?
You may read on forums about combining the two .apk les into one, but fortunately the problem is quite
easy to solve.
Although it may not seem like it you can actually upload two dierent .apk les, both the armv7 and x-86
versions, for your app submission (although the ability to do this is hidden slightly).
If youve uploaded apps to the app store before, you may know that whenever you upload a new version
of the app it needs to have a higher version number than the previously uploaded application (which we
can set in the cong.xml le). This is how you update your application after already having submitted it,
you increase the version number in the cong.xml le, rebuild, and then resubmit.
Im going to show you how you can upload multiple .apk les for the same submission though - just follow
these steps:
Click on the Upload APK button to upload your rst .apk le, either android-armv7-release.apk or
Once that has nished uploading click [Switch to Advanced Mode] in the top right corner of the
screen:
554
Once you have switched to advanced mode, click the upload button again and upload your second .apk
le.
You should now have both .apk les uploaded, and be able to see them listed. You will notice that they
have the same version number but a dierent version code - one version code must be higher than the
other in order for this to work - fortunately crosswalk handles this for us automatically.
555
Updating on the App Stores
You thought you were done? Almost, but theres one more important topic we need to cover.
Once youve got your application live on the app store, it probably wont be too long before you want to
make an update - to add some new feature or perhaps to x a bug that found its way into the production
application.
Fortunately, updating your application is really easy. All you have to do is modify your cong.xml le and
In the example above Ive updated the version to 1.0.1 but you could also use 1.1.0 or 2.0.0, it doesnt
matter just as long as the version number is higher than the one you submitted previously.
Then all you have to do is rebuild your application using the exact same steps as you used before, except
obviously it will be a bit easier this time because you dont have to generate the certicates and so on
again. In fact its important that you use the same details to sign the application again, otherwise it wont
work.
You also wont have to recreate your app store listings of course. In the case of iOS you will just need to
log in to manage your existing application in iTunes Connect and upload and then submit a new build. You
556
and you should choose the iOS option. Enter in the version number that matches the new version in your
cong.xml le and update any elds in the listing that need to be updated. Upload your new build using
XCode or Application Loader just like before, and once again add the new build to the app store listing:
557
Once you have uploaded the new build you can submit it for review again.
For Android you will just need to go to your existing application in the Google Play developer console and
update any elds that need updating. You should then go to the APK section of the listing and upload your
new .apk le (make sure to upload both .apk les if you are using Crosswalk) and then hit Publish now to
Production.
New builds you submit will still need to go through the review process, so for iOS this is going to take
about a week again and for Google Play it will be a few hours.
If youve made it through this course (and even if you havent yet) I would like to give you a huge
Thank you!
Not only have you invested your time and money into learning HTML5 mobile application development
(which I think is a very wise choice), youve also invested time and money into me. I love teaching devel-
opers HTML5 mobile development and it means a lot to me to know that you trust in my abilities enough
to buy this book, and by doing that youre also allowing me to invest more time into creating even more
I assume if youve completed this course then youre probably familiar with my blog, but if youre not make
sure to check it out. I release free HTML5 mobile development tutorials there every week, and if youre
looking to expand your Ionic skills even more theres plenty to check out.
I hope you enjoyed this course and have taken a lot away from it. Im always looking for feedback on how
to continually improve though so please send me an email with any feedback you have.
If you need help with anything related to HTML5 mobile development feel free to get in touch. I respond
to as many requests for help as I can but I receive a lot of emails so I cant always get to them all. If you
want more personal and guaranteed support, I also oer consulting services.
Also feel free to send me an email just to let me know what youre working on, I always like hearing what
558