Ionic 5
Ionic 5
www.dunddverlag.de
ISBN (Print): 978-3-945102-54-1
Preface
How do I get to write this book?
“Ionic 5 - Creating awesome apps for Android, iOS, Desktop and Web” is my third
book about the excellent Ionic framework for developing mobile apps. My own
search for specialized literature on this topic was sobering: the few things there are
to be found in the book trade are outdated. That's when I realized: the book I'm
looking for has yet to be written!
Just like my first books “Ionic 5” addresses new or emerging software developers
who previously had little or nothing to do with programming apps or worked with
other tools and frameworks and want to build really cool apps in a simple way. If
you feel addressed, this book is for you!
The book spans from the idea of the popular app framework Ionic and its installa-
tion to the realization of a complete app including its publication in Google Play
and Apple's App Store. It offers you a comprehensive introduction to Ionic. With
in-depth background knowledge, for example, on Angular, Cordova or JavaScript,
I'll hold myself back; there is already good literature about it. A small exception is
the second chapter "Angular Essentials", in which I briefly describe the essential
concepts and structures of Angular. The one who would like to know more, I'll offer
further links in suitable places.
For each chapter I dedicate an own aspect of Ionic and gradually add new func-
tionalities to an initially simple app called "BoB Tours". If you accompany me, at the
end of this book, you not only learned to know and use the most important fea-
tures of Ionic, but also understood how it works in context. With that you finally
have the necessary knowledge to be able to develop your own awesome apps
with Ionic.
If you find a chapter less exciting or want to skip for any other reason, you can
download the source code of a chapter from the book's website (see "The book's
website" on page 20) and continue working on the following chapter.
1
Ionic 5 • Preface
... Simon Grimm for his great project "Ionic Academy" (https://ionicacademy.com),
whose visit I highly recommend to any new Ionite.
... Paul Halliday, Josh Morony, Max Schwarzmüller, Jorge Vegara, Sani Yusuf and all
the other passionate authors of excellent Ionic tutorials.
... the team of the GFU Cyrus AG in Cologne, Germany, which was infected by my
enthusiasm for Ionic and spontaneously integrated me into their seminar program
with this topic.
… the readers of my books for the precious feedback that motivated me to stay
tuned.
... you and all other readers of this book. Feel free to give me precious feedback
this time, too.
... finally and emphatically with my loved ones for their patience and consideration
when I worked hours and hours on this book and for their amazing leniency, when
I talked about Ionic, Apps & Co. no less often and always long lectured. What they
have left behind them, you as a reader can now look forward to ;-)
2
Ionic 5 • Preface
3
Ionic 5 • Preface
4
Ionic 5 • 1 Introduction
1 Introduction
1.1 The idea behind Ionic
The creators of Ionic didn't reinvent the wheel. The made it even rounder!
The Ionic framework with its functions and components gives us a clear direction to
a straightforward app development for Android, iOS and the web – and – if you
like it – for the desktop and Windows 10 Universal, too. Ionic itself takes mainly
established and very well proven frameworks and forges it together to a powerful
Software Development Kit (SDK).
Let me shortly introduce the most important frameworks and modules under the
hood of Ionic:
Angular
Angular (formerly known as AngularJS) is a JavaScript framework for creating sin-
gle page web applications. It is developed by a community of individuals and
companies, led by Google, and published as open source software.
Angular has data binding as an automatic way of updating the view, controllers to
manage the DOM, directives to establish new HTML syntax, reusable components,
form validation and much more.
Ionic has a specific @ionic/angular package that helps with integration with Angu-
lar. In the chapter "Angular Essentials" (starting from page 25) I describe the most
important concepts of this powerful framework.
https://angular.io
5
Ionic 5 • 1 Introduction
App-Scripts
When ionic serve or ionic cordova run are invoked, it ultimately calls Node
Package Manager (npm) scripts. These npm scripts call the @ionic/app-scripts li-
brary to execute the build process.
Out of the box, Ionic starters have been preconfigured with great defaults for
building fast apps, including:
• Multi-core processing tasks in parallel for faster builds
• In-memory file transpiling and bundling
• Transpiling source code to ES5 JavaScript
• Ahead of Time (AoT) template compiling
• Just in Time (JiT) template compiling
• Template inlining for JiT builds
• Bundling modules for faster runtime execution
• Tree-shaking unused components and dead-code removal
• Generating CSS from bundled component Sass files
• Auto-prefixing vendor CSS prefixes
• Minifying JavaScript files
• Compressing CSS files
• Copying src static assets to www
• Linting source files
• Watching source files for live-reloading
Just the bullet list above is a little overwhelming, and each task requires quite a bit
of development time just to get started. Ionic App Script's intention is to make it
easier to complete common tasks so developers can focus on building their app,
rather than building build scripts.
https://github.com/ionic-team/ionic-app-scripts
6
Ionic 5 • 1 Introduction
Autoprefixer
Autoprefixer is a tool that adds vendor-specific-prefixes to hand-written Sass/CSS
code. This ensures that standardized CSS rules you write will be applied across all
supporting browsers. Let's have a look at an example.
You write:
.example {
display: grid;
transition: all .5s;
user-select: none;
background: linear-gradient(to bottom, white, black);
}
https://github.com/postcss/autoprefixer#browsers
7
Ionic 5 • 1 Introduction
Closure Compiler
Google's Closure Compiler is a tool for making JavaScript download and run faster.
Instead of compiling from a source language to machine code, it compiles from
JavaScript to better JavaScript. It parses your JavaScript, analyzes it, removes dead
code and rewrites and minimizes what's left. It also checks syntax, variable refer-
ences and types, and warns about common JavaScript pitfalls.
What are the benefits of using Closure Compiler?
• Efficiency. The Closure Compiler reduces the size of your JavaScript files and
makes them more efficient, helping your application to load faster and reducing
your bandwidth needs.
• Code checking. The Closure Compiler provides warnings for illegal JavaScript
and warnings for potentially dangerous operations, helping you to produce
JavaScript that is less buggy and easier to maintain.
More about Google's Closure Compiler you can find here:
https://developers.google.com/closure/compiler/
8
Ionic 5 • 1 Introduction
Ionic apps use Cordova plugins to provide near-hardware functionalities like cam-
era, accelerometer or geolocation. Ionic calls its curated set of Cordova plugins
Ionic Native. I dedicated the chapter 9 to the topic "Ionic Native" (starting on page
393).
Capacitor is Ionic's “spiritual successor” to Cordova, focused entirely on enabling
modern web apps to run on all major platforms with ease. I've dedicated a section
in the bonus chapter to Capacitor (starting from page 565).
https://ionicframework.com/docs/native/
https://capacitor.ionicframework.com/
https://cordova.apache.org
9
Ionic 5 • 1 Introduction
Git
Git is a free and open source distributed version control system for managing
code. It allows development teams to contribute code to the same project without
causing code conflicts.
Git is easy to learn and has a tiny footprint with fast performance. It outclasses
SCM tools like Subversion, CVS, Perforce, and ClearCase with features like cheap
local branching, convenient staging areas, and multiple workflows.
Git works directly with Ionic Appflow (see “12.9 Ionic Appflow“ on page 553).
Appflow uses your code base as the source of truth for Deploy and Package
builds. In order for Appflow to access your code, you can choose to integrate di-
rectly using a hosting service like Github or Bitbucket, or you can push your code
directly to Appflow.
More about Git and related topics you can find here:
https://git-scm.com
https://git-scm.com/book/en/v2
https://github.com
https://bitbucket.org
10
Ionic 5 • 1 Introduction
Node.js
Node.js is a runtime environment that allows JavaScript to be written on the
server-side. In addition to being used for web services, node is often used to build
developer tools, such as the Ionic CLI.
With Node.js you can also realize web servers. Node.js runs in the JavaScript run-
time environment "V8", originally developed for Google Chrome, and provides a
resource-efficient architecture that enables a particularly large number of concur-
rent network connections.
Let's have a look at an HTTP server version of a hello world program in Node.js
using text/html as Content-Type header and port 3000 :
var http = require('http');
On the Web you'll find interesting tutorials on full-stack projects, which were real-
ized with Firebase or MongoDB as backend, a Node.js server as middleware and
Ionic as frontend, for example (see link below).
More about Node.js and related topics you can find here:
https://nodejs.org
https://www.joshmorony.com/integrating-an-ionic-application-with-a-
nodejs-backend/
11
Ionic 5 • 1 Introduction
Stencil (new)
Stencil is a Web Component compiler built by the Ionic Framework team to help
build faster, more capable components that work across all major frameworks.
Web components are a set of web platform APIs that allow you to create new cus-
tom, reusable, encapsulated HTML tags to use in web pages and web apps. They
are based on existing web standards.
Stencil combines the concepts of popular frameworks into a simple build-time tool.
https://stenciljs.com
https://github.com/ionic-team/stencil
12
Ionic 5 • 1 Introduction
TypeScript
TypeScript is a superset of JavaScript, which means it gives you JavaScript, along
with a number of extra features such as type declarations and interfaces. Although
Ionic is built with TypeScript, using it to build an Ionic app is completely optional -
but recommended.
The first publicly available version of TypeScript was released in 2012 after two
years of development by Microsoft in version 0.8. Shortly after the language was
announced, it was praised by Miguel de Icaza. However, he complained that there
were no other development environments apart from Microsoft Visual Studio,
which wasn't available for Linux and MacOS in 2013. Since 2013 there was plug-
in support for Eclipse. Meanwhile, a variety of text editors and development envi-
ronments support TypeScript. These include Emacs, vim, Sublime Text, WebStorm,
Atom and Microsoft's own Visual Studio Code editor. The latter I'll use in this book
to develop Ionic apps.
TypeScript doesn't render JavaScript a static typed language, but allows strong typ
ing. With this, variables and methods can be typed, whereupon certain errors can
be detected even at compile time.
The basic types in TypeScript are: Any, Array, Boolean, Enum, Never, Null,
Number, Object, String, Tuple, Undefined, Void .
https://www.typescriptlang.org
http://www.typescriptlang.org/docs/handbook/basic-types.html
13
Ionic 5 • 1 Introduction
Webpack
Webpack bundles together JavaScript modules and other assets. It can be used to
create single or multiple "chunks" that are only loaded when needed. Webpack can
be used to take many files and dependencies and bundle them into one file, or
other types.
Webpack is designed primarily for JavaScript, but can convert front-end elements
such as HTML, CSS, and even images, if the corresponding plug-ins are included.
https://webpack.js.org
14
Ionic 5 • 1 Introduction
1.2 Installations
15
Ionic 5 • 1 Introduction
Ionic CLI
Now we install the Ionic command-line utility (CLI). It supports us with a bunch of
dev tools and help options to create and develop our applications. Install the Ionic
CLI globally with npm:
$ npm install -g @ionic/cli
Note: The -g means it is a global install. For Window’s it's recommended to open
an Admin command prompt. Mac/Linux users should use sudo for installation.
In the course of this book we will get to know the most important commands of
the Ionic CLI. To give you an overview in advance, you can enter the following af-
ter installing the CLI in the terminal:
$ ionic --help
More about the Ionic CLI (including a full command reference) you can find here:
https://ionicframework.com/docs/cli
16
Ionic 5 • 1 Introduction
https://code.visualstudio.com/
It's the Integrated Development Environment (IDE) I use in this book. There are IDE
alternatives like the “big” Microsoft Visual Studio, Atom and Eclipse or, of course,
Ionic's own new Ionic Studio (https://ionicframework.com/studio).
Since many illustrations and explanations in this book are based on VSC, however,
I recommend that you also use VSC if you want to better follow the programming
in this book and avoid (visual) deviations.
After the installation and the first call of VSC you should activate the integrated ter-
minal (menu View > Terminal ). So you can code and use the Ionic CLI in one
interface.
17
Ionic 5 • 1 Introduction
Google Chrome
For developing you can choose whatever browser you want. But I recommend to
use Google Chrome. Its built-in Developer Tools will give us great support while
developing, debugging and testing Ionic applications.
https://www.google.com/chrome
https://www.google.com/chrome/dev
18
Ionic 5 • 1 Introduction
19
Ionic 5 • 1 Introduction
If it happens that you read my book in a few months, install the latest version of
Ionic and notice at some point that the code used here for some reason doesn't
work, take a look at the forum of the website. If necessary, updated hints are given
for each chapter. Otherwise, don't be afraid to post a question in the forum. The
reader community and I will do our best to answer as soon as possible.
https://ionic.andreas-dormann.de/
20
Ionic 5 • 1 Introduction
Our app can be characterized as follows: "BoB Tours" is the app of an imaginary
tourism company called "Best of Bonn Tours GmbH", in short: "BoB Tours", that
offers city tours, biking, hiking and segway trips around my beautiful home of
Bonn.
In the app, the tour offer can be searched for regions (e.g., "Bonn City" or "Bonn's
Surrounding Areas") and tour types (e.g., “Round Trips” or "Segway Tours") as well
as for a price range. If you have found an interesting tour, you can view their de -
tails together with a photo and save them as a favorite.
21
Ionic 5 • 1 Introduction
The starting point and the route from the current location to the starting point of
each tour can be displayed in a map. Finally, you can formulate and send a book -
ing request directly to the tourism company from the app. Of course the form will
be validated.
Our app should make a good impression on a smartphone, in the browser and on
a tablet as well. Therefore, we will also turn to the aspect of "responsibility".
In the user settings, you can finally switch between different color designs.
You see, the whole thing is already a small real life app. In doing so, we will en-
counter a number of typical programming situations, as in the real life of an app
developer.
22
Ionic 5 • 1 Introduction
Incidentally, I got inspiration for this app from some of the interesting tour offers on
bonntouren.de. I can personally recommend the charming owner Soledad Sichert
as an excellent tour guide!
If you have completed this book and your app successfully, you should donate
you and your loved ones a trip to Germany to Bonn's Rhineland and one of the
exciting Bonn tours ;-)
https://ionic.andreas-dormann.de/demo
23
Ionic 5 • 1 Introduction
Summary
In this chapter, you got to know the idea behind Ionic. You now know that Ionic
has many specialized single frameworks under its hood.
You also did all the installations necessary to work with Ionic: Node and npm, Ionic
CLI, Microsoft Visual Studio Code and Google Chrome with its Dev Tools.
I finally introduced you to the book website and our app "Bob Tours".
24
Ionic 5 • 2 Angular Essentials
2 Angular Essentials
2.1 What is Angular?
Preliminary remarks
Client-side programming has become extremely complex in recent times. The re-
quirements for a web application are now just as high as for a desktop application
- if not higher. The desire for a framework that enables the developer to cover all
these requirements on the web is thus getting bigger.
Angular is a client-side framework that makes it possible to create web applications.
It helps the developer to bring known and new architectural concepts to the client
and develop complex applications. The work can be aligned not only on the serv-
er, but via JavaScript on the client. The application can fulfill modern architectural
concepts by separating the responsibilities and is thus more maintainable and
testable.
Ionic uses Angular since Ionic 2 (and its predecessor AngularJS since Ionic 1).
Therefore, it makes sense to be proficient in Angular to develop good Ionic apps.
We also use Angular with the current Ionic 5 in this book.
It should not go unmentioned that Ionic 5 also cooperates with other frameworks. I
have dedicated this topic to the bonus chapter “Ionic and other frameworks” (start-
ing on page 561).
25
Ionic 5 • 2 Angular Essentials
Angular makes it possible to communicate with components, feed them with data
and receive events from components; it makes components reusable and more
isolated. For example, the flexibility to separately develop the view as HTML allows
different teams to work on logic, architecture, and design.
In order to achieve the greatest possible flexibility and to be broadly positioned,
Angular draws on further projects and their functionality, such as RxJS . The integra-
tion of third-party libraries is therefore simple and simple, which means you can
extend your application to almost any JavaScript library and use its flexibility.
This allows you to create complete applications that are bigger and more powerful
than a "normal" website: A web application that has the same rights and obligations
(architecture, testability, etc.) as an application in a familiar language, such as the
desktop.
In this chapter I describe the most important concepts and structures of Angular,
which can be useful for you in the realization of your Ionic Apps.
26
Ionic 5 • 2 Angular Essentials
2.2 Components
With Angular, there is no direct manipulation of the HTML-DOM, but a separation
according to the model-view-controller (MVC) pattern: In the view, the developer
defines so-called templates that connect static HTML tags with dynamic contents
from the controller. This mix allows the framework through custom HTML syntax
elements ("directives").
In the template for the tour list in Listing 2 you can see a custom tag again with
<TourDetails> . This is a child component that interacts with the parent compo-
nent through properties and events. Using the @Input() a n d @Output()
decorators, the developer must clearly define which properties and events are ac-
cessible to other components. For event communication Angular offers its own
event emitter.
A component triggers various events throughout its lifecycle that the developer can
hook into. Listing 1 shows this with the example of the event OnInit , which from
the point of view of TypeScript is a class that, like all other types, has to be inte-
grated with import {OnInit, ...} from '@angular/core' . Other events are
related to data binding (ngOnChanges , ngDoCheck ).
27
Ionic 5 • 2 Angular Essentials
@Component({
selector: 'TourList',
templateUrl: 'App/TourList/TourList.html',
providers: [BookingService]
})
export class TourList implements OnInit {
status: string;
tours: Tour[];
ngOnInit() {
this.bookingService.getAll(data => { // Callback
this.tours = this.bookingService.tours;
this.status = this.tours.length + " tours booked.";
});
}
Delete(tour: Tour) {
this.bookingService.delete(tour); // Call service
this.status = `Tour ${tour.ID} deleted!`;
}
Change(tour: Tour) {
let link = ['/edit', tour.ID];
this.router.navigate(link);
}
28
Ionic 5 • 2 Angular Essentials
<ul>
<li *ngFor="let t of tours;
let isEven = even;
let i = index">
<span [ngClass]="{'text-primary': isEven,
'text-info': !isEven}">
<span class="badge">
{{i+1}}: Tour #{{t.ID | fixedLenNumber:3}}
</span>
<TourDetails [tour]="t"
(tourDeletedEvent)="onTourDeleted($event)">
</TourDetails>
<button type="info"
*ngIf="t.FreeSeats > 0"
(click)="Change(t)">
Change
</button>
<button type="warning"
[disabled]="t.FreeSeats <= 0"
(click)="Delete(t)">
Delete
</button>
</span>
</li>
</ul>
Don't worry if this seems very abstract to you. From the next chapter we will cre-
ate many components (pages) ourselves. And with the help of Ionic, the whole
thing will be very easy for you.
Further informations about this topic you can find in the official Angular documen-
tation:
https://angular.io/guide/architecture-components
29
Ionic 5 • 2 Angular Essentials
30
Ionic 5 • 2 Angular Essentials
We distinguish between hooks for the component itself (red and blue) and hooks
for their children (green).
ngOnChanges
is invoked every time there is a change in one of the input properties of a compo-
nent.
ngOnInit
is invoked when a component has been initialized. This hook is only called once af-
ter the first ngOnChanges
ngDoCheck
ngOnDestroy
This method will be invoked just before Angular destroys a component. Use this
hook to unsubscribe Observables (see “2.10 Observables”, starting from page 46)
and detach event handlers to avoid memory leaks.
is invoked after Angular performs any Projection (see “2.6 Content Projection” on
page 39) into a components view.
ngAfterContentChecked
is invoked each time the content of the given component has been checked by
the change detection mechanism of Angular.
31
Ionic 5 • 2 Angular Essentials
ngAfterViewInit
is invoked when a component’s view has been fully initialized.
ngAfterViewChecked
is invoked each time the view of the given component has been checked by the
change detection mechanism of Angular.
Code example
In this code I show a typical use of the constructor and ngOnInit hook. Both will
be used regularly throughout this book. They're Angular's bread and butter hooks.
import { Component } from '@angular/core';
@Component({
selector: 'actors-data',
templateUrl: 'actors-data.html',
})
export class ActorsData {
myStar;
constructor() {
console.log('constructor – My star is $(this.myStar)');
myStar = 'Julia Roberts';
}
ngOnInit() {
console.log('ngOnInit – My star is $(this.myStar)');
}
32
Ionic 5 • 2 Angular Essentials
At the constructor's console log output the property myStar is undefined. The val-
ue is assigned after the console statement. By the time the ngOnInit hook is called
we can see that the property myStar is now set to the given value.
More detailed information about Lifecycle Hooks you can find here:
https://angular.io/guide/lifecycle-hooks
https://ionicframework.com/docs/lifecycle/angular
33
Ionic 5 • 2 Angular Essentials
class Environment
{
Client c = new Client();
c.Process();
}
class Client
{
public void Operation()
{
Service d = new Service();
d.Action();
}
}
class Service
{
public void Action()
{
...
}
}
34
Ionic 5 • 2 Angular Essentials
class Environment
{
Client c = new Client(new Service());
c.Process();
}
class Client
{
Service d;
class Service
{
public void Action()
{
...
}
}
Further informations about this topic you can find in the official Angular documen-
tation:
https://angular.io/guide/dependency-injection
35
Ionic 5 • 2 Angular Essentials
Routes
A route simply defines an URL path and a component to display it's content.
There are three main types of routes that you will use frequently:
// Redirect
{ path: 'here', redirectTo: 'there', pathMatch: 'full' }
];
src/app/app-routing.module.ts
The root configuration for an Ionic application router lives in the src/app/ap-
p-routing.module.ts file. Here you define your routes. For pages that you
generate with the Ionic CLI (ionic generate page... ) all corresponding routes
are defined automatically. For example see the route for a page named Fa-
vorites :
{
path: 'favorites',
loadChildren:()=>import('./pages/favorites/favorites.module')
.then(m => m.FavoritesPageModule)
}
36
Ionic 5 • 2 Angular Essentials
many components). The basic idea of lazy loading is that it breaks your application
down into smaller chunks, and only loads what is necessary at the time. When
your application is first loaded, only the components that are necessary to display
the first screen need to be loaded. This is especially beneficial for Progressive Web
Apps where page load times are critical. This magic is made possible with
Webpack Code Splitting.
With lazy loading, your application can boot much faster because it doesn’t need
to load much – you could have a huge application, with 50 pages, but it could still
load just as fast as an application with just 2 pages.
A lazy loaded route renders the FavoritesPageModule where the so called out-
let is defined in the HTML in the app.component.html :
<ion-router-outlet></ion-router-outlet>
When you navigate to the /favorites path in the browser, it will render the spe-
cific component in the HTML outlet.
Ionic's own router outlet implementation <ion-router-outlet> (basically, you just
plop the router outlet wherever you want the component for the active route to
be displayed) is mostly the same as Angular’s <router-outlet> except that it will
automatically apply the screen transition animations with a “direction” (e.g. a for-
ward navigation will animate the new screen in from the right).
ActivatedRoute
Angular has an ActivatedRoute to bring data from one page to another. In other
words: an ActivatedRoute contains the information about a route associated with
a component (page) loaded in an outlet.
Example: Data is sent from a calling page e.g. via HTML like
[routerLink]="['/target', data]"
@Component({
selector: 'app-target',
37
Ionic 5 • 2 Angular Essentials
templateUrl: './target.page.html',
styleUrls: ['./target.page.scss'],
})
export class TargetPage implements OnInit {
incoming_data = null;
ngOnInit() {
this.incoming_data = this.activatedRoute.snapshot.params;
}
https://angular.io/api/router/ActivatedRoute
38
Ionic 5 • 2 Angular Essentials
Angular automatically pulls the value of the title and myStar properties from the
component and inserts those values into the browser. Angular updates the display
when these properties change. More precisely, the redisplay occurs after some
kind of asynchronous event related to the view, such as a keystroke, a timer com-
pletion, or a response to an HTTP request.
When you bootstrap with the AppComponent class (in main.ts ), Angular looks for
an <app-root> in the index.html , finds it, instantiates an instance of AppCompo-
nent, and renders it inside the <app-root> tag.
Now run the app. It should display the title and star name:
https://angular.io/guide/displaying-data
39
Ionic 5 • 2 Angular Essentials
2.7 *ngFor
One very often used Angular directive is ngFor . It's a “repeater” directive and
helps you to iterate through an array of objects.
Remember the example class PrettyWoman in the previous division “2.6 Content
Projection“. Here is a short code example that uses the HTML unordered list with
<ul> and <li> tags and Content Projection for showing a list of actors in “Pretty
Woman”:
<ul>
<li *ngFor=”let actor of actors”>
{{ actor }}
</li>
</ul>
You should never forget the leading asterisk (*) in *ngFor . It's an essential part of
the syntax.
Further informations about this topic you can find in the Angular documentation:
https://angular.io/guide/displaying-data
40
Ionic 5 • 2 Angular Essentials
2.8 Pipes
Angular pipes enable data to be transformed and/or formatted in the DOM before
rendering (for strings, for example). Often, with regard to the user experience, you
don't want to display the data within an app one to one, even in the view. Imagine
we have an actor object like this:
import { Component } from '@angular/core';
@Component({
selector: 'actor-data',
templateUrl: 'actor-data.html',
})
export class ActorData {
actor = {
name: 'Julia Roberts',
birthday: new Date(1967, 10, 28)
}
In the HTML file we use Content Projection (see “2.6 Content Projection” on page
39) to display the data:
<div>
<h1>{{actor.name}}</h1>
<h2>{{actor.birthday}}</h2>
</div>
Since the Content Projection in this case uses the toString( ) method in ac-
tor.birthday and renders the result of it to the view, the following is rendered to
the UI:
41
Ionic 5 • 2 Angular Essentials
However, our users are not interested in the current time zone or the day of the
week. So we just want to show them the date in a common format. In order to im-
plement this formatting as elegantly as possible, Angular offers the possibility to use
pipes for this purpose. You use a kind of filter for the Content Projection, which
then delivers the result. For this we need a new TypeScript class, which is declared
as a pipe with a decorator:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'birthdayFormatPipe'
})
export class BirthdayFormatPipe implements PipeTransform {
Now we have to make the pipe known with our ActorData component module:
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ActorData } from './actor-data';
import { BirthdayFormatPipe }
from './../birthday-format.pipe';
@NgModule({
imports: [
...
],
declarations: [ActorData, BirthdayFormatPipe]
})
export class ActorDataModule {}
42
Ionic 5 • 2 Angular Essentials
Now we have to use our pipe in the HTML file. To use a pipe, we need the so-
called pipe operator (|):
<div>
<h1>{{actor.name}}</h1>
<h2>{{actor.birthday | birthdayFormatPipe}}</h2>
</div>
Hint: To prevent problems with undefined and/or null exceptions, there is the
Safe Navigation Operator (also known as the Elvis Operator). This can be used if
you are not sure if the property being bound doesn't belong to an object that is
undefined and/or null at the time of rendering. In our example it looks like this:
<div>
<h1>{{actor?.name}}</h1>
<h2>{{actor?.birthday | birthdayFormatPipe}}</h2>
</div>
https://angular.io/guide/pipes
http://masteringionic.com/blog/2017-08-02-understanding-angular-pipes
43
Ionic 5 • 2 Angular Essentials
2.9 Promises
Resolve a Promise
But how do we respond to a resolve or a reject ? To do this, each Promise ob-
ject has a then function, which means something like: If the asynchrony is finished
or the promise has been resolved (by resolve or reject ), then execute. For this
reason, promises are also often called thenables.
This then function can handle two callback functions. The first for the success and
the second for the case of error.
44
Ionic 5 • 2 Angular Essentials
Without the need to understand the code of the function getTours() , you imme-
diately realize that getTours() is a Promise function, right? The little word then
tells it. Once getTours() asynchronously and successfully returns data, some more
functions (inside the curly brackets) are executed here. All these inner functions can
rely on getTours() keeping its promise and delivering valid data.
Further informations about Promises you can find here:
https://codecraft.tv/courses/angular/es6-typescript/promises/
45
Ionic 5 • 2 Angular Essentials
2.10 Observables
An observable is an object that emits events (or notifications). An observer is an
object that listens for these events, and does something when an event is received.
Together, they create a pattern that can be used for programming asynchronously.
The principle is very similar to the Observer pattern and is called Reactive Pro-
gramming. Angular uses the observables of the ReactiveX architecture as a basis.
Through Microsoft's Microsoft Reactive Extensions, there is a very good implemen-
tation in JavaScript and other languages - called RxJS for short.
Let's look at a complete example of how observables in Angular (and Ionic) can be
used. In the following, the individual sections and functions of the following source
code are explained in more detail.
@Injectable({
providedIn: 'root'
})
export class ActorsService {
actors = [{
id: 1,
name: 'Julia Roberts',
birthday: new Date('1967-10-28')
},
46
Ionic 5 • 2 Angular Essentials
{
id: 2,
name: 'Richard Gere',
birthday: new Date('1949-08-31')
},
{
id: 3,
name: 'Hector Elizondo',
birthday: new Date('1936-12-22')
}];
constructor() { }
// Create an Observable
public getActors(): any {
const actorsObservable = new Observable(observer => {
setTimeout(() => {
observer.next(this.actors);
}, 1000);
});
return actorsObservable;
}
@Component({
selector: 'actors-data',
templateUrl: 'actors-data.html',
})
export class ActorsData {
actors = [];
47
Ionic 5 • 2 Angular Essentials
ngOnInit() {
const actorsObservable = this.actorsService.getActors();
// Define a Subscriber
actorsObservable.subscribe((actorsData) => {
this.actors = actorsData;
});
}
Explanation to Listing 1:
48
Ionic 5 • 2 Angular Essentials
The Observable calls the next method of its observer object to emit data
(this.actors ). To be able to use an Observable, Observable is to be imported
from the ReactiveX library (RxJS ):
import { Observable } from 'rxjs';
Explanation to Listing 2:
In ngOnInit the service is called and a subscriber is defined who writes the data
output by the service to the actor array:
ngOnInit() {
const actorsObservable = this.actorsService.getActors();
actorsObservable.subscribe((actorsData) => {
this.actors = actorsData;
});
}
49
Ionic 5 • 2 Angular Essentials
Explanation to Listing 3:
In the HTML file, the array is rendered via an * ngFor loop (see “2.7 *ngFor” on
page 40) into the view.
Further informations about Observables you can find in the official Angular docu-
mentation:
https://angular.io/guide/observables
50
Ionic 5 • 2 Angular Essentials
For a basic explanation of Promises, see section “2.9 Promises” on page 44.
async/await are the JavaScript keywords that greatly simplify asynchronous pro-
gramming:
In other words: With this concept, you only use a couple of keywords, but your
code will behave as though it is synchronous code. This is a fantastic change. You
get the benefits of async, with the readability of sync (or close to).
An example:
Look at our Promise function from page 44:
function asyncWriteFanMailToJuliaRoberts() {
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Mail sent!");
if (error) {
reject();
} else {
resolve();
}
}, 1000);
});
return promise;
}
What if we have more than one Promise before we continue? We can solve it in
two ways - one after the other or at the same time.
51
Ionic 5 • 2 Angular Essentials
async function() {
let mail = await asyncWriteFanMailToJuliaRoberts();
let answer = await asyncReadAnswerFromJuliaRoberts();
if (answer == 'Hi fan, I want to meet you!') {
let pretty = await asyncMakeMePrettyForWoman();
MeetJuliaRoberts();
}
}
async spam_fan_mails() {
let mail_to_amy = asyncWriteFanMailToAmyYasbeck();
let mail_to_julia = asyncWriteFanMailToJuliaRoberts();
let mail_to_laura = asyncWriteFanMailToLauraSanGiacomo();
Here are three Promises running at the same time and console.log('Spammed
all fan mails!') is waiting for all. The total execution time is the longest execu-
tion time of the promises.
52
Ionic 5 • 2 Angular Essentials
Optimists who think that all their favorite actresses want to meet with them can
now wait for all answers and finally decide with which woman they want to meet
or arrange to meet them all ;-)
Above you see a function labeled async with a whole series of await commands
in it. This initialize() function is part of a service and gets all data from a data-
base by some sub functions. The sub functions are called one after the other. A
loading component is displayed during the data retrieval and only dismissed after
all data has been loaded.
53
Ionic 5 • 2 Angular Essentials
Summary
In this chapter you met the most important concepts and structures of Angular,
which can be useful for you in the realization of your Ionic Apps:
54
Ionic 5 • 3 The first app
Then write
$ ionic start first-app
Please select the JavaScript framework to use for your new app. To
bypass this prompt next time,
supply a value for the --type option.
We accept the preselected Angular Framework with Enter. Ionic now asks:
Let's pick the perfect starter template! �
Starter templates are ready-to-go Ionic apps that come packed with
everything you need to build
your app. To bypass this prompt next time, supply template, the sec-
ond argument to ionic start.
55
Ionic 5 • 3 The first app
Again we accept the preselection (the tabs template) and press Enter. The creation
process continues:
Preparing directory ./first-app - done!
4 Downloading and extracting tabs starter - done!
──────────────────────────────────────────────────────────────
� https://ion.link/appflow �
──────────────────────────────────────────────────────────────
> npm i
npm WARN deprecated [email protected]: core-js@<3 is no longer main-
tained and not recommended for usage due to the number of issues.
Please, upgrade your dependencies to the actual version of core-js@3.
npm WARN deprecated [email protected]: request has been deprecated, see
https://github.com/request/request/issues/3142
SOLINK_MODULE(target) Release/.node
CXX(target) Release/obj.target/fse/fsevents.o
SOLINK_MODULE(target) Release/fse.node
56
Ionic 5 • 3 The first app
SOLINK_MODULE(target) Release/.node
CXX(target) Release/obj.target/fse/fsevents.o
SOLINK_MODULE(target) Release/fse.node
SOLINK_MODULE(target) Release/.node
CXX(target) Release/obj.target/fse/fsevents.o
SOLINK_MODULE(target) Release/fse.node
SOLINK_MODULE(target) Release/.node
CXX(target) Release/obj.target/fse/fsevents.o
SOLINK_MODULE(target) Release/fse.node
57
Ionic 5 • 3 The first app
added 1468 packages from 1074 contributors and audited 19143 packages
in 61.874s
found 0 vulnerabilities
After doing this, you may fix the identity used for this commit with:
58
Ionic 5 • 3 The first app
59
Ionic 5 • 3 The first app
Perhaps you get a few warnings, e.g. because of deprecated modules. Don't worry.
We can ignore them at the moment.
The creation process of our first Ionic application has finished. And as you can see,
Ionic has created a lot of files, which we will get to know bit by bit.
60
Ionic 5 • 3 The first app
In Visual Studio Code (VSC), our app structure looks like this:
We'll discuss this structure in more detail later (see "3 . 4 Structure of an Ionic
project " starting on page 69). First of all, this first glance should suffice and show
that we have just created a complete project framework with all the components
required for our first app with the Ionic start command.
That we've really got a working app now, we'll try out immediately.
61
Ionic 5 • 3 The first app
62
Ionic 5 • 3 The first app
Local: http://localhost:8100
… and creates a build of our first app. It's shown in the (default) browser by run-
ning a web server at http://localhost:8100. Our first Ionic app runs! That's
great, isn't it?
63
Ionic 5 • 3 The first app
To let our app look like a real app (and not like a website – yeah, of course, it is a
website) let's activate the developer tools in Google Chrome (CMD+ALT+I). Acti-
vate the Device Toolbar by clicking on the Toggle Device Toolbar button in the
developer tools header (see picture below). Now you can simulate different mobile
devices to see what our app looks like in different environments.
64
Ionic 5 • 3 The first app
Our first app in an iPhone 6/7/8 Plus (left) and Galaxy S5 (right) view:
We can even display our app in the format of an iPad. This will later become of
practical importance if we also make our app suitable for tablet use (see " 8.10 UI-
Design for Tablets (Split Pane Layout)", starting on page 382).
The Rotate icon in the preview pane of Chrome lets you switch the orientation be-
tween Portrait and Landscape.
65
Ionic 5 • 3 The first app
<ion-content [fullscreen]="true">
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">Tab 1</ion-title>
</ion-toolbar>
</ion-header>
To enjoy the next exciting moment, you should place browser and editor windows
next to each other.
Now save the changed code in the editor (CTRL+S or cmd+S ) and pay attention to
the contents of the browser window!
66
Ionic 5 • 3 The first app
Voila! The app has immediately updated itself and implemented your changes im-
mediately. Great, this live reload, right?
In the mood for a really blatant change? Then open the file src/theme/vari-
ables.scss and change the line
67
Ionic 5 • 3 The first app
With this small modification you have just radically changed the appearance of the
entire app and switched from dark mode to light mode!
Is that cool?
68
Ionic 5 • 3 The first app
69
Ionic 5 • 3 The first app
src
In the folder src (source) we will spend most of our development time, as it con-
tains most of the code files that we will be editing. Here we will (soon) also create a
whole series of own code files.
app
This is the startup folder of an app. Here or in app.module.ts and app.compo-
nent.ts you can place initialization code.
explore-container
Contains a component that is used by this example tabs app.
• tab1.module.ts is the module file of the page with all imports of further re-
quired modules.
• tab1.page.html contains the HTML code with the visible elements of a page.
• tab1.page.scss is a Sass file. In short, Sass is syntactically extended CSS and
takes care of style statements for a page.
• tab1.page.specs is used for test purposes.
• tab1.page.ts is the code behind the UI and our main playground for pro-
gramming.
app-routing.module.ts, app.component.html,
app.component.specs.ts, app.component.ts, app.module.ts
These are the top level files of our app. Their meaning basically corresponds to the
individual files on the page level (see tab1 above).
70
Ionic 5 • 3 The first app
assets
contains all media files that are delivered with an app.
environments
contains predefined or custom information for the build process.
theme
contains central theming and styling files. After initial creation of an app, this folder
contains the file variables.scss . We will get to know these in more detail in
chapter 8 “Theming, Styling, Customizing” (starting on page 331).
global.scss
imports all global Ionic style files.
index.html
is the start file of an app. Typically, metadata and references to external JavaScripts
are put in the header of index.html . In the body tag you can find the app-root
tag, the entry point for every Ionic app.
package.json
contains a lot of meta-data about a project. Mostly it will be used for managing de-
pendencies of a project.
Others
All other folders and files that I have not listed here for the sake of clarity, we'll get
to know in the further course of this book.
71
Ionic 5 • 3 The first app
After Ionic finished the creation process, we change into the project folder with
$ cd bob-tours
And that's the first look at our new side menu app:
72
Ionic 5 • 3 The first app
If you click on the menu button, the side menu is displayed and reveals to us that
the app consists of a few pages, below it contains a few labels. This is now the basis
of our "BoB Tours" app, whereby we will later replace the sample pages with our
own.
For practice you can switch from the color scheme dark to light again.
Do you remember how to do that? If not, see page 67.
I also do this to save some ink for letterpress in the following ;-)
73
Ionic 5 • 3 The first app
Summary
In this chapter you have programmed your first apps. Congratulations!
You now know ionic start and ionic serve and how to set up the Chrome
DevTools to simulate an app-like look.
You also have an overview of the project structure of an Ionic app.
74
Ionic 5 • 4 Navigation
4 Navigation
4.1 Have a plan
If you're planning an app its a good idea to think about its page navigation early.
You shouldn't code at the drop of a hat. So take a pencil and sketch your plan! For
our app BoB-Tours I've drawn the following navigation draft (not with a pencil,
but with Apple Keynote):
The pages in the horizontal (Favorites , Regions , Tour-Types ) should form our
menu pages. From there you get to the following pages List , Details and Re-
quest , where it goes from the Favorites directly to the Details .
75
Ionic 5 • 4 Navigation
Let's get down to business and first take a look at the folder structure of the cur -
rent app. As we can see, there is a folder directory automatically generated by
our sidemenu template containing a single app page.
We could change it manually and already use it for our app. The missing pages
would then have to be completed. Just stupid that one page always consists of
several files (x.routing.module.ts, x.module.ts , x.page.html , x.page.scss , x.-
page.specs.ts and x.page.ts ). That's where boring routine work comes to us to
create all these pages, right?
No! Ionic thanks! In this task, the integrated code generator will help us.
76
Ionic 5 • 4 Navigation
This command uses the Angular CLI to generate features such as pages,
components, directives, services, etc.
You can specify a path to nest your feature within any number of
subdirectories. For example, specify a name of
"pages/New Page" to generate page files at src/app/pages/new-
page/.
Usage:
$ ionic generate <type> <name>
Inputs:
Examples:
$ ionic generate
$ ionic generate page
$ ionic generate page contact
$ ionic generate component contact/form
$ ionic generate component login-form --change-detection=OnPush
$ ionic generate directive ripple --skip-import
$ ionic generate service api/user
77
Ionic 5 • 4 Navigation
All right, generate page sounds very good! Let's try this:
$ ionic generate page pages/Favorites
In a new folder named pages we get a subfolder favorites with all required page
files in it. We also get an updated app-routing.module.ts. In app-routing.mod-
ule.ts, as described in "2.5 Routing and Lazy Loading" (starting on page 36),
navigation between the pages of an app is organized. We will soon tune this mod-
ule.
But now let's create the other pages with the help of g (short form for generate ):
$ ionic g page pages/Regions
$ ionic g page pages/Tour-Types
$ ionic g page pages/List
$ ionic g page pages/Details
$ ionic g page pages/Request
Clean-up
The following cleanups have to be done:
1. Deleting the unnecessary folder directory
78
Ionic 5 • 4 Navigation
79
Ionic 5 • 4 Navigation
{
path: 'regions',
loadChildren:
() => import('./pages/regions/regions.module')
.then(m => m.RegionsPageModule)
},
{
path: 'tour-types',
loadChildren:
() => import('./pages/tour-types/tour-types.module')
.then(m => m.TourTypesPageModule)
},
{
path: 'list',
loadChildren:
() => import('./pages/list/list.module')
.then(m => m.ListPageModule)
},
{
path: 'details',
loadChildren:
() => import('./pages/details/details.module')
.then(m => m.DetailsPageModule)
},
{
path: 'request',
loadChildren:
() => import('./pages/request/request.module')
.then(m => m.RequestPageModule)
}
];
@NgModule({
imports: [
RouterModule.forRoot(routes,
{ preloadingStrategy: PreloadAllModules })
],
exports: [RouterModule]
})
export class AppRoutingModule { }
80
Ionic 5 • 4 Navigation
81
Ionic 5 • 4 Navigation
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();
});
}
}
<ion-menu-toggle auto-hide="false"
*ngFor="let p of appPages; let i = index">
<ion-item (click)="selectedIndex = i"
routerDirection="root"
[routerLink]="[p.url]"
lines="none"
detail="false"
[class.selected]="selectedIndex == i">
<ion-icon slot="start"
[ios]="p.icon + '-outline'"
[md]="p.icon + '-sharp'"></ion-icon>
<ion-label>{{ p.title }}</ion-label>
</ion-item>
</ion-menu-toggle>
</ion-list>
<ion-list id="labels-list">
<ion-list-header>Labels</ion-list-header>
82
Ionic 5 • 4 Navigation
and our app shows the three side menu entries Favorites, Regions and Tour-
Types and should look like this in the browser:
But wait – when you activate Toggle device toolbar in the Developer Tools to
show our app in mobile style, you see that a button for achieving the side menu is
missing.
83
Ionic 5 • 4 Navigation
Let's fix this by adding the following (bold) lines to each of the headers of fa-
vorites.page.html , regions.page.html and tour-types.page.html :
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>Favorites</ion-title>
</ion-toolbar>
</ion-header>
Now our app works as expected and you can switch to every main page and
achieve the side menu by clicking on the now inserted „sandwich“ icon (menu but-
ton) on every page.
84
Ionic 5 • 4 Navigation
4.3 Routing
app-routing.module.ts
The concepts of Routing and Lazy Loading in Angular/Ionic are described in chap-
ter 2 „Angular Essentials“ (see 2.5 Routing and Lazy Loading, starting on page 36).
As we have seen the root configuration for our router lives in the src/app/
app-routing.module.ts file. For all our generated pages the CLI defined the
corresponding routes automatically.
For example look at the route for the page Details :
{
path: 'details',
loadChildren:
() => import('./pages/details/details.module')
.then(m => m.DetailsPageModule)
}
It's a lazy loaded route that renders the DetailsPageModule where the outlet is
defined in the HTML in the app.component.html :
<ion-router-outlet id=”main-content”></ion-router-outlet>
When you navigate to the /favorites path in the browser, it will render the spe-
cific component in the HTML outlet.
A button or link is the most basic way to navigate to one of our defined routes.
Let's say, we want to navigate from Favorites to Details . We can simply create
a button in the favorites.page.html file like this one:
<ion-content class="ion-padding">
<ion-button href="/details">Show details</ion-button>
</ion-content>
85
Ionic 5 • 4 Navigation
routerLink / routerDirection
A more advanced and my preferred way to switch to another page is using the
routerLink attribute in combination with the routerDirection attribute:
<ion-content class="ion-padding">
<ion-button routerLink="/details"
routerDirection="forward">
Show details
</ion-button>
</ion-content>
In this way we get a smooth forward animation while switching between pages.
The Ionic back button navigates back in the app's history upon click. <ion-back-
-button> is smart enough to know what to render based on the mode and when
to show based on the navigation stack. When there's no history, the attribute de-
faultHref can be used to define the url to navigate back to by default.
Let's complete our navigation stack by implementing a next route from Details to
Request .
86
Ionic 5 • 4 Navigation
87
Ionic 5 • 4 Navigation
So far, so nice! But what's an app without any data? And how will data be brought
from one page to another? These questions will be answered in the next division
of this chapter.
88
Ionic 5 • 4 Navigation
@Component({
selector: 'app-favorites',
templateUrl: './favorites.page.html',
styleUrls: ['./favorites.page.scss'],
})
export class FavoritesPage implements OnInit {
tours = [
{ ID: 1, Title: 'City walk' },
{ ID: 2, Title: 'On the trails of Beethoven' },
{ ID: 3, Title: 'Villa Hammerschmidt' }
];
constructor() { }
ngOnInit() {
}
89
Ionic 5 • 4 Navigation
The Title property of every tour is then be displayed as text by Content Projec-
tion (see “2.6 Content Projection”, on page 39).
Look at the line
[routerLink]="['/details', tour]"
more exactly!
90
Ionic 5 • 4 Navigation
@Component({
selector: 'app-details',
templateUrl: './details.page.html',
styleUrls: ['./details.page.scss'],
})
export class DetailsPage implements OnInit {
tour = null;
ngOnInit() {
console.log(this.activatedRoute);
this.tour = this.activatedRoute.snapshot.params;
}
To get data from the calling (previous) page, we can use the ActivatedRoute from
@angular/router . To use it we have to import it with
91
Ionic 5 • 4 Navigation
ActivatedRoute has a property snapshot , which in turn provides the tour object
through its params property. This is exactly the tour object, that of the previous
page over
[routerLink]="['/details', tour]"
The concepts of Routing and Lazy Loading in Angular/Ionic are described in chap-
ter 2 „Angular Essentials“ (see 2.5 Routing and Lazy Loading, starting on page 36).
I invite you to check out the many other parameters of ActivatedRoute in the
DevTools. For this I have inserted the line
console.log(this.activatedRoute);
<ion-content class="ion-padding">
...
</ion-content>
92
Ionic 5 • 4 Navigation
Usually, the best way to tackle this situation is to simply pass an id through the
URL, and then use that id to grab the rest of the data through a provider/service.
We will explore this in the next chapter (chapter 5 “Services & Storage”, page 97).
But yet, as a first demonstration of using routerLink , we are satisfied with this sim-
ple solution.
https://ionicframework.com/docs/api/router-outlet
https://ionicframework.com/docs/api/back-button
93
Ionic 5 • 4 Navigation
It's easy to use: After the installation activate the Google Chrome DevTools, switch
to the new Augury entry and click on RouteTree . Now Augury shows you a visual-
ization of the router tree and the nature of every route in our app:
94
Ionic 5 • 4 Navigation
Summary
In this chapter you've used Ionic's code generator to create some pages.
You got to know the app-routing.module.ts file, which defines the routes to the
pages of an app.
You now know how to use ActivatedRoute to transfer data from one app page
to the next.
You've got a pro tip to use the Augury Chrome Plugin to visualize the router tree
of an app.
95
Ionic 5 • 4 Navigation
96
Ionic 5 • 5 Services & Storage
97
Ionic 5 • 5 Services & Storage
You do that via the Add project button. Give it the name "BoB-Tours-App".
98
Ionic 5 • 5 Services & Storage
Click on the menu button (far right) and select the entry Import JSON.
99
Ionic 5 • 5 Services & Storage
In the dialog box that opens, select the file bob-tours-data.json (from my book's
website) and click on Import. We have now created an online database with the
collections Regions , Tours and Tourtypes.
Our database backend is now ready to start, waiting to be used by our app.
100
Ionic 5 • 5 Services & Storage
That's why it is a best practice to separate presentation of data from data access
by encapsulating data access in a separate service and delegating to that service in
the component, even in simple cases like this one.
Create a service
In Terminal we write
$ ionic g service services/bob-tours
and let the Ionic CLI (more precisely Angular) do the work for us.
> ng generate service services/bob-tours
CREATE src/app/services/bob-tours.service.spec.ts (344 bytes)
CREATE src/app/services/bob-tours.service.ts (137 bytes)
[OK] Generated service!
After finishing the creation process we have a service or let's say, the shell of a ser-
vice.
You find it in the services folder that we told the CLI to create and to place the
service inside it. The folder contains two files:
• bob-tours.service.spec.ts
• bob-tours.service.ts
As noted earlier, a spec file is used for testing. You'll find out more about this in
Chapter 11 "Debugging & Testing" (starting from page 447).
101
Ionic 5 • 5 Services & Storage
@Injectable({
providedIn: 'root'
})
export class BobToursService {
constructor() { }
}
@Injectable({
providedIn: 'root'
})
export class BobToursService {
baseUrl = 'https://bob-tours-app.firebaseio.com/';
getRegions() {
let requestUrl = `${this.baseUrl}/Regions.json`;
return this.http.get(requestUrl).toPromise();
}
}
102
Ionic 5 • 5 Services & Storage
Above the constructor, we declared a base url containing the root path to our
database call:
baseUrl = 'https://bob-tours-app.firebaseio.com/';
Pay attention to the special syntax with the character ` at the beginning and at the
end of the whole string. In conjunction with the $ sign and the curly braces, we can
concatenate the variable this.baseUrl with the following string part to form a
complete expression.
103
Ionic 5 • 5 Services & Storage
@Component({
selector: 'app-regions',
templateUrl: './regions.page.html',
styleUrls: ['./regions.page.scss'],
})
export class RegionsPage implements OnInit {
regions: any;
constructor(private btService:BobToursService) { }
ngOnInit() {
this.btService.getRegions()
.then(data => this.regions = data);
}
means "If data arrives, pass that data to this.regions " (our local page variable).
104
Ionic 5 • 5 Services & Storage
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let region of regions">
{{region.Name}}
</ion-item>
</ion-list>
</ion-content>
A typical *ngFor closure ensures that a separate list entry is created for each re-
gion read from the database. Using Content Projection, we output the name of
each region.
105
Ionic 5 • 5 Services & Storage
at resolveToken (core.js:17514)
at tryResolveToken (core.js:17440)
at StaticInjector.get (core.js:17266)
at resolveNgModuleDep (core.js:30393)
at NgModuleRef_.get (core.js:31578)
at injectInjectorOnly (core.js:734)
at resolvePromise (zone-evergreen.js:797)
at resolvePromise (zone-evergreen.js:754)
at zone-evergreen.js:858
at ZoneDelegate.invokeTask (zone-evergreen.js:391)
at Object.onInvokeTask (core.js:39680)
at ZoneDelegate.invokeTask (zone-evergreen.js:390)
at Zone.runTask (zone-evergreen.js:168)
at drainMicroTaskQueue (zone-evergreen.js:559)
at ZoneTask.invokeTask [as invoke] (zone-evergreen.js:469)
at invokeTask (zone-evergreen.js:1603)
Uups! What's wrong? Did we miss anything? Yes – and the key indication to that
is found in those few words:
No provider for HttpClient!
106
Ionic 5 • 5 Services & Storage
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [
BrowserModule,
IonicModule.forRoot(),
AppRoutingModule,
HttpClientModule
],
providers: [
StatusBar,
SplashScreen,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrate-
gy }
],
bootstrap: [AppComponent]
})
export class AppModule {}
and will be rewarded by a Regions page filled with data coming from a database!
107
Ionic 5 • 5 Services & Storage
Perfect, isn't it? Not quite. We have one little flaw and that has to do with the
event, when the database is asked for data. Let's check this.
In the regions.page.ts file we wrote:
ngOnInit() {
this.btService.getRegions()
.then(data => this.regions = data);
}
What this code exactly does is: every time, the Regions page is initialized in the
ngOnInit hook (see “2.3 Lifecycle Hooks”, starting on page 30), the database is
asked for the data. We easily can verify this with a look into the Developer Tools
> Network .
108
Ionic 5 • 5 Services & Storage
Switch between the side menu and the other pages in our app several times and
you'll get a result like this:
Granted, the retrieved data amount of 482 bytes is tiny and the retrieval times are
quite fast. But unnecessary calls should nevertheless be avoided. Because on a
smartphone, this can feel a bit slower than in our browser, especially if the user has
a slow Internet connection!
There is simply no need for our app to retrieve the data from the database each
time it views the page. So let's look at the question of whether there are other
events where we could accommodate our HTTP call.
As described in chapter 2 "Angular Essentials" (see division “2.3 Lifecycle Hooks”,
starting on page 30), there are several hooks in a page component where code
can be placed. But none of the hooks help us here, because with each of them the
data would be reloaded when the page is viewed.
But what remains of the possibilities to load the data for Regions (and Tour-
Types ) only once each time the app starts, even though the associated pages are
reloaded each time?
109
Ionic 5 • 5 Services & Storage
The solution to our problem is to say goodbye to the hooks associated with page
up and down and to think about a different approach.
Let's take a look at how Ionic organizes the launch of an app and take a look at
src/app/app.components.ts .
How about if we also initialize our service here, i.e. load our Regions and Tour-
Types ? Thus, the data retrieval would be done only once at app launch.
110
Ionic 5 • 5 Services & Storage
@Component({
selector: 'app-root',
templateUrl: 'app.component.html'
})
export class AppComponent {
public appPages = [
{
title: 'Favorites',
url: '/favorites',
icon: 'star'
},
{
title: 'Regions',
url: '/regions',
icon: 'images'
},
{
title: 'Tour-Types',
url: '/tour-types',
icon: 'bus'
}
];
constructor(
private platform: Platform,
private splashScreen: SplashScreen,
private statusBar: StatusBar
) {
this.initializeApp();
}
111
Ionic 5 • 5 Services & Storage
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();
});
}
ngOnInit() { }
}
We'll place our initialization of the data service right here, too. So the call for our
data will be done exactly once at the app's start only.
@Injectable({
providedIn: 'root'
})
export class BobToursService {
baseUrl = 'https://bob-tours-app.firebaseio.com/';
initialize() {
this.getRegions()
.then(data => this.regions = data);
}
112
Ionic 5 • 5 Services & Storage
getRegions() {
let requestUrl = `${this.baseUrl}/Regions.json`;
return this.http.get(requestUrl).toPromise();
}
@Component({
selector: 'app-root',
templateUrl: 'app.component.html'
})
export class AppComponent {
public appPages = [
{
title: 'Favorites',
url: '/favorites',
icon: 'star'
},
{
title: 'Regions',
url: '/regions',
icon: 'images'
113
Ionic 5 • 5 Services & Storage
},
{
title: 'Tour-Types',
url: '/tour-types',
icon: 'bus'
}
];
constructor(
private platform: Platform,
private splashScreen: SplashScreen,
private statusBar: StatusBar,
private btService: BobToursService
) {
this.initializeApp();
}
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();
this.btService.initialize();
});
}
}
@Component({
selector: 'app-regions',
templateUrl: './regions.page.html',
styleUrls: ['./regions.page.scss'],
})
114
Ionic 5 • 5 Services & Storage
regions: any;
constructor(private btService:BobToursService) { }
ngOnInit() {
this.regions = this.btService.regions;
}
}
We delete the previous call for our service. Instead we set a reference to the re-
gions variable of the service (bold formatted code above), where the data now
comes from.
That's it. Start the app and check the Developer Tools Network protocol.
Our service is called only once at the app start - no matter how many times we
switch between our pages.
115
Ionic 5 • 5 Services & Storage
@Injectable({
providedIn: 'root'
})
export class BobToursService {
baseUrl = 'https://bob-tours-app.firebaseio.com/';
initialize() {
this.getRegions()
.then(data => this.regions = data);
this.getTourtypes()
.then(data => this.tourtypes = data);
}
getRegions() {
let requestUrl = `${this.baseUrl}/Regions.json`;
return this.http.get(requestUrl).toPromise();
}
116
Ionic 5 • 5 Services & Storage
getTourtypes() {
let requestUrl = `${this.baseUrl}/Tourtypes.json`;
return this.http.get(requestUrl).toPromise();
}
We give our service another public property called tourtypes , also of type any .
public tourtypes: any;
getTourtypes() {
let requestUrl = `${this.baseUrl}/Tourtypes.json`;
return this.http.get(requestUrl).toPromise();
}
The method is now also called within initialize() and passed the retrieved data
to the public property tourtypes.
this.getTourtypes()
.then(data => this.tourtypes = data);
@Component({
selector: 'app-tour-types',
templateUrl: './tour-types.page.html',
styleUrls: ['./tour-types.page.scss'],
})
export class TourTypesPage implements OnInit {
tourtypes: any;
117
Ionic 5 • 5 Services & Storage
constructor(private btService:BobToursService) { }
ngOnInit() {
this.tourtypes = this.btService.tourtypes;
}
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let tourtype of tourtypes">
{{tourtype.Name}}
</ion-item>
</ion-list>
</ion-content>
Again: A typical *ngFor closure ensures that a separate list entry is created for
each tour type read from the service. Using Content Projection, we output the
name of each tour type.
118
Ionic 5 • 5 Services & Storage
Start the app and check the newly created page Tour-Types :
119
Ionic 5 • 5 Services & Storage
https://lodash.com/
@Injectable({
providedIn: 'root'
})
export class BobToursService {
baseUrl = 'https://bob-tours-app.firebaseio.com/';
initialize() {
this.getRegions()
.then(data => this.regions = data);
this.getTourtypes()
.then(data => this.tourtypes = _.sortBy(data, 'Name'));
}
getRegions() {
let requestUrl = `${this.baseUrl}/Regions.json`;
return this.http.get(requestUrl).toPromise();
}
120
Ionic 5 • 5 Services & Storage
getTourtypes() {
let requestUrl = `${this.baseUrl}/Tourtypes.json`;
return this.http.get(requestUrl).toPromise();
}
We can sort the incoming data from our getTourtypes() method with:
_.sortBy(data, 'Name')
The first parameter is the data to be sorted. The second parameter is the property
to sort by. And this is the result with sorted list items:
121
Ionic 5 • 5 Services & Storage
@Injectable({
providedIn: 'root'
})
export class BobToursService {
initialize() {
this.getRegions()
.then(data => this.regions = data);
this.getTourtypes()
.then(data => this.tourtypes = _.sortBy(data, 'Name'));
this.getTours()
.then(data => this.tours = _.sortBy(data, 'Title'));
}
getRegions() {
let requestUrl = `${this.baseUrl}/Regions.json`;
return this.http.get(requestUrl).toPromise();
}
getTourtypes() {
let requestUrl = `${this.baseUrl}/Tourtypes.json`;
return this.http.get(requestUrl).toPromise();
}
122
Ionic 5 • 5 Services & Storage
getTours() {
let requestUrl = `${this.baseUrl}/Tours.json`;
return this.http.get(requestUrl).toPromise();
}
}
We give our service another public property called tours , also of type any .
public tours: any;
The method is now also called within initialize() and passed the retrieved data
to the public property tours and is sorted by the Title property.
this.getTours()
.then(data => this.tourtypes = _.sortBy(data, 'Title'));
@Component({
selector: 'app-list',
templateUrl: './list.page.html',
styleUrls: ['./list.page.scss'],
})
export class ListPage implements OnInit {
tours: any;
123
Ionic 5 • 5 Services & Storage
ngOnInit() {
this.tours = this.btService.tours;
}
}
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let tour of tours">
{{tour.Title}}
</ion-item>
</ion-list>
</ion-content>
124
Ionic 5 • 5 Services & Storage
And to show all tours on the page we create the usual list structure with *ngFor :
<ion-list>
<ion-item *ngFor="let tour of tours">
{{tour.Title}}
</ion-item>
</ion-list>
Filtering
The following things are left to do:
1. Navigate from the Regions or Tour-Types page to the List page
2. Filter the tours on the List page depending on the previous selection
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let region of regions"
[routerLink]="['/list']"
routerDirection="forward">
{{region.Name}}
</ion-item>
</ion-list>
</ion-content>
125
Ionic 5 • 5 Services & Storage
You remember routerLink and routerDirection ? If not, skip a few pages back-
wards to chapter 4 “Navigation” (see 4.3 Routing, page 85).
When we now start the app and click on one of the items of the Regions page,
we always get the complete list of tours. Great – but what's missing is a filtering
function to show tours from the selected region only.
What can be the criteria for filtering the tour data? To answer this question we
should inspect the Regions and a Tour object in our database:
As you can see, each region has an ID property and each tour has a Region
property that matches one of the region IDs. So we can filter that!
126
Ionic 5 • 5 Services & Storage
<ion-title>Regions</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let region of regions"
[routerLink]="['/list',
{ Category: 'Region',
Criteria: region.ID,
Name: region.Name
}
]"
routerDirection="forward">
{{region.Name}}
</ion-item>
</ion-list>
</ion-content>
@Component({
selector: 'app-list',
templateUrl: './list.page.html',
styleUrls: ['./list.page.scss'],
})
export class ListPage implements OnInit {
tours: any;
selection: any;
127
Ionic 5 • 5 Services & Storage
constructor(private btService:BobToursService,
private activatedRoute: ActivatedRoute) { }
ngOnInit() {
this.selection = this.activatedRoute.snapshot.params;
let category = this.selection.Category;
let criteria = this.selection.Criteria;
this.tours = _.filter(
this.btService.tours,
[ category, criteria ]
);
}
Finally we use the filter function from Lodash (of course we had to import it) to fil -
ter all tours by the category 'Region' and a selected criteria, for example 'BN' .
this.tours = _.filter(this.btService.tours,
[ category, criteria ]);
128
Ionic 5 • 5 Services & Storage
What about the Name property? Because we put the Name property of a selected
region as third parameter to the routerLink params object, we can use it for pro-
jecting the title into list.page.html as follows:
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>{{selection.Name}}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let tour of tours">
{{tour.Title}}
</ion-item>
</ion-list>
</ion-content>
129
Ionic 5 • 5 Services & Storage
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let tourtype of tourtypes"
[routerLink]="['/list',
{ Category: 'Tourtype',
Criteria: tourtype.ID,
Name: tourtype.Name
}
]"
routerDirection="forward">
{{tourtype.Name}}
</ion-item>
</ion-list>
</ion-content>
130
Ionic 5 • 5 Services & Storage
131
Ionic 5 • 5 Services & Storage
If you look back at our navigation concept in chapter 4 “Navigation” (4.1 Have a
plan, page 75), you see, that the Details page is reached from the List page –
and later on, of course – by the Favorites page, too. From the Details page
you can navigate to the Request page (we always built this route).
Let me show you a slightly different usage of routerLink . In order to ensure that
this alternative works, we we'll solve the following tasks:
...
{
path: 'details/:id',
loadChildren:
() => import('./pages/details/details.module')
.then(m => m.DetailsPageModule)
}
...
With appending :id to the path we place a route parameter in the path. For ex-
ample, to see the tour details page for tour with ID 5, we must use the following
URL:
http://localhost:8100/details/5
132
Ionic 5 • 5 Services & Storage
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let tour of tours"
[routerLink]="'/details/' + tour.ID"
routerDirection="forward">
{{tour.Title}}
</ion-item>
</ion-list>
</ion-content>
@Component({
selector: 'app-details',
133
Ionic 5 • 5 Services & Storage
templateUrl: './details.page.html',
styleUrls: ['./details.page.scss'],
})
export class DetailsPage implements OnInit {
tour = null;
ngOnInit() {
let id = this.activatedRoute.snapshot.paramMap.get('id');
this.tour = _.find(this.btService.tours, ['ID',
parseInt(id)]);
}
In ngOnInit we grab the id route parameter with the following line of code:
let id = this.activatedRoute.snapshot.paramMap.get('id');
we now can use it to find the tour with the given id:
this.tour = _.find(this.btService.tours, ['ID',
parseInt(id)]);
Note: The id parameter is supplied as a string. However, the IDs of our tour ob-
jects read from the database are available as integers. Therefore we have to use
parseInt(id) to convert the given id string to an integer so that a tour can be
found.
134
Ionic 5 • 5 Services & Storage
Navigation completed
Let's start our app. We can now call the Regions of the side menu, for example,
select a region and by clicking on one of the listed tours to view their details. Well,
really much indicated - aside from the title and the Request button - nothing is
here (which we will of course change later).
135
Ionic 5 • 5 Services & Storage
A very simple way to locally store a small amount of data in an app, is the so
called Local Storage.
@Injectable({
providedIn: 'root'
})
export class FavoritesService {
constructor() { }
initialize(tours) {
this.favTours = [];
this.favIDs = JSON.parse(window.localStorage
.getItem('FavoritesIDs'));
if (this.favIDs == null) {
this.favIDs = [];
} else {
tours.forEach(tour => {
if (this.favIDs.indexOf(tour.ID) != -1) {
this.favTours.push(tour);
}
});
136
Ionic 5 • 5 Services & Storage
}
}
add(tour) {
this.favIDs.push(tour.ID);
this.favTours.push(tour);
window.localStorage.setItem('FavoritesIDs',
JSON.stringify(this.favIDs));
}
remove(tour) {
let removeIndex:number = this.favIDs.indexOf(tour.ID);
if (removeIndex != -1) {
this.favIDs.splice(removeIndex, 1);
this.favTours.splice(removeIndex, 1);
window.localStorage.setItem('FavoritesIDs',
JSON.stringify(this.favIDs));
}
}
Our provider has two properties, favIDs and favTours , and the methods ini-
tialize , add and remove . Their functionality in more detail:
initialize(tours)
In initialize(tours) we get all tours (from BobToursService, as we can see lat-
er). With
this.favTours = [];
The next line of code reads the key 'FavoritesIDs' from windows.localStor-
age using its getItem method. To transform the result string into an array, we
need JSON.parse :
this.favIDs = JSON.parse(window.localStorage
.getItem('FavoritesIDs'));
137
Ionic 5 • 5 Services & Storage
Otherwise
} else {
we go through all the tours and search for the right tours based on the IDs read
from the local storage
tours.forEach(tour => {
if (this.favIDs.indexOf(tour.ID) != -1) {
add(tours)
In add(tours) we react to the fact that the user has clicked the "Add to Favorites"
button (as we can see later).
this.favIDs.push(tour.ID);
this.favTours.push(tour);
window.localStorage.setItem('FavoritesIDs',
JSON.stringify(this.favIDs));
We add the tour.ID to the favIDs array and add the whole tour object to the
favTours array. Finally we save the stringified favIDs array to the Local Storage
using its setItem method. setItem expects the key ('FavoritesIDs' ) and the
value (this.favDIs ).
remove(tour)
In remove(tours) we react to the fact that the user has clicked the "Remove from
Favorites" button (as we can see later).
With
let removeIndex:number = this.favIDs.indexOf(tour.ID);
138
Ionic 5 • 5 Services & Storage
If there is an index
if (removeIndex != -1) {
Finally we save the stringified favIDs array to the Local Storage using its setItem
method.
window.localStorage.setItem('FavoritesIDs',
JSON.stringify(this.favIDs));
@Injectable({
providedIn: 'root'
})
export class BobToursService {
baseUrl = 'https://bob-tours-app.firebaseio.com/';
139
Ionic 5 • 5 Services & Storage
initialize() {
this.getRegions().then(data => this.regions = data);
this.getTourtypes().then(data => this.tourtypes
= _.sortBy(data, 'Name'));
this.getTours().then(data => {
this.tours = _.sortBy(data, 'Title');
this.favService.initialize(this.tours);
});
}
getRegions() {
let requestUrl = `${this.baseUrl}/Regions.json`;
return this.http.get(requestUrl).toPromise();
}
getTourtypes() {
let requestUrl = `${this.baseUrl}/Tourtypes.json`;
return this.http.get(requestUrl).toPromise();
}
getTours() {
let requestUrl = `${this.baseUrl}/Tours.json`;
return this.http.get(requestUrl).toPromise();
}
You are almost an Ionic veteran and realize what we have done here: The new Fa-
voritesService was imported, injected into the constructor and called in the
initialize method of the BobToursService via its own initialize method,
where all tours are handed over and are now also available in FavoritesService.
140
Ionic 5 • 5 Services & Storage
/* tours = [
{ ID: 1, Title: 'City walk' },
{ ID: 2, Title: 'On the trails of Beethoven' },
{ ID: 3, Title: 'Villa Hammerschmidt' }
]; */
ngOnInit() { }
You see that I commented out the mock array tours – you can delete it, because
in a few seconds we use the real favorites tours.
In favorites.page.html we change:
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>Favorites</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let tour of favService.favTours"
[routerLink]="'/details/' + tour.ID"
routerDirection="forward">
{{ tour.Title }}
141
Ionic 5 • 5 Services & Storage
</ion-item>
<ion-item *ngIf="favService.favTours?.length==0">
You didn't choose any favorites yet!
</ion-item>
</ion-list>
</ion-content>
The *ngFor loop iterates through favService.favTours now. This property (ar-
ray) will hold all user-defined favorites later. In [routerLink] we again use the
now known tour.ID route parameter to specify the tour to be displayed on De-
tailsPage.
An interesting detail is the second ion-item :
<ion-item *ngIf="favService.favTours?.length==0">
You didn't choose any favorites yet!
</ion-item>
The *ngIf directive says: Show me only, if there's no element in the favSer-
vice.favTours array. This is the case if the user hasn't yet selected a favorite. And
that's exactly what we show him as a hint.
@Component({
selector: 'app-details',
templateUrl: './details.page.html',
142
Ionic 5 • 5 Services & Storage
styleUrls: ['./details.page.scss'],
})
export class DetailsPage implements OnInit {
tour = null;
isFavorite: boolean;
ngOnInit() {
let id = this.activatedRoute.snapshot.paramMap.get('id');
this.tour =
_.find(this.btService.tours, ['ID', parseInt(id)]);
this.isFavorite =
this.favService.favIDs.indexOf(parseInt(id)) != -1;
}
<ion-content class="ion-padding">
<ion-button routerLink="/request"
143
Ionic 5 • 5 Services & Storage
routerDirection="forward">
Request a Tour
</ion-button>
</ion-content>
<ion-footer class="ion-padding">
<ion-button (click)="favService.add(tour);
isFavorite=true;"
*ngIf="!isFavorite"
expand="block">
Add to Favorites
</ion-button>
<ion-button (click)="favService.remove(tour);
isFavorite=false;"
*ngIf="isFavorite"
expand="block">
Remove from Favorites
</ion-button>
</ion-footer>
The click event of the “Add to Favorites” button triggers the add method of the
FavoritesService. It also sets isFavorite to true to control the visibility logic of the
buttons.
T h e click event of the “Remove from Favorites” button triggers the remove
method of the FavoritesService. It also sets isFavorite to false to control the vis-
ibility logic of the buttons, too.
144
Ionic 5 • 5 Services & Storage
Our favorites management is complete and we can see it all in the running app:
In Chrome's DevTools, you can view the contents of Local Storage in the Applica-
tion panel:
145
Ionic 5 • 5 Services & Storage
When running in a native app context, Storage will prioritize using SQLite (if it's in-
stalled), as it's one of the most stable and widely used file-based databases, and
avoids some of the pitfalls of things like LocalStorage and IndexedDB , such as the
OS deciding to clear out such data in low disk-space situations.
When running in the web or as a Progressive Web App, Storage will attempt to
use IndexedDB , WebSQL , and LocalStorage , in that order.
Usage
First, install the package:
$ npm install --save @ionic/storage
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [
146
Ionic 5 • 5 Services & Storage
BrowserModule,
IonicModule.forRoot(),
AppRoutingModule,
HttpClientModule,
IonicStorageModule.forRoot()
],
providers: [
StatusBar,
SplashScreen,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrate-
gy }
],
bootstrap: [AppComponent]
})
export class AppModule {}
@Injectable({
providedIn: 'root'
})
export class FavoritesService {
initialize(tours) {
this.favTours = [];
// this.favIDs =
JSON.parse(window.localStorage.getItem('FavoritesIDs'));
this.storage.ready().then(() => {
this.storage.get('FavoritesIDs').then(ids => {
this.favIDs = ids;
147
Ionic 5 • 5 Services & Storage
if (this.favIDs == null) {
this.favIDs = [];
} else {
tours.forEach(tour => {
if (this.favIDs.indexOf(tour.ID) != -1) {
this.favTours.push(tour);
}
});
}
});
});
}
add(tour) {
this.favIDs.push(tour.ID);
this.favTours.push(tour);
// window.localStorage.setItem('FavoritesIDs',
// JSON.stringify(this.favIDs));
this.storage.set('FavoritesIDs', this.favIDs);
}
remove(tour) {
let removeIndex:number = this.favIDs.indexOf(tour.ID);
if (removeIndex != -1) {
this.favIDs.splice(removeIndex, 1);
this.favTours.splice(removeIndex, 1);
// window.localStorage.setItem('FavoritesIDs',
// JSON.stringify(this.favIDs));
this.storage.set('FavoritesIDs', this.favIDs);
}
}
We import Storage
import { Storage } from '@ionic/storage';
148
Ionic 5 • 5 Services & Storage
In the add and remove methods we write the entire favIDs array each into the
storage. That's it!
Here too, in Chrome's DevTools, you can view the contents of Ionic Storage in the
Application panel:
149
Ionic 5 • 5 Services & Storage
Summary
In this chapter, you got to know Google Firebase. You have read data from a data-
base with the aid of HttpClient .
You sorted and filtered data using the Lodash library to make it appealing in the
app.
You got to know route parameters as a supplement to the Angular routing con-
cept.
Finally, with Local Storage and Ionic Storage, you've used a variety of ways to store
user-specific data.
150
Ionic 5 • 6 UI Components
6 UI Components
6.1 Introduction
Ionic apps are made of high-level building blocks called Components, which allow
you to quickly construct the UI for your app. Ionic comes stock with a number of
components, including cards, lists, and tabs.
151
Ionic 5 • 6 UI Components
Each Ionic component consists of one or more custom elements. Each custom ele-
ment, in turn, may expose properties, methods, events, and CSS custom
properties.
The components are simply to use, modify and extend in many ways. I recom-
mend to read the very, very good documentation on:
https://ionicframework.com/docs/components
For a deeper insight you should dive into the API docs here:
https://ionicframework.com/docs/api
With the wide variety of components my first intention was to only write about a
hand-picked selection of them. But it's such a fun to use them, that I decided to
talk about every component! You can't only explore them all in this chapter - you'll
bring them also into our app and let them groove ;-)
152
Ionic 5 • 6 UI Components
Sometimes Action Sheets are used as an alternative for menus, but respect, that
the Ionic documentation points out, not to use them for navigation.
There are multiple ways to dismiss the Action Sheet, including tapping the back-
drop or hitting the escape key on desktop.
<ion-content class="ion-padding">
<!-- <ion-button routerLink="/request"
routerDirection="forward">
Request a Tour
</ion-button> -->
</ion-content>
153
Ionic 5 • 6 UI Components
<ion-footer class="ion-padding">
<ion-button expand="block" (click)="presentActionSheet()">
Options
</ion-button>
<!-- <ion-button (click)="favService.add(tour);
isFavorite=true;"
*ngIf="!isFavorite"
expand="block">
Add to Favorites
</ion-button>
<ion-button (click)="favService.remove(tour);
isFavorite=false;"
*ngIf="isFavorite"
expand="block">
Remove from Favorites
</ion-button> -->
</ion-footer>
As you can see, we place the “Options” button in the footer division of our page.
This is the usual place for an Action Sheet. The
expand=”block”
attribute specifies the button as an inline full-width block with rounded corners.
With
(click)="presentActionSheet()"
In details.page.ts we add:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { BobToursService }
from 'src/app/services/bob-tours.service';
import { FavoritesService }
from 'src/app/services/favorites.service';
import { ActionSheetController } from '@ionic/angular';
import _ from 'lodash';
@Component({
154
Ionic 5 • 6 UI Components
selector: 'app-details',
templateUrl: './details.page.html',
styleUrls: ['./details.page.scss'],
})
export class DetailsPage implements OnInit {
tour = null;
isFavorite: boolean;
ngOnInit() {
let id = this.activatedRoute.snapshot.paramMap.get('id');
this.tour =
_.find(this.btService.tours, ['ID', parseInt(id)]);
this.isFavorite =
this.favService.favIDs.indexOf(parseInt(id)) != -1;
}
async presentActionSheet() {
const actionSheet = await this.actionSheetCtrl.create({
header: 'Tour',
buttons: [
{
text: 'Request',
handler: () => {
// We implement this later with a Modal Controller.
window.location.href = "/request";
}
},
{
text: (this.isFavorite) ? 'Remove from Favorites'
: 'Add to Favorites',
role: (this.isFavorite) ? 'destructive' : '',
handler: () => {
if (this.isFavorite) {
this.favService.remove(this.tour);
this.isFavorite = false;
155
Ionic 5 • 6 UI Components
} else {
this.favService.add(this.tour);
this.isFavorite = true;
}
}
},
{
text: 'Cancel',
role: 'cancel'
}
]
});
await actionSheet.present();
}
The special thing about this method is that it contains asynchronous calls and
therefore we have to mark the method as async :
async presentActionSheet() {
If you are not yet familiar with the subject of asynchrony, please read the section
"2.11 Async / Await" (starting on page 51) in chapter 2 "Angular Essentials".
156
Ionic 5 • 6 UI Components
This method expects an object as its parameter. Like every JSON object, the ob-
ject expression is enclosed in curly braces. Don't forget them.
and an array of three buttons, where each button is an object again. Let's start with
the first button:
{
text: 'Request',
handler: () => {
// We implement this later with a Modal Controller.
window.location.href = "/request";
}
}
This button gets the label 'Request' as well as an event handler, to which we
pass the path to the next page with a simple href instruction. We'll rebuild this
part later. But for now it works that way.
Via its property text this button gets the label 'Remove from Favorites' or 'Add
to Favorites' , depending on the value of this.isFavorite . You remember
this Boolean variable? If not, please read page 143.
157
Ionic 5 • 6 UI Components
The role property sets a special look - depending on the target platform. Again,
depending on isFavorite , we can ensure that, for example, in the iOS world, the
button turns red when it receives the label 'Remove from Favorites' . This makes
it clear to the user that a click on the button performs a destructive action. On the
other hand, labeled 'Add to Favorites' and without a role property the button
will have the default look for the platform.
T h e h a n d l e r p r o p e r t y c a l l s this.favService.remove(this.tour) or
this.favService.add(this.tour), again – depending on the value of this.is-
Favorite .
Via its property text this button gets the label 'Cancel' . The role gets the value
'cancel' what means, that this button will always load as the bottom button, no
matter where they are in the array and additionally, if the action sheet is dismissed
by tapping the backdrop, then it will fire the handler from the button with the can-
cel role. And since we have not defined a handler for this button, the action sheet
will also be closed when you click this button.
158
Ionic 5 • 6 UI Components
https://ionicframework.com/docs/api/action-sheet
https://ionicframework.com/docs/api/action-sheet-controller
159
Ionic 5 • 6 UI Components
6.3 Alert
An Alert is a dialog that presents users with information or collects information
from the user using inputs. An Alert appears on top of the app's content, and must
be manually dismissed by the user before they can resume interaction with the
app. It can also optionally have a header, subHeader and message.
@Component({
selector: 'app-details',
templateUrl: './details.page.html',
styleUrls: ['./details.page.scss'],
})
export class DetailsPage implements OnInit {
tour = null;
isFavorite: boolean;
ngOnInit() {
let id =
this.activatedRoute.snapshot.paramMap.get('id');
this.tour =
160
Ionic 5 • 6 UI Components
async presentActionSheet() {
const actionSheet = await this.actionSheetCtrl.create({
header: 'Tour',
buttons: [
{
text: 'Request',
handler: () => {
// We implement this later with a Modal Controller.
}
},
{
text: (this.isFavorite) ? 'Remove from Favorites'
: 'Add to Favorites',
role: (this.isFavorite) ? 'destructive' : '',
handler: () => {
if (this.isFavorite) {
this.presentAlert();
//this.favService.remove(this.tour);
//this.isFavorite = false;
} else {
this.favService.add(this.tour);
this.isFavorite = true;
}
}
},
{
text: 'Cancel',
role: 'cancel'
}
]
});
await actionSheet.present();
}
async presentAlert() {
const alert = await this.alertCtrl.create({
header: 'Remove Favorite?',
161
Ionic 5 • 6 UI Components
async presentAlert() {
If you are not yet familiar with the subject of asynchrony, please read the section
"2.11 Async / Await" (starting on page 51) in chapter 2 "Angular Essentials".
162
Ionic 5 • 6 UI Components
This method expects an object as its parameter. Like every JSON object., the ob-
ject expression is enclosed in curly braces. Don't forget them.
Within the object we define a header
header: 'Remove Favorite?',
a message
message: 'Do you really want to remove this Favorite?'
and an array of two buttons, where each button is an object again. Let's start with
the first button:
{
text: 'No'
}
This is a simple 'No' button. Without a handler it closes the Alert by clicking on it.
It's labeled with 'Yes' and owns a handler. This handler contains the code that
were previously placed in presentActionSheet() (see commented lines).
163
Ionic 5 • 6 UI Components
https://ionicframework.com/docs/api/alert-controller
164
Ionic 5 • 6 UI Components
6.4 Badge
Badges are inline block elements that usually appear near another element. Typi-
cally they contain a number or other characters. They can be used as a notification
that there are additional items associated with an element and indicate how many
items there are.
RegionsPage
Let's start with regions.page.ts :
import { Component, OnInit } from '@angular/core';
import { BobToursService }
from 'src/app/services/bob-tours.service';
import _ from 'lodash';
@Component({
selector: 'app-regions',
templateUrl: './regions.page.html',
styleUrls: ['./regions.page.scss'],
})
export class RegionsPage implements OnInit {
regions: any;
constructor(private btService:BobToursService) { }
ngOnInit() {
this.regions = this.btService.regions;
this.regions.forEach(region => {
const tours =
_.filter(this.btService.tours, ['Region', region.ID]);
region['Count'] = tours.length;
});
}
}
165
Ionic 5 • 6 UI Components
In ngOnInit we count. That means we start with a forEach loop across all regions:
this.regions.forEach(region => {
The result of the filtered (counted) tours is saved in a variable tours . So in each
run we can easily grab the length property of tours and assign it on the fly to
each region as new Count property:
region['Count'] = tours.length;
In regions.page.html we add:
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>Regions</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let region of regions"
[routerLink]="['/list', {
Category: 'Region',
ID: region.ID,
Name: region.Name
} ]"
routerDirection="forward">
{{region.Name}}
<ion-badge slot="end">{{region.Count}}</ion-badge>
</ion-item>
</ion-list>
</ion-content>
166
Ionic 5 • 6 UI Components
We use the ion-badge tag in conjunction with the slot=”end” directive for a
right-justified alignment.
167
Ionic 5 • 6 UI Components
TourTypesPage
In the same manner we extend the Tour-Types page.
Let's start with tour-types.page.ts :
import { Component, OnInit } from '@angular/core';
import { BobToursService }
from 'src/app/services/bob-tours.service';
import _ from 'lodash';
@Component({
selector: 'app-tour-types',
templateUrl: './tour-types.page.html',
styleUrls: ['./tour-types.page.scss'],
})
export class TourTypesPage implements OnInit {
tourtypes: any;
constructor(private btService:BobToursService) { }
ngOnInit() {
this.tourtypes = this.btService.tourtypes;
this.tourtypes.forEach(tourtype => {
const tours = _.filter(this.btService.tours,
['Tourtype', tourtype.ID]);
tourtype['Count'] = tours.length;
});
}
In ngOnInit we count. That means we start with a forEach loop across all tour-
types:
this.tourtypes.forEach(tourtype => {
168
Ionic 5 • 6 UI Components
The result of the filtered (counted) tours is saved in a variable tours . So in each
run we can easily grab the length property of tours and assign it on the fly to
each tourtype as new Count property:
tourtype['Count'] = tours.length;
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let tourtype of tourtypes"
[routerLink]="['/list', {
Category: 'Tourtype',
ID: tourtype.ID,
Name: tourtype.Name
} ]"
routerDirection="forward">
{{tourtype.Name}}
<ion-badge slot="end">{{tourtype.Count}}</ion-badge>
</ion-item>
</ion-list>
</ion-content>
We use the ion-badge tag again in conjunction with the slot=”end” directive for
a right-justified alignment of each Badge component.
169
Ionic 5 • 6 UI Components
https://ionicframework.com/docs/api/badge
170
Ionic 5 • 6 UI Components
6.5 Button
Buttons are essential elements for user interaction and navigation in an app. It
should always be clear, which action is triggered by a button. Buttons can have a
label and/or an icon. Their appearance can be influenced by many attributes.
In our app we already use Buttons on nearly every page, so I think it's not neces -
sary to deliver another example for its usage here.
https://ionicframework.com/docs/api/button
171
Ionic 5 • 6 UI Components
6.6 Card
Cards are perfect components to organize informations in a structured manner. For
a mobile user experience Cards are furthermore an easy way to present texts and
images pretty well on different display sizes. A Card can be a single component,
but is often made up of some header, title, subtitle and content.
In our app we can use a Card to show detailed tour informations on the De-
tailsPage .
@Component({
selector: 'app-details',
templateUrl: './details.page.html',
styleUrls: ['./details.page.scss'],
})
export class DetailsPage implements OnInit {
tour = null;
isFavorite: boolean;
region: string;
tourtype: string;
172
Ionic 5 • 6 UI Components
ngOnInit() {
let id = this.activatedRoute.snapshot.paramMap.get('id');
this.tour = _.find(
this.btService.tours,
['ID', parseInt(id)]
);
this.isFavorite =
this.favService.favIDs.indexOf(parseInt(id)) != -1;
this.region = _.find(
this.btService.regions,
{ 'ID': this.tour.Region }
).Name;
this.tourtype = _.find(
this.btService.tourtypes,
{ 'ID': this.tour.Tourtype }
).Name;
}
async presentActionSheet() {
...
}
async presentAlert() {
...
}
First we define two string variables: region and tourtype . These variables will get
the long texts that we want to display:
173
Ionic 5 • 6 UI Components
region: string;
tourtype: string;
We only need the value of the property Name from the found Region object.
.Name;
In the result this value is assigned to this.region . This is our long term that we'll
display.
In the same manner we find the long term for the TourType of a tour and assign it
to this.tourtype.
<ion-content class="ion-padding">
<ion-card>
<ion-card-header>
<ion-card-subtitle>
{{tourtype}} / {{region}}
</ion-card-subtitle>
<ion-card-title>
{{tour.Title}}
</ion-card-title>
</ion-card-header>
<ion-card-content>
174
Ionic 5 • 6 UI Components
{{tour.Description}}
</ion-card-content>
</ion-card>
</ion-content>
<ion-footer class="ion-padding">
<ion-button expand="block" (click)="presentActionSheet()">
Options
</ion-button>
</ion-footer>
• ion-card-subtitle
• ion-card-title
Let's have a look at ion-card-subtitle :
<ion-card-subtitle>
{{tourtype}}/{{region}}
</ion-card-subtitle>
Within this subtitle we display the previously determined long terms of tourtype
and region with typical content projection (see “2.6 Content Projection” on page
39).
we project the tour title (was previously in the header's ion-title tag).
175
Ionic 5 • 6 UI Components
https://ionicframework.com/docs/api/card
176
Ionic 5 • 6 UI Components
6.7 Checkbox
Checkboxes allow the selection of multiple options from a set of options. They ap-
pear as checked (ticked) when activated. Clicking on a Checkbox will toggle the
checked property. By setting the checked property they can also be checked pro-
grammatically.
@Component({
selector: 'app-root',
templateUrl: 'app.component.html'
})
export class AppComponent {
public appPages = [
{
title: 'Favorites',
url: '/favorites',
icon: 'star'
},
{
title: 'Regions',
url: '/regions',
icon: 'images'
},
{
title: 'Tour-Types',
url: '/tour-types',
icon: 'bus'
177
Ionic 5 • 6 UI Components
}
];
constructor(
private platform: Platform,
private splashScreen: SplashScreen,
private statusBar: StatusBar,
public btService: BobToursService
) {
this.initializeApp();
}
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();
this.btService.initialize();
});
}
In app.component.html we add:
<ion-app>
<ion-split-pane contentId="main-content">
<ion-menu contentId="main-content" type="overlay">
178
Ionic 5 • 6 UI Components
<ion-content>
<ion-list id="inbox-list">
<ion-menu-toggle auto-hide="false"
*ngFor="let p of appPages; let i = index">
<ion-item routerDirection="root"
[routerLink]="[p.url]" lines="none"
detail="false">
<ion-icon slot="start"
[ios]="p.icon + '-outline'"
[md]="p.icon + '-sharp'"></ion-icon>
<ion-label>{{ p.title }}</ion-label>
</ion-item>
</ion-menu-toggle>
</ion-list>
</ion-content>
<ion-footer>
<ion-list>
<ion-list-header>Settings</ion-list-header>
<ion-item>
<ion-label>Allow messages</ion-label>
<ion-checkbox [(ngModel)]="settings.notifications"
(ionChange)="updateSettings()">
</ion-checkbox>
</ion-item>
</ion-list>
</ion-footer>
</ion-menu>
<ion-router-outlet id="main-content"></ion-router-outlet>
</ion-split-pane>
</ion-app>
In the new footer area we define a list with a header entry and initially one single
item. Within the item we have two elements: a label 'Allow messages' and a
Checkbox component.
Let's have a closer look at the Checkbox. The line of code
[(ngModel)]="settings.notifications"
179
Ionic 5 • 6 UI Components
means: Bind the content (value) of the Checkbox to the pages' settings.notifi-
cations . The outer brackets represent the binding from sourcetoview and the
inner parentheses from viewtosource. It's Angular's syntax for two-way data bind-
ing. We'll see it in action later.
means: If the user changes the value of the Checkbox, call the updateSettings()
method on our page.
Finally we have to do a last thing. We have to import FormsModule into the app.-
module.ts . If we don't do that we run into an error, because without this module
we wouldn't be able to use [(ngModel)] for data binding.
Note: In every page component this module is already imported.
So let's do it:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [
BrowserModule,
IonicModule.forRoot(),
180
Ionic 5 • 6 UI Components
AppRoutingModule,
HttpClientModule,
IonicStorageModule.forRoot(),
FormsModule
],
providers: [
StatusBar,
SplashScreen,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrate-
gy }
],
bootstrap: [AppComponent]
})
export class AppModule {}
Now let's start our app and see the Checkbox (and console) in action:
https://ionicframework.com/docs/api/checkbox
181
Ionic 5 • 6 UI Components
6.8 Chip
Chips represent complex entities in small blocks, such as a contact. A Chip can
contain several different elements such as avatars, text, and icons.
182
Ionic 5 • 6 UI Components
<ion-title>Tour Details</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-card>
<ion-card-header>
<ion-card-subtitle>{{tourtype}} / {{region}}</ion-card-sub-
title>
<ion-card-title>{{tour.Title}}</ion-card-title>
</ion-card-header>
<ion-card-content>
{{tour.Description}}
<hr />
<ion-chip>
<ion-icon name="stopwatch"></ion-icon>
<ion-label>{{tour.Duration}} min</ion-label>
</ion-chip>
<ion-chip>
<ion-label>up to {{tour.MaxPersons}}</ion-label>
<ion-icon name="people"></ion-icon>
</ion-chip>
<ion-chip>
<ion-icon name="language"></ion-icon>
<ion-label>{{tour.PriceG}} € (German)</ion-label>
</ion-chip>
<ion-chip>
<ion-icon name="globe"></ion-icon>
<ion-label>
{{tour.PriceF}} € (Other language)
</ion-label>
</ion-chip>
</ion-card-content>
</ion-card>
</ion-content>
<ion-footer class="ion-padding">
<ion-button expand="block" (click)="presentActionSheet()">
Options
</ion-button>
</ion-footer>
183
Ionic 5 • 6 UI Components
As you can see, I always combine an icon with a label in the Chips. The result looks
like this:
https://ionicframework.com/docs/api/chip
184
Ionic 5 • 6 UI Components
@Component({
selector: 'app-request',
templateUrl: './request.page.html',
styleUrls: ['./request.page.scss'],
})
export class RequestPage implements OnInit {
constructor() { }
ngOnInit() {
185
Ionic 5 • 6 UI Components
In ngOnInit() , the start and end dates for our Datetime component are calculat-
ed. Here, we assume the following consideration: A tour request should be
possible from the point of view of our imaginary tourism company at the earliest
for the day after tomorrow and a maximum of two years in advance.
Example: If the user asks for a tour on January 1 st 2020, our calculation should
determine January 3rd 2020 (issued as "2020-01-03") as the earliest and January
3rd 2022 (issued as "2022-01-03") as latest inquiry date.
186
Ionic 5 • 6 UI Components
We're using the getTime() function and add a multiplication with milliseconds, sec-
onds, minutes, hours, and two days.
Since the Datetime component expects calendar data in ISO 8601 format, we
f o r m a t i t w i t h toISOString() and assign it to the string variable
this.day_after_tomorrow . We're not interested in the time, so we use
slice(0,10) to shorten the string to the first 10 digits:
this.day_after_tomorrow
= day_after_tomorrow.toISOString().slice(0, 10);
In the same manner we calculate a date (end date) two years later and use the
previously calculated day_after_tomorrow:
let two_years_later
= new Date(day_after_tomorrow.getTime()
+ 1000*60*60*24*365*2);
Again, we convert the whole thing into an ISO string and assign the (shortened) re-
sult to the string variable this.two_years_later :
this.two_years_later = two_years_later.toISOString()
.slice(0, 10);
<ion-content class="ion-padding">
<ion-list>
187
Ionic 5 • 6 UI Components
<ion-footer class="ion-padding">
<ion-button expand="block"
routerLink="/favorites"
routerDirection="root">
Back to Favorites
</ion-button>
<ion-button expand="block" (click)="send()">
Send request
</ion-button>
</ion-footer>
Within the content area we define a list with two list items. Each list item contains a
label and a datetime component, one for the date and one for the time.
Let's have a look at the first line of code of the datetime component for the date:
<ion-datetime [(ngModel)]="request.Date"
188
Ionic 5 • 6 UI Components
This line of code binds the selected date value of the component via [(ngModel)]
(Angular's two-way data binding) to the property Date of our page's request ob-
ject. With
min="{{day_after_tomorrow}}"
we determine how the date should be formatted and displayed after leaving the
input interface, whereas
picker-format="MMMM DD YYYY"
determines which columns should be shown in the interface, the order of the col-
umns, and which format to use within each column.
A list with all valid formatting expressions you can find in the link below.
With
placeholder="Please choose your desired date!"
Now let's have a look at the Datetime component for the time:
<ion-datetime [(ngModel)]="request.Time"
binds the selected time value of the component via [(ngModel)] to the property
Time of our page's request object. With
hourValues="9,10,11,12,13,14,15,16,17"
we control exactly which hours to display, the hourValues input can take a num-
ber, an array of numbers, or a string of comma separated numbers.
With
MinuteValues="0,15,30,45"
we control exactly which minutes to display, the minuteValues input can take a
number, an array of numbers, or a string of comma separated numbers.
189
Ionic 5 • 6 UI Components
we determine how the time should be formatted and displayed after leaving the
input interface, whereas
picker-format="h mm"
determines which columns should be shown in the interface, the order of the col-
umns, and which format to use within each column.
A list with all valid formatting expressions you can find in the link below.
With
placeholder="Please choose your desired time!"
190
Ionic 5 • 6 UI Components
More about Date and Time Pickers as well as about an universal Picker compo-
nent you can find here:
https://ionicframework.com/docs/api/datetime
https://ionicframework.com/docs/api/picker
If you need time-consuming date functions, I can recommend the JavaScript library
moment.js:
https://momentjs.com/
191
Ionic 5 • 6 UI Components
If the FAB button isn't wrapped with <ion-fab> , it will scroll with the content. FAB
buttons have a default size, a mini size and can accept different colors.
@Component({
selector: 'app-details',
templateUrl: './details.page.html',
styleUrls: ['./details.page.scss'],
})
export class DetailsPage implements OnInit {
tour = null;
isFavorite: boolean;
region: string;
192
Ionic 5 • 6 UI Components
tourtype: string;
showSocial: boolean;
ngOnInit() {
...
}
async presentActionSheet() {
...
}
async presentAlert() {
...
}
Another method openSocial() is initially only for the output of a text in the con-
sole to show what social app has been chosen.
193
Ionic 5 • 6 UI Components
<ion-content class="ion-padding">
...
</ion-content>
194
Ionic 5 • 6 UI Components
Our “Options” button for calling the ActionSheet (see “6.2 Action Sheet” on page
153) is now controlled by
*ngIf="!showSocial"
That means: If the value of showSocial is false , show the “Options” button, other-
wise hide it. As you will soon see, our FAB button needs space to spread out. It
gets this space by hiding the "Options" buttons in the manner just described.
With
<ion-fab vertical="bottom" horizontal="end" slot="fixed"
(click)="toggleSocial()">
we place a FAB button on the right end in the footer area. We add a click handler
to call to our toggleSocial() method that controls the toogleSocial variable.
https://ionicframework.com/docs/api/fab
https://ionicframework.com/docs/api/fab-list
195
Ionic 5 • 6 UI Components
6.11 Grid
Ionic's Grid system is a powerful mobile-first system for building custom layouts
and based on the Flexible Box Layout Module (short: Flexbox). Flexbox was de-
signed as a one-dimensional layout model, and as a method that could offer space
distribution between items in an interface and powerful alignment capabilities.
When I describe Flexbox as being one dimensional I'm describing the fact that
Flexbox deals with layout in one dimension at a time — either as a row or as a col
umn. This can be contrasted with the two-dimensional model of CSS Grid Layout,
which controls columns and rows together.
Grid is composed of three units — a grid, row(s) and column(s). Columns will ex-
pand to fill the row, and will resize to fit additional columns. It is based on a 12
column layout with different breakpoints based on the screen size. The number of
columns can be customized using CSS.
<ion-content class="ion-padding">
<ion-card>
<ion-card-header>
<ion-card-subtitle>{{tourtype}} / {{region}}</ion-card-
subtitle>
<ion-card-title>{{tour.Title}}</ion-card-title>
</ion-card-header>
<ion-card-content>
{{tour.Description}}
<hr />
<ion-grid no-padding>
<ion-row style="margin-left: -12px;">
<ion-col size-xs="12" size-md="auto">
<ion-chip>
196
Ionic 5 • 6 UI Components
<ion-icon name="stopwatch"></ion-icon>
<ion-label>{{tour.Duration}} min</ion-label>
</ion-chip>
</ion-col>
<ion-col size-xs="12" size-md="auto">
<ion-chip>
<ion-label>up to {{tour.MaxPersons}}</ion-label>
<ion-icon name="people"></ion-icon>
</ion-chip>
</ion-col>
<ion-col size-xs="12" size-md="auto">
<ion-chip>
<ion-icon name="language"></ion-icon>
<ion-label>
{{tour.PriceG}} € (German)
</ion-label>
</ion-chip>
</ion-col>
<ion-col size-xs="12" size-md="auto">
<ion-chip>
<ion-icon name="globe"></ion-icon>
<ion-label>
{{tour.PriceF}} € (Other language)
</ion-label>
</ion-chip>
</ion-col>
</ion-row>
</ion-grid>
</ion-card-content>
</ion-card>
</ion-content>
We define a Grid with one row and four columns. Its task is, depending on the
available display width, to arrange the chips either in one or more rows or, if it is
particularly narrow, among themselves.
197
Ionic 5 • 6 UI Components
The first attribute size-xs="12" specifies that with extra small (xs ) display size, one
column will get the full width of 12 units.
The second attribute size-md="auto" specifies that with middle (md ) display size,
one column will get an automatic width.
The default breakpoints are for xs a min-width of 0px and for md a min-width of
768px . In other words: If the display has a width up to 767px, each chip gets a full
12-unit-wide column, so each Chip stands in its own “row” (visually speaking, not
technical). If the display has a width greater than 767px, each column gets an au-
tomatic width, i.e. each becomes as wide as the respective embedded Chip needs
as space.
You'll find the information for these and all other breakpoints in the Ionic docu -
mentation (3rd link below).
198
Ionic 5 • 6 UI Components
The left picture shows our app on an iPhone screen. In portrait mode my iPhone 6
has a width of 414px, so the xs directive makes sure that each Chip is displayed
in a full width column (with 12 units).
The right picture shows our app on an iPad screen. In portrait mode it has a width
of 768px – exact the width of the md breakpoint, so it arranges all Chips in one
single row, because every column uses auto-width and everything together fits in a
single row.
https://ionicframework.com/docs/api/grid
https://ionicframework.com/docs/layout/grid
https://ionicframework.com/docs/layout/css-utilities#ionic-breakpoints
199
Ionic 5 • 6 UI Components
6.12 Icons
Pictures say more than a thousand words. We see a symbol and immediately un-
derstand what is meant. That's why there is no app that wants to be operated
intuitively, comes over to icons.
Ionic has more than 1.100 (!) Icons that can be addressed via a name attribute. Ion-
ic calls them Ionicons (an acronym for Ionic icons, of course).
200
Ionic 5 • 6 UI Components
The Ionicons are available in three versions: Outline , Filled , Sharp . You can
choose a version by its name , for example:
201
Ionic 5 • 6 UI Components
With this data, it's easy to add icons to our list in regions.page.html :
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>Regions</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let region of regions"
[routerLink]="['/list',
{ Category: 'Region',
ID: region.ID,
Name: region.Name } ]"
routerDirection="forward">
<ion-icon name="{{region.Icon}}"
slot="start"></ion-icon>
{{region.Name}}
<ion-badge slot="end">{{region.Count}}</ion-badge>
</ion-item>
</ion-list>
</ion-content>
The use of the Ionicon component is very simple: Using the Name property, we
pass the name from the respective region object via Code Projection. Via the slot
attribute we determine that the icon should be displayed at the start (beginning)
of a list entry. And that's the optical result:
202
Ionic 5 • 6 UI Components
<ion-content class="ion-padding">
<ion-list>
203
Ionic 5 • 6 UI Components
https://ionicons.com/
204
Ionic 5 • 6 UI Components
6.13 Images
What would an app be without pictures or photos? I'm sure you've been waiting
for this topic, right?
Of course, Ionic makes it easy to get pictures into our app. For this we can use the
standard HTML tag img or the new Ionic component ion-img . The ion-img com-
ponent is a tag that will lazily load an image when ever the tag is in the viewport.
This is extremely useful when generating a large list as images are only loaded
when they're visible.
<ion-content class="ion-padding">
<ion-card>
<ion-card-header>
<ion-card-subtitle>
{{tourtype}} / {{region}}
</ion-card-subtitle>
<ion-card-title>{{tour.Title}}</ion-card-title>
</ion-card-header>
<ion-card-content>
<ion-img src="https://ionic.andreas-dormann.de/img/big/
{{tour.Image}}"></ion-img>
<hr />
{{tour.Description}}
<hr />
<ion-grid no-padding>
...
</ion-grid>
</ion-card-content>
</ion-card>
</ion-content>
205
Ionic 5 • 6 UI Components
The ion-img component has a src attribute to which we pass the path to our on-
line image. The name of the respective image file is obtained from the loaded
database data via Code Projection.
206
Ionic 5 • 6 UI Components
Hint: Since Ionic 4.2 it is possible to use ion-img combined with the ionError
event. The ionError event is emitted when an image fails to load. This can be
useful to load default images in case the specified image isn't found.
More about Images and related topics you can find here:
https://ionicframework.com/docs/api/avatar
https://ionicframework.com/docs/api/img
https://ionicframework.com/docs/api/thumbnail
207
Ionic 5 • 6 UI Components
6.14 Input
Input components are important for collecting and managing user input. They
should follow platform-specific guidelines and be intuitive to use. Ionic provides all
the necessary ingredients for this.
The Input component in Ionic is a wrapper to the HTML input element with cus-
tom styling and additional functionality. It accepts most of the same properties as
the HTML input, but works great on desktop devices and integrates with the key-
board on mobile devices.
It is meant for text type inputs only, such as "text" , "password" , "email" , "num-
ber" , "search" , "tel" , and "url" . It supports all standard text input events
including keyup, keydown, keypress, and more.
<ion-content class="ion-padding">
<ion-list>
<!-- Date -->
<ion-item>
<ion-label>Date</ion-label>
<ion-datetime [(ngModel)]="request.Date"
min="{{day_after_tomorrow}}"
max="{{two_years_later}}"
display-format="DDD, MMMM DD YYYY"
picker-format="MMMM DD YYYY"
placeholder="Choose your desired date!">
</ion-datetime>
</ion-item>
208
Ionic 5 • 6 UI Components
<ion-footer class="ion-padding">
<ion-button expand="block"
routerLink="/favorites"
routerDirection="root">
Back to Favorites
</ion-button>
<ion-button expand="block" (click)="send()">
Send request
</ion-button>
</ion-footer>
209
Ionic 5 • 6 UI Components
In each ion-item tag we combine a label with an Input component. Nothing fancy.
Have a look at the Email Input. There we use type=”email” . That means on a
mobile device a different virtual keyboard is shown.
Again, we bind the user data via [(ngModel)] directives to our request object:
FirstName , LastName and Email .
...
constructor() { }
...
ngOnInit() {
...
}
console.log('by', this.request.FirstName,
this.request.LastName,
this.request.Email);
}
}
210
Ionic 5 • 6 UI Components
Our updated RequestPage with First name, Last name and Email Inputs:
More about Input – and its relative: TextArea – you can find here:
https://ionicframework.com/docs/api/input
https://ionicframework.com/docs/api/textarea
211
Ionic 5 • 6 UI Components
6.15 List
Lists are used to replace information in a line. They represent a central design
component in many apps - as in our app.
Lists are made up of multiple rows of items which can contain text, buttons, tog-
gles, icons, thumbnails, and much more. Lists generally contain items with similar
data content, such as images and text.
Lists support several interactions including swiping items to reveal options, dragging
to reorder items within the list, and deleting items.
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let tour of tours"
[routerLink]="'/details/' + tour.ID"
routerDirection="forward">
<ion-thumbnail slot="start">
<img src="https://ionic.andreas-dormann.de/img/small/
{{tour.Image}}">
</ion-thumbnail>
<ion-label>
<h2 class="ion-text-wrap">{{tour.Title}}</h2>
<p>Duration: {{tour.Duration}} min</p>
</ion-label>
212
Ionic 5 • 6 UI Components
</ion-item>
</ion-list>
</ion-content>
we added an ion-thumbnail tag at the start slot of each list item. To show a pic-
ture within a thumbnail we need to embed an image:
<img src="https://ionic.andreas-dormann.de/img/small/
{{tour.Image}}">
As source we use an absolute file path to the (online) picture in combination with
the code injected variable tour.Image . It's the name of the image, for example
'Bonn.jpg' , that comes from the tour data from our database.
213
Ionic 5 • 6 UI Components
214
Ionic 5 • 6 UI Components
<ion-content class="ion-padding">
<ion-list>
215
Ionic 5 • 6 UI Components
</ion-list>
</ion-content>
<ion-footer class="ion-padding">
...
</ion-footer>
216
Ionic 5 • 6 UI Components
Here's our RequestPage with more structure (left: iOS style, right: Android style):
217
Ionic 5 • 6 UI Components
You can find more about Lists and related topics here:
https://ionicframework.com/docs/api/list
https://ionicframework.com/docs/api/avatar
https://ionicframework.com/docs/api/item-divider
https://ionicframework.com/docs/api/item-group
https://ionicframework.com/docs/api/label
https://ionicframework.com/docs/api/list-header
https://ionicframework.com/docs/api/thumbnail
218
Ionic 5 • 6 UI Components
6.16 Menu
The Menu component is a navigation drawer that slides in from the side of the
current view. By default, it slides in from the left, but the side can be overridden.
The Menu will be displayed differently based on the mode, however the display
type can be changed to any of the available Menu types. The Menu element
should be a sibling to the root content element. There can be any number of
Menus attached to the content. These can be controlled from the templates, or
programmatically using the MenuController.
We had good luck to use the side menu template to start our app, because the
whole structure for using a Menu is already built in. But this is the right place to
have a closer look at our app's UI architecture.
Let's inspect app.component.html and see how ion-menu and the related ion-
menu-toggle are used:
<ion-app>
<ion-split-pane contentId="main-content">
<ion-content>
<ion-list id="inbox-list">
<ion-menu-toggle auto-hide="false"
*ngFor="let p of appPages; let i = index">
<ion-item routerDirection="root"
[routerLink]="[p.url]"
lines="none" detail="false">
<ion-icon slot="start"
[ios]="p.icon + '-outline'"
[md]="p.icon + '-sharp'"></ion-icon>
<ion-label>{{ p.title }}</ion-label>
</ion-item>
</ion-menu-toggle>
</ion-list>
</ion-content>
<ion-footer>
<ion-list>
219
Ionic 5 • 6 UI Components
<ion-list-header>Settings</ion-list-header>
<ion-item>
<ion-label>Allow messages</ion-label>
<ion-checkbox [(ngModel)]="settings.notifications"
(ionChange)="updateSettings()">
</ion-checkbox>
</ion-item>
</ion-list>
</ion-footer>
</ion-menu>
<ion-router-outlet id="main-content"></ion-router-outlet>
</ion-split-pane>
</ion-app>
• a content area
• a footer area
Thus, the structure of a Menu corresponds to the structure of a page (we could
add a header area, too).
We use it here with an *ngFor loop through all entries in the appPages array.
By default, it's only visible when the selected Menu is active. A Menu is active
when it can be opened/closed. If the Menu is disabled or it's being presented as a
Split-Pane (as in our case), the Menu is marked as non-active and ion-menu-tog-
gle hides itself. In our case it's desired to keep ion-menu-toggle always visible, so
the autoHide property is set to false .
220
Ionic 5 • 6 UI Components
There are a few related topics to Menu. You can find them here:
https://ionicframework.com/docs/api/menu
https://ionicframework.com/docs/api/menu-button
https://ionicframework.com/docs/api/menu-controller
https://ionicframework.com/docs/api/menu-toggle
https://ionicframework.com/docs/api/split-pane
221
Ionic 5 • 6 UI Components
6.17 Modal
Modals are used as temporary slide ins, often for something like logins or a choice
of options, or when filtering items in a list, as well as many other use cases. A
Modal is a dialog that appears on top of the app's content, and must be dismissed
by the app before interaction can resume. Modals can be created using a Modal
Controller. They can be customized by passing modal options in the Modal con-
troller's create method.
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
DetailsPageRoutingModule
222
Ionic 5 • 6 UI Components
],
declarations: [DetailsPage, RequestPage],
entryComponents: [RequestPage]
})
export class DetailsPageModule { }
When lazy loading a Modal (our RequestPage), it's important to note that the
Modal won't be loaded when it's opened, but rather when the module that im -
ports the Modal's module (our DetailsPage) is loaded. So it's necessary to import
our RequestPage into the module of the DetailsPage, namely details.module.ts .
223
Ionic 5 • 6 UI Components
})
export class DetailsPage implements OnInit {
tour = null;
isFavorite: boolean;
region: string;
tourtype: string;
showSocial: boolean;
ngOnInit() {
...
}
// Action Sheet
async presentActionSheet() {
const actionSheet = await this.actionSheetCtrl.create({
header: 'Tour',
buttons: [
{
text: 'Request',
handler: () => {
this.presentModal();
// window.location.href = "/request";
}
},
{
text: (this.isFavorite) ? 'Remove from Favorites'
: 'Add to Favorites',
role: (this.isFavorite) ? 'destructive' : '',
handler: () => {
if (this.isFavorite) {
this.presentAlert();
//this.favService.remove(this.tour);
224
Ionic 5 • 6 UI Components
//this.isFavorite = false;
} else {
this.favService.add(this.tour);
this.isFavorite = true;
}
}
},
{
text: 'Cancel',
role: 'cancel'
}
]
});
await actionSheet.present();
}
async presentAlert() {
...
}
toggleSocial() {
...
}
openSocial(app) {
...
}
}
To be able to show a component as Modal we need a ModalController. So we im-
port it with
import { ..., ..., ModalController }
225
Ionic 5 • 6 UI Components
now through
this.presentModal();
<ion-header>
<ion-toolbar>
<!-- <ion-buttons slot="start">
226
Ionic 5 • 6 UI Components
<ion-back-button></ion-back-button>
</ion-buttons> -->
<ion-title>Request</ion-title>
<ion-buttons slot="end">
<ion-button (click)="cancel()">Cancel</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-list>
227
Ionic 5 • 6 UI Components
228
Ionic 5 • 6 UI Components
</ion-list>
</ion-content>
we place a Cancel button in the upper right corner and assign a cancel()
method in the click handler, which we will write shortly.
<ion-item-divider>
<ion-label>Tour</ion-label>
</ion-item-divider>
and a label with a header and a paragraph to show the title and the duration of a
tour:
<ion-label>
<h2 text-wrap>{{tour.Title}}</h2>
<p>Duration: {{tour.Duration}} min</p>
</ion-label>
229
Ionic 5 • 6 UI Components
@Component({
selector: 'app-request',
templateUrl: './request.page.html',
styleUrls: ['./request.page.scss'],
})
export class RequestPage implements OnInit {
constructor(
private modalCtrl: ModalController,
private navParams: NavParams
) {
this.tour = navParams.data;
}
ngOnInit() {
...
}
send() {
...
}
230
Ionic 5 • 6 UI Components
In order to be able to receive the tour data that we have submitted via the compo-
nentProps property of the ModalController in details.page.ts , we need
NavParams . We import this with
So with
this.tour = navParams.data;
we can assign the data to the previously declared and set tour object.
At the end: You also have to be able to close the modal somehow.
For this we need the ModalController class that we import with
import { ModalController, ... } from '@ionic/angular';
That's it.
231
Ionic 5 • 6 UI Components
https://ionicframework.com/docs/api/modal
https://ionicframework.com/docs/api/modal-controller
https://ionicframework.com/docs/api/backdrop
232
Ionic 5 • 6 UI Components
6.18 Popover
A Popover is a dialog that appears on top of the current page. It can be used for
anything, but generally it is used for overflow actions that don't fit in the navigation
bar. To present a Popover, call the present method on a Popover instance. In or-
der to position the Popover relative to the element clicked, a click event needs to
be passed into the options of the the present method. If the event isn't passed, the
Popover will be positioned in the center of the viewport.
1. Create an AboutComponent
You've never created a component before? Of course, you did! Because you cre-
ated pages - and pages are components. Creating an ordinary component is very
similar to creating a page. We use the Ionic CLI for this task:
$ ionic g component components/about
Of course you can use your name. After all, it's your app ;-)
233
Ionic 5 • 6 UI Components
import { AboutComponent }
from './components/about/about.component';
@NgModule({
declarations: [AppComponent, AboutComponent],
entryComponents: [AboutComponent],
imports: [
BrowserModule,
IonicModule.forRoot(),
AppRoutingModule,
HttpClientModule,
IonicStorageModule.forRoot(),
FormsModule
],
providers: [
StatusBar,
SplashScreen,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrate-
gy }
],
bootstrap: [AppComponent]
})
234
Ionic 5 • 6 UI Components
@Component({
selector: 'app-root',
templateUrl: 'app.component.html'
})
export class AppComponent {
public appPages = [
...
];
constructor(
235
Ionic 5 • 6 UI Components
initializeApp() {
...
}
236
Ionic 5 • 6 UI Components
The create method expects an object with at least a property component , where
we assign our AboutComponent:
component: AboutComponent
we influence the the appearance of the component. It's not hard to guess how.
We don't need a close method. A simple tap outside the Popover will close it au -
tomatically.
<ion-split-pane>
<ion-menu>
<ion-header>
<ion-toolbar>
<ion-title>Menu</ion-title>
</ion-toolbar>
</ion-header>
237
Ionic 5 • 6 UI Components
<ion-content>
<ion-list>
<ion-menu-toggle *ngFor="let p of appPages"
auto-hide="false">
<ion-item [routerDirection]="'root'"
[routerLink]="[p.url]">
<ion-icon slot="start"
[name]="p.icon"></ion-icon>
<ion-label>
{{p.title}}
</ion-label>
</ion-item>
</ion-menu-toggle>
<ion-item (click)="about()">
<ion-icon slot="start" name="information-circle">
</ion-icon>
<ion-label>About this app</ion-label>
</ion-item>
</ion-list>
</ion-content>
<ion-footer>
...
</ion-footer>
</ion-menu>
<ion-router-outlet main></ion-router-outlet>
</ion-split-pane>
</ion-app>
In the content area, directly below the closing </ion-menu-toggle> tag, we add a
new list item with embedded icon and label. A click handler references to the
about() method we created before.
That's it.
238
Ionic 5 • 6 UI Components
https://ionicframework.com/docs/api/popover
https://ionicframework.com/docs/api/popover-controller
239
Ionic 5 • 6 UI Components
• ion-loading / ion-loading-controller
• ion-progress-bar
• ion-skeleton-text
• ion-spinner
@Injectable({
providedIn: 'root'
})
export class BobToursService {
baseUrl = 'https://bob-tours-app.firebaseio.com/';
async initialize() {
240
Ionic 5 • 6 UI Components
getRegions() {
let requestUrl = `${this.baseUrl}/Regions.json`;
return this.http.get(requestUrl).toPromise();
}
getTourtypes() {
let requestUrl = `${this.baseUrl}/Tourtypes.json`;
return this.http.get(requestUrl).toPromise();
}
getTours() {
let requestUrl = `${this.baseUrl}/Tours.json`;
return this.http.get(requestUrl).toPromise();
}
241
Ionic 5 • 6 UI Components
That's crucial. Because our LoadingController should be displayed before the start
of the service calls and only be hidden after finishing the last call. This is definitely
an asynchronous workflow, isn't it?
With
const loading = await this.loadingCtrl.create({
we create a LoadingController. And as you already know from the many other
controllers, its create method expects an object as a parameter. Its property mes-
sage gets textual information about what's happening:
With the property spinner we define the appearance of the loading animation:
spinner: 'crescent'
There are a few more spinner animations: "bubbles" | "circles" | "crescent" | "dots" |
"lines" | "lines-small" | null | undefined
If a value isn't passed to spinner the Loading Indicator will use the spinner speci-
fied by the platform.
All subsequent service calls are made asynchronous (prefixed with async again),
i.e.. we wait until each call ends before we start the next one:
await this.getRegions().then(...);
await this.getTourtypes().then(...);
await this.getTours().then(...);
A word about this workflow: In real life, we would parallelize service calls to save
the user's time. In our case, the calls are so brief that we would not have much
242
Ionic 5 • 6 UI Components
https://ionicframework.com/docs/api/loading
https://ionicframework.com/docs/api/loading-controller
https://ionicframework.com/docs/api/progress-bar
https://ionicframework.com/docs/api/skeleton-text
https://ionicframework.com/docs/api/spinner
243
Ionic 5 • 6 UI Components
6.20 Radio
Like the Checkbox a Radio is an input component representing a boolean value.
Radios are generally used as a set of related options inside of a group, but they
can also be used alone. Pressing on a Radio will check it. They can also be
checked programmatically by setting the checked property.
An ion-radio-group can be used to group a set of radios. When Radios are in-
side of a Radio Group, only one Radio in the group will be checked at any time.
Pressing a Radio will check it and uncheck the previously selected Radio, if there is
one. If a Radio isn't in a group with another Radio, then both Radios will have the
ability to be checked at the same time.
@Component({
selector: 'app-root',
templateUrl: 'app.component.html'
})
export class AppComponent {
public appPages = [
...
];
244
Ionic 5 • 6 UI Components
constructor(
private platform: Platform,
private splashScreen: SplashScreen,
private statusBar: StatusBar,
public btService: BobToursService,
private popoverCtrl: PopoverController,
private storage: Storage
) {
this.initializeApp();
}
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();
this.btService.initialize();
this.loadSettings();
});
}
// Load settings
loadSettings() {
this.storage.ready().then(() => {
this.storage.get('settings').then(settings => {
if (settings == null) {
this.settings.style = 'summer-style';
} else {
this.settings = settings;
}
});
});
}
245
Ionic 5 • 6 UI Components
component: AboutComponent,
translucent: true
});
await popover.present();
}
we get the storage content of the settings key. At the very first start of the app
the content will be null , so with
if (settings == null) {
this.settings.style = 'summer-style';
}
246
Ionic 5 • 6 UI Components
We'll now add a radio group and two ion-radio items in app.component.html :
<ion-app>
<ion-split-pane>
<ion-menu>
<ion-header>
<ion-toolbar>
<ion-title>Menu</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
...
</ion-content>
<ion-footer>
<ion-list>
<ion-list-header>Settings</ion-list-header>
<ion-radio-group [(ngModel)]="settings.style"
(ionChange)="updateSettings()">
<ion-item>
<ion-label>Azure-Style</ion-label>
<ion-radio value="azure-style"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Summer-Style</ion-label>
<ion-radio value="summer-style"></ion-radio>
</ion-item>
</ion-radio-group>
<ion-item>
<ion-label>Allow messages</ion-label>
<ion-checkbox
[(ngModel)]="settings.notifications"
247
Ionic 5 • 6 UI Components
(ionChange)="updateSettings()">
</ion-checkbox>
</ion-item>
</ion-list>
</ion-footer>
</ion-menu>
<ion-router-outlet main></ion-router-outlet>
</ion-split-pane>
</ion-app>
With the attribute ion-radio-group our list becomes a component, within which
there can only ever be one selected Radio element. With the instruction
[(ngModel)]="settings.style"
we tie the Radio Group to the new style property of our settings object. This
receives the respective value of the selected Radios, either "azure-style" or "sum-
mer-style" . Now to the instruction
(ionChange)="updateSettings()"
248
Ionic 5 • 6 UI Components
Here is our app with the extended side menu and Radios:
You can also use the Chrome Developer Tools > Application > Storage to
take a look at the storage and view the saved user settings.
More informations about Radios and Radio Groups you can find here:
https://ionicframework.com/docs/api/radio
https://ionicframework.com/docs/api/radio-group
249
Ionic 5 • 6 UI Components
6.21 Range
A Range is an input element that allows the user to enter a range of values. A
Range consists of one or two sliders that move along a bar.
Labels can be placed on either side of the Range by adding the range-left or
range-right property to the element. The element doesn't have to be an ion-
label , it can be added to any element to place it to the left or right of the Range.
Minimum and maximum values can be passed to the Range through the min and
max properties, respectively. By default, the Range sets the min to 0 and the max to
100 .
The step property specifies the value granularity of the Range's value. It can be
useful to set the step when the value isn't in increments of 1. Setting the step
property will show tick marks on the Range for each step. The snaps property can
be set to automatically move the knob to the nearest tick mark based on the step
property value.
Setting the dualKnobs property to true on the Range component will enable two
knobs on the Range. If the Range has two knobs, the value will be an object con-
taining two properties: lower and upper .
@Injectable({
providedIn: 'root'
})
export class BobToursService {
250
Ionic 5 • 6 UI Components
baseUrl = 'https://bob-tours-app.firebaseio.com/';
async initialize() {
const loading = await this.loadingCtrl.create({
message: 'Loading tour data...',
spinner: 'crescent'
});
await loading.present();
await this.getRegions().then(data => this.regions = data);
await this.getTourtypes()
.then(data => this.tourtypes
= _.sortBy(data, 'Name'));
await this.getTours().then(data => {
this.tours = _.sortBy(data, 'Title');
this.all_tours = _.sortBy(data, 'Title');
this.favService.initialize(this.all_tours);
});
await loading.dismiss();
}
251
Ionic 5 • 6 UI Components
we put there all tour data from the database sorted by Title . Now follows the fil-
ter function filterTours(price) , which receives as a parameter a price, more
precisely, a price range with the properties lower (lowest price range) and upper
(highest price range).
In an Lodash filter function, we now check each tour for whether its price is within
this price range. As a result we get back an array with all suitable tours in this.-
tours . In other words, from the total amount of tours in this.all_tours , we'll
filter out the priced tours and give them to this.tours . The function itself delivers
over
return this.tours.length;
Now we integrate the new filter function in our side menu code. Therefore we
complete app.component.ts :
import { Component } from '@angular/core';
import { Platform, PopoverController } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-
screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';
252
Ionic 5 • 6 UI Components
@Component({
selector: 'app-root',
templateUrl: 'app.component.html'
})
export class AppComponent {
public appPages = [
...
];
constructor(
private platform: Platform,
private splashScreen: SplashScreen,
private statusBar: StatusBar,
public btService: BobToursService,
private popoverCtrl: PopoverController,
private storage: Storage
) {
this.initializeApp();
}
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();
this.btService.initialize();
this.loadSettings();
});
}
// Loading settings
loadSettings() {
...
253
Ionic 5 • 6 UI Components
First, we declare the two new variables price and hits . To price we assign an
object with the properties lower and upper and the values 80 and 400. Both
variables will soon be bound to the UI.
In filterByPrice() we call the previously defined filterTours method and pass
the price range object to it.
At the end we add the price range filter to our UI and add the following HTML
lines to app.component.html :
<ion-app>
<ion-split-pane>
<ion-menu>
<ion-header>
<ion-toolbar>
<ion-title>Menu</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
254
Ionic 5 • 6 UI Components
...
</ion-content>
<ion-footer>
<ion-list>
<ion-radio-group [(ngModel)]="settings.style"
(ionChange)="updateSettings()">
<ion-list-header>
Price from {{price.lower}} to {{price.upper}} EUR
({{hits}} hits)
<ion-badge slot="end">{{hits}}</ion-badge>
</ion-list-header>
<ion-item>
<ion-range min="80" max="400" step="20"
pin="true" snaps="true"
dualKnobs="true"
[(ngModel)]="price"
(ionChange)="filterByPrice()">
<ion-label slot="start">80</ion-label>
<ion-label slot="end">400</ion-label>
</ion-range>
</ion-item>
<ion-list-header>Settings</ion-list-header>
<ion-item>
<ion-label>Azure-Style</ion-label>
<ion-radio value="azure-style"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Summer-Style</ion-label>
<ion-radio value="summer-style"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Allow messages</ion-label>
<ion-checkbox [(ngModel)]="settings.notifications "
(ionChange)="updateSettings()">
255
Ionic 5 • 6 UI Components
</ion-checkbox>
</ion-item>
</ion-radio-group>
</ion-list>
</ion-footer>
</ion-menu>
<ion-router-outlet main></ion-router-outlet>
</ion-split-pane>
</ion-app>
we create a list heading, which is the current price range as well as the number of
hits of the filtering in brackets.
ensures that the current value is displayed when a slider is pressed and with
snaps="true"
the knob snaps to tick marks evenly spaced based on the step property value.
dualKnobs="true"
256
Ionic 5 • 6 UI Components
we respond to any change that the user makes via the sliders. We've just written
the function.
Optimizations
We should make a small optimization. Since it can now happen that a column has
no more tours to offer due to a "strict" filtering, i.e. displays its Badge value 0, we
should spare the user that he clicks here in the void. In such a case we will deacti-
vate the relevant list entry.
Here is the supplement in regions.page.html:
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>Regions</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let region of regions"
[routerLink]="['/list', {
Category: 'Region',
ID: region.ID,
Name: region.Name
257
Ionic 5 • 6 UI Components
} ]"
routerDirection="forward"
[disabled]="region.Count==0">
<ion-icon name="{{region.Icon}}" slot="start">
</ion-icon>
{{region.Name}}
<ion-badge slot="end">{{region.Count}}</ion-badge>
</ion-item>
</ion-list>
</ion-content>
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let tourtype of tourtypes"
[routerLink]="['/list', {
Category: 'Tourtype',
ID: tourtype.ID,
Name: tourtype.Name
}]"
routerDirection="forward"
[disabled]="tourtype.Count==0">
<ion-icon name="{{tourtype.Icon}}" slot="start">
</ion-icon>
{{tourtype.Name}}
<ion-badge slot="end">{{tourtype.Count}}</ion-badge>
</ion-item>
</ion-list>
</ion-content>
258
Ionic 5 • 6 UI Components
Our app has a little flaw – maybe you have already noticed it: The Badge num-
bers that indicate the number of available tours, update only when we switch from
“Tour-Types” to “Regions” or vice versa in the side menu. But an update should
take place immediately as soon as the price range is changed. We'll fix that in
Chapter 8 “Theming, Styling, Customizing” (see “UI-Design for Tablets (Split Pane
Layout)” on page 382).
More informations about the Range component you can find here:
https://ionicframework.com/docs/api/range
259
Ionic 5 • 6 UI Components
6.22 Reorder
Lists that can be freely ordered by the user are a standard functionality that an
app should master. Fortunately, Ionic supports us in this, so that the implementa-
tion isn't a big deal.
It's Ionic's Reorder component that allows an item to be dragged to change its or-
der. It must be used within an ion-reorder-group to provide a visual drag and
drop interface.
ion-reorder is the anchor users will use to drag and drop items inside the ion-
reorder-group .
@Injectable({
providedIn: 'root'
})
export class FavoritesService {
initialize(tours) {
this.favTours = [];
this.storage.ready().then(() => {
this.storage.get('FavoritesIDs').then(ids => {
this.favIDs = ids;
if (this.favIDs == null) {
this.favIDs = [];
} else {
this.favIDs.forEach(favID => {
260
Ionic 5 • 6 UI Components
add(tour) {
this.favIDs.push(tour.ID);
this.favTours.push(tour);
this.storage.set('FavoritesIDs', this.favIDs);
}
remove(tour) {
let removeIndex:number = this.favIDs.indexOf(tour.ID);
if (removeIndex != -1) {
this.favIDs.splice(removeIndex, 1);
this.favTours.splice(removeIndex, 1);
this.storage.set('FavoritesIDs', this.favIDs);
}
}
reorder(ev) {
ev.detail.complete(this.favTours);
this.favIDs = this.favTours.map(tour => tour.ID);
this.storage.set('FavoritesIDs', this.favIDs);
}
We replace our previous forEach loop with a loop through all favIDs :
this.favIDs.forEach(favID => {
let tour = tours.filter(t => t.ID == favID)[0];
this.favTours.push(tour);
});
261
Ionic 5 • 6 UI Components
This ensures that the favorites are now displayed in the saved sequence.
Then we add a reorder function, where we get an event object (called ev here).
Where this event object comes from, we will see soon. Anyway - with
ev.detail.complete(this.favTours);
we can access the new order of tours and assign them to favTours .
With
this.storage.set('FavoritesIDs', this.favIDs);
<ion-content class="ion-padding">
<ion-list>
<ion-reorder-group
(ionItemReorder)="favService.reorder($event)"
disabled="false">
<ion-item *ngFor="let tour of favService.favTours"
[routerLink]="'/details/' + tour.ID"
routerDirection="forward">
<ion-reorder slot="start">
<ion-icon name="swap-vertical-outline"></ion-icon>
</ion-reorder>
<ion-label>{{ tour?.Title }}</ion-label>
</ion-item>
262
Ionic 5 • 6 UI Components
</ion-reorder-group>
<ion-item *ngIf="favService.favTours?.length==0">
You didn't choose any favorites yet!
</ion-item>
</ion-list>
</ion-content>
<ion-footer class=”ion-padding”
*ngIf="favService.favTours?.length>1">
<small>
You can reorder your favorites by drag-drop an item using
the <ion-icon name="swap-vertical-outline"></ion-icon>
icon!
</small>
</ion-footer>
The detail property of the ionItemReorder event includes all of the relevant in-
formation about the reorder operation, including the from and to indexes. In the
context of reordering, an item moves from an index to a new index. We use de-
tail's complete method to get the completely new order of favTours .
we add an ion-reorder tag at the beginning of each line and give it a swap icon.
Last but not least, our FavoritesPage gets a footer that only becomes visible if the
user has added more than one favorite:
<ion-footer ... *ngIf="favService.favTours?.length>1">
263
Ionic 5 • 6 UI Components
Inside the footer, we give a hint that the list entries can be reordered:
<small>
You can reorder your favorites by drag-drop an item using
the <ion-icon name="swap-vertical-outline"></ion-icon> icon!
</small>
https://ionicframework.com/docs/api/reorder
https://ionicframework.com/docs/api/reorder-group
264
Ionic 5 • 6 UI Components
6.23 Searchbar
A Searchbar is always a great help when it comes to browsing larger databases.
The Searchbar in Ionic offers us a finished component that should be used instead
of an input to search lists. A clear button is displayed upon entering input in the
search bar's text field. Clicking on the clear button will erase the text field and the
input will remain focused. A cancel button can be enabled which will clear the input
and lose the focus upon click.
<ion-content class="ion-padding">
<ion-searchbar (ionChange)="search($event)"
placeholder="Search"
clearIcon>
</ion-searchbar>
<ion-list>
<ion-item *ngFor="let tour of tours"
[routerLink]="'/details/' + tour.ID"
routerDirection="forward">
<ion-thumbnail slot="start">
<img src="http://ionic.andreas-dormann.de/img/small/
{{tour.Image}}">
</ion-thumbnail>
<ion-label>
<h2 text-wrap>{{tour.Title}}</h2>
<p>Duration: {{tour.Duration}} min</p>
265
Ionic 5 • 6 UI Components
</ion-label>
</ion-item>
</ion-list>
</ion-content>
As you can see, installing a Searchbar is a breeze. If the user inserts something in
the Searchbar, an ionChange event will be triggered. For this event, we assign the
search() function to be written immediately, giving it the $event object of ion-
Change :
(ionChange)="search($event)"
With
placeholder="Search"
we show a placeholder text as long as the user hasn't yet entered their own search
text.
clearIcon
sets the clear icon (defaults to "close-circle" for ios and "close" for md ).
@Component({
selector: 'app-list',
templateUrl: './list.page.html',
styleUrls: ['./list.page.scss'],
})
export class ListPage implements OnInit {
tours: any;
selection: any;
constructor(private btService:BobToursService,
private activatedRoute: ActivatedRoute) { }
266
Ionic 5 • 6 UI Components
ngOnInit() {
this.selection = this.activatedRoute.snapshot.params;
let category = this.selection.Category;
let criteria = this.selection.Criteria;
this.tours = _.filter(this.btService.tours,
[category, criteria]);
}
With
this.tours = _.filter(this.btService.tours,
[this.selection.Category,
this.selection.Criteria]);
we first filter for category and criteria and assign the result to this.tours (as we
already do in ngOnInit ).
267
Ionic 5 • 6 UI Components
we continue to filter this.tours and only search out the tours where the search
text appears in the title of a tour. Usually you compare the lowercase strings with
toLowerCase() . So it doesn't matter which upper or lower case the user uses.
https://ionicframework.com/docs/api/searchbar
268
Ionic 5 • 6 UI Components
6.24 Segment
Segments display a group of related buttons, sometimes known as segmented
controls, in a horizontal row. They can be displayed inside of a toolbar or the main
content.
Their functionality is similar to tabs, where selecting one will deselect all others. Seg-
ments are useful for toggling between different views inside of the content. Tabs
should be used instead of a Segment when clicking on a control should navigate
between pages.
269
Ionic 5 • 6 UI Components
redirectTo: 'favorites',
pathMatch: 'full'
},
{
path: 'favorites',
loadChildren:
() => import('./pages/favorites/favorites.module')
.then(m => m.FavoritesPageModule)
},
{
path: 'regions',
loadChildren:
() => import('./pages/regions/regions.module')
.then(m => m.RegionsPageModule)
},
{
path: 'tour-types',
loadChildren:
() => import('./pages/tour-types/tour-types.module')
.then(m => m.TourTypesPageModule)
},
{
path: 'list',
loadChildren:
() => import('./pages/list/list.module')
.then(m => m.ListPageModule)
},
{
path: 'details/:id',
loadChildren:
() => import('./pages/details/details.module')
.then(m => m.DetailsPageModule)
},
{
path: 'request',
loadChildren:
() => import('./pages/request/request.module')
.then(m => m.RequestPageModule)
},
270
Ionic 5 • 6 UI Components
{
path: 'map',
loadChildren:
() => import('./pages/map/map.module')
.then( m => m.MapPageModule)
}
];
@NgModule({
imports: [
RouterModule.forRoot(routes, { preloadingStrategy:
PreloadAllModules })
],
exports: [RouterModule]
})
export class AppRoutingModule { }
As we can see, there's a new entry for a 'map' path, automatically built by the gen-
erate (g) command. There's nothing more to do here for us.
@NgModule({
imports: [
271
Ionic 5 • 6 UI Components
CommonModule,
FormsModule,
IonicModule,
DetailsPageRoutingModule
],
declarations: [DetailsPage, RequestPage, MapPage],
entryComponents: [RequestPage, MapPage]
})
export class DetailsPageModule { }
@Component({
selector: 'app-details',
templateUrl: './details.page.html',
styleUrls: ['./details.page.scss'],
})
export class DetailsPage implements OnInit {
tour = null;
isFavorite: boolean;
region: string;
tourtype: string;
272
Ionic 5 • 6 UI Components
showSocial: boolean;
ngOnInit() {
...
}
// Action Sheet
async presentActionSheet() {
const actionSheet = await this.actionSheetCtrl.create({
header: 'Tour',
buttons: [
{
text: 'Request',
handler: () => {
this.presentModal();
}
},
{
text: 'Map/Route',
handler: () => {
this.presentMap();
}
},
{
text: (this.isFavorite) ? 'Remove from Favorites'
: 'Add to Favorites',
role: (this.isFavorite) ? 'destructive' : '',
handler: () => {
if (this.isFavorite) {
this.presentAlert();
} else {
this.favService.add(this.tour);
this.isFavorite = true;
}
}
273
Ionic 5 • 6 UI Components
},
{
text: 'Cancel',
role: 'cancel'
}
]
});
await actionSheet.present();
}
// Alert
async presentAlert() {
...
}
274
Ionic 5 • 6 UI Components
You see, we call the MapPage as modal (as we do with the RequestPage ) and pass
the current tour object via componentProps as parameter.
275
Ionic 5 • 6 UI Components
Let's test the new 'Map/Route' option. If we did everything right, then our app
should look like this:
276
Ionic 5 • 6 UI Components
<ion-content>
</ion-content>
<ion-footer class="ion-padding">
<ion-segment [(ngModel)]="currentView"
(ionChange)="currentViewChanged($event)">
<ion-segment-button value="map">
<ion-label>Map</ion-label>
</ion-segment-button>
<ion-segment-button value="route">
<ion-label>Route</ion-label>
</ion-segment-button>
<ion-segment-button value="description">
<ion-label>Description</ion-label>
</ion-segment-button>
</ion-segment>
</ion-footer>
binds a variable currentView (which we are about to define) to the code behind.
(ionChange)="currentViewChanged($event)"
277
Ionic 5 • 6 UI Components
<ion-segment-button value="route">
<ion-label>Route</ion-label>
</ion-segment-button>
<ion-segment-button value="description">
<ion-label>Description</ion-label>
</ion-segment-button>
This defines our three Segment buttons, labeled as Map , Route and Description.
The value attribute of each ion-segment-button is the value that is bound to ng-
Model (assigned to the variable currentView ).
@Component({
selector: 'app-map',
templateUrl: './map.page.html',
styleUrls: ['./map.page.scss'],
})
export class MapPage implements OnInit {
currentView = 'map';
constructor() { }
ngOnInit() {
}
The variable currentView holds the current selected Segment value. It's defaulted
with the value 'map' .
278
Ionic 5 • 6 UI Components
The event object named ev passed here has the property detail.value . That's
the value of an ion-segment-button (its value attribute). For now we simply out-
put ev.detail.value and this.currentView to the console.
https://ionicframework.com/docs/api/segment
https://ionicframework.com/docs/api/segment-button
279
Ionic 5 • 6 UI Components
6.25 Select
Selects are form controls to select an option, or options, from a set of options, simi -
lar to a native <select> element. When a user taps the Select, a dialog appears
with all of the options in a large, easy to select list.
Select a language
In our app we want to use a Select component that allows the user to select the
language in which to run a tour.
Let's start with some HTML / ion-tags in request.page.html :
<ion-header>
...
</ion-header>
<ion-content class="ion-padding">
<ion-list>
280
Ionic 5 • 6 UI Components
<ion-item>
<ion-label>The guide should speak</ion-label>
<ion-select [(ngModel)]="request.Language"
interface="popover">
<ion-select-option>english</ion-select-option>
<ion-select-option>spanish</ion-select-option>
<ion-select-option>chinese</ion-select-option>
<ion-select-option>german</ion-select-option>
<ion-select-option>french</ion-select-option>
<ion-select-option>italian</ion-select-option>
</ion-select>
</ion-item>
</ion-item-group>
</ion-list>
</ion-content>
<ion-footer class="ion-padding">
...
</ion-footer>
Below the “Schedule” group we create a new ion-item-group with the caption
“Language”:
<ion-item-divider>
<ion-label>Language</ion-label>
</ion-item-divider>
281
Ionic 5 • 6 UI Components
With
interface="popover"
we let the component show only a simple popover when the user taps the com-
ponent. Other possible values are action-sheet or alert , which would show
Cancel and OK buttons. But we can do without the buttons here.
@Component({
selector: 'app-request',
templateUrl: './request.page.html',
styleUrls: ['./request.page.scss'],
})
export class RequestPage implements OnInit {
constructor(navParams: NavParams) {
this.tour = navParams.data;
}
ngOnInit() {
282
Ionic 5 • 6 UI Components
...
}
...
}
https://ionicframework.com/docs/api/select
https://ionicframework.com/docs/api/select-option
283
Ionic 5 • 6 UI Components
6.26 Slides
Ionic provides a very powerful and fully loaded Slider which can be modified in
any form. The Slides component is a multi-section container. Each section can be
swiped or dragged between. It contains any number of Slide components.
Create a slideshow
We want to create a slideshow that gives the user a quick visual overview of all the
tours. Each Slide offers a button with which the user can mark a tour as a favorite.
That's it.
284
Ionic 5 • 6 UI Components
import { BobToursService }
from './services/bob-tours.service';
import { AboutComponent }
from './components/about/about.component';
import { Storage } from '@ionic/storage';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html'
})
export class AppComponent {
public appPages = [
{
title: 'Favorites',
url: '/favorites',
icon: 'star'
},
{
title: 'Regions',
url: '/regions',
icon: 'images'
},
{
title: 'Tour-Types',
url: '/tour-types',
icon: 'bus'
},
{
title: 'Slideshow',
url: '/slideshow',
icon: 'play'
}
];
constructor(
...
) {
this.initializeApp();
285
Ionic 5 • 6 UI Components
initializeApp() {
...
}
// Loading settings
loadSettings() {
...
}
This little code adds a “Slideshow” entry to our side menu and allows us to navi -
gate to the corresponding page - nothing fancy.
286
Ionic 5 • 6 UI Components
</ion-toolbar>
</ion-header>
<ion-content>
<ion-slides [options]="sliderConfig">
<ion-slide *ngFor="let tour of btService.all_tours">
<ion-card>
<ion-card-header>
<ion-card-title>
{{ tour.Title }}
</ion-card-title>
</ion-card-header>
<ion-card-content>
<img src="https://ionic.andreas-dormann.de/img/big/
{{tour.Image}}">
<hr />
<ion-button expand="full"
(click)="manageFavorites(tour)">
<ion-icon *ngIf="!tour.isFavorite"
name="star" slot="start"></ion-icon>
<ion-label *ngIf="!tour.isFavorite">
Add to Favorites
</ion-label>
<ion-icon *ngIf="tour.isFavorite"
name="trash" slot="start"></ion-icon>
<ion-label *ngIf="tour.isFavorite">
Remove from Favorites
</ion-label>
</ion-button>
</ion-card-content>
</ion-card>
</ion-slide>
</ion-slides>
</ion-content>
287
Ionic 5 • 6 UI Components
<ion-menu-button></ion-menu-button>
</ion-buttons>
We build our Slides with the help of an *ngFor loop through all tours:
<ion-slide *ngFor="let tour of btService.all_tours">
288
Ionic 5 • 6 UI Components
Depending on whether the tour is already a favorite or not, the button contains the
appropriate icon and label. We realize this conditional UI with a few *ngIf clauses.
@Component({
selector: 'app-slideshow',
templateUrl: './slideshow.page.html',
styleUrls: ['./slideshow.page.scss'],
})
export class SlideshowPage implements OnInit {
sliderConfig = {
centeredSlides: true,
autoplay: { delay: 2400 },
loop: true
};
constructor(public btService:BobToursService) { }
ngOnInit() {
}
manageFavorites(tour) {
if (!tour.isFavorite) {
this.btService.favService.add(tour);
} else {
this.btService.favService.remove(tour);
289
Ionic 5 • 6 UI Components
}
tour.isFavorite = !tour.isFavorite;
}
}
290
Ionic 5 • 6 UI Components
@Injectable({
providedIn: 'root'
})
export class FavoritesService {
initialize(tours) {
this.favTours = [];
this.storage.ready().then(() => {
this.storage.get('FavoritesIDs').then(ids => {
this.favIDs = ids;
if (this.favIDs == null) {
this.favIDs = [];
} else {
this.favIDs.forEach(favID => {
let tour = tours.filter(t => t.ID == favID)[0];
tour.isFavorite = true;
this.favTours.push(tour);
});
}
});
});
}
add(tour) {
...
}
remove(tour) {
291
Ionic 5 • 6 UI Components
...
}
reorder(ev) {
...
to ensure that each tour coming from the storage is signed as isFavorite . That
corresponds to the previously defined manageFavorites method in slideshow.-
page.ts .
@Injectable({
providedIn: 'root'
})
export class BobToursService {
baseUrl = 'https://bob-tours-app.firebaseio.com/';
292
Ionic 5 • 6 UI Components
async initialize() {
...
}
...
That's it.
Sit back now and enjoy the new slideshow in our app:
https://ionicframework.com/docs/api/slides
http://idangero.us/swiper/
293
Ionic 5 • 6 UI Components
6.27 Toast
Toasts are subtle hints that appear over the content of an app. They can be used
to provide feedback about an operation or to display a system message. Toasts
are usually hidden automatically after a short display time.
@Component({
selector: 'app-request',
templateUrl: './request.page.html',
styleUrls: ['./request.page.scss'],
})
export class RequestPage implements OnInit {
constructor(
private modalCtrl: ModalController,
private navParams: NavParams,
private toastCtrl: ToastController
) {
this.tour = navParams.data;
}
ngOnInit() {
...
}
294
Ionic 5 • 6 UI Components
send() {
this.confirm();
...
}
With
this.confirm();
295
Ionic 5 • 6 UI Components
https://ionicframework.com/docs/api/toast
https://ionicframework.com/docs/api/toast-controller
296
Ionic 5 • 6 UI Components
6.28 Toggle
A Toggle is an input component that holds a boolean value. Like a checkbox, tog -
gles are often used to Toggle an app setting on or off.
Attributes like value , disabled , and checked can be added to the Toggle to con-
trol its behavior.
<ion-content class="ion-padding">
<ion-list>
297
Ionic 5 • 6 UI Components
<ion-item>
<ion-label>We need a bus.</ion-label>
<ion-toggle [(ngModel)]="request.Bus"></ion-toggle>
</ion-item>
</ion-item-group>
</ion-list>
</ion-content>
<ion-footer class="ion-padding">
...
</ion-footer>
Below the language selection group we place a new ion-item-group that will only
be shown if the selected tour is a bus trip (we'll check that soon):
<ion-item-group *ngIf="isBusTrip">
The latter we two-way-bind via [(ngModel)] to our existing request object, there
to a (new) property Bus .
298
Ionic 5 • 6 UI Components
@Component({
selector: 'app-request',
templateUrl: './request.page.html',
styleUrls: ['./request.page.scss'],
})
export class RequestPage implements OnInit {
constructor(
private modalCtrl: ModalController,
private navParams: NavParams,
private toastCtrl: ToastController
) {
this.tour = navParams.data;
}
ngOnInit() {
299
Ionic 5 • 6 UI Components
send() {
...
}
and within ngOnInit we check whether the selected tour is a bus tour or not:
this.isBusTrip = this.tour.Tourtype == 'BU';
300
Ionic 5 • 6 UI Components
https://ionicframework.com/docs/api/toggle
301
Ionic 5 • 6 UI Components
6.29 Toolbar
Toolbars are positioned above or below content. When a Toolbar is placed in an
<ion-header> it will appear fixed at the top of the content, and when it is in an
<ion-footer> it will appear fixed at the bottom. Fullscreen content will scroll be-
hind a toolbar in a header or footer. When placed within an <ion-content> ,
Toolbars will scroll with the content.
<ion-content>
</ion-content>
<ion-footer class="ion-padding">
...
</ion-footer>
The close button's click event is assigned to a close() function which we will
write now.
302
Ionic 5 • 6 UI Components
@Component({
selector: 'app-map',
templateUrl: './map.page.html',
styleUrls: ['./map.page.scss'],
})
export class MapPage implements OnInit {
currentView = 'map';
constructor(
private modalCtrl: ModalController
) { }
ngOnInit() {
}
currentViewChanged(ev) {
console.log(ev.detail.value);
console.log(this.currentView);
}
close() {
this.modalCtrl.dismiss();
}
303
Ionic 5 • 6 UI Components
More informations about Toolbar and its related topics you can find here:
https://ionicframework.com/docs/api/toolbar
https://ionicframework.com/docs/api/header
https://ionicframework.com/docs/api/footer
https://ionicframework.com/docs/api/title
https://ionicframework.com/docs/api/buttons
https://ionicframework.com/docs/api/back-button
304
Ionic 5 • 6 UI Components
Summary
In this chapter you got to know all the high-level building blocks of the Ionic frame-
work called components.
And did you notice? In this chapter, you'll find all the components in alphabetical
order. So it's a good place to look up here if you need it.
Now you know how to use the typical controllers like ActionSheetController, Alert-
Controller, ModalController and PopoverController. You can visualize the progress
of processes via Progress Indicators. You can design attractive lists with headers, di-
viders and thumbnail images, layout with the Grid component, build slideshows
and create awesome app pages with Badges, Cards, Iamges and much, much
more.
305
Ionic 5 • 6 UI Components
306
Ionic 5 • 7 Form validation
7 Form validation
7.1 Introduction
Developing good Forms requires design and user experience (UX) skills, as well as
a framework with support for two-way data binding, change tracking, validation,
and error handling such as Ionic/Angular. But I think, you already know this, other-
wise you wouldn't have bought this book ;-)
We'll use the latter, because it's the more advanced way of validation and indis-
pensable if your forms get really nice and complex.
Don't worry – it's not rocket science.
At this point I want to thank Josh Morony, whose excellent blog articles and tutori-
als have accompanied me for years. His very good Ionic tutorial “Advanced Forms
& Validation in Ionic & Angular” inspired me to write this chapter.
Ok, let's plunge into the exciting adventure of form validation!
307
Ionic 5 • 7 Form validation
1. Desired Date
• Earliest possible booking: the day after tomorrow
• Latest possible booking: in two years
• Required
2. Desired Time
• Earliest possible time for a tour: 9:00 AM
• Latest possible time for a tour: 5:00 PM (17:00)
• Required
3. Language
• Valid language from a dropdown
• Required
4. Need bus
• Default is false
5. First name
• Must be shorter than 30 characters
• Contains only letters and spaces
• Required
6. Last name
• Must be shorter than 30 characters
• Contains only letters and spaces
• Required
7. Email
• Valid email
• Required
308
Ionic 5 • 7 Form validation
309
Ionic 5 • 7 Form validation
7.3 Implementation
We implement our form validation in the following steps:
import { AboutComponent }
from './components/about/about.component';
310
Ionic 5 • 7 Form validation
@NgModule({
declarations: [AppComponent, AboutComponent],
entryComponents: [AboutComponent],
imports: [
BrowserModule,
IonicModule.forRoot(),
AppRoutingModule,
HttpClientModule,
IonicStorageModule.forRoot(),
FormsModule,
ReactiveFormsModule
],
providers: [
StatusBar,
SplashScreen,
{ provide: RouteReuseStrategy,
useClass: IonicRouteStrategy }
],
bootstrap: [AppComponent]
})
export class AppModule {}
import { RequestPageRoutingModule }
from './request-routing.module';
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
311
Ionic 5 • 7 Form validation
RequestPageRoutingModule,
ReactiveFormsModule
],
declarations: [RequestPage]
})
export class RequestPageModule { }
Of course, we want to avoid this error and therefore add the absolutely necessary
import of ReactiveFormsModule inside details.module.ts, too:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule }
from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { DetailsPageRoutingModule }
from './details-routing.module';
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
DetailsPageRoutingModule,
ReactiveFormsModule
],
declarations: [DetailsPage, RequestPage, MapPage],
312
Ionic 5 • 7 Form validation
@Component({
selector: 'app-request',
templateUrl: './request.page.html',
styleUrls: ['./request.page.scss'],
})
export class RequestPage implements OnInit {
isBusTrip: boolean;
validationForm: FormGroup;
validationMessages: any;
constructor(
private modalCtrl: ModalController,
private navParams: NavParams,
private toastCtrl: ToastController,
public formBuilder: FormBuilder
) {
this.tour = navParams.data;
}
313
Ionic 5 • 7 Form validation
ngOnInit() {
// Prepare form validation
this.prepareFormValidation();
...
}
this.validationForm = this.formBuilder.group({
DesiredDate: ['', Validators.required],
DesiredTime: new FormControl('', Validators.required),
Language: new FormControl('english'),
NeedBus: new FormControl(false),
FirstName: new FormControl('', Validators.compose([
Validators.minLength(2),
Validators.maxLength(30),
Validators.pattern('[a-zA-Z ]*'),
Validators.required])),
LastName: new FormControl('', Validators.compose([
Validators.minLength(2),
Validators.maxLength(30),
Validators.pattern('[a-zA-Z ]*'),
Validators.required])),
Email: new FormControl('', Validators.compose([
//Validators.email,
Validators.pattern(/^(([^<>()\[\]\\.,;:\s@"]+(\.
[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.
[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-
9]+\.)+[a-zA-Z]{2,}))$/),
Validators.required]))
});
this.validationMessages = {
'DesiredDate': [
{ type: 'required',
message: 'Date is required.'}
],
'DesiredTime': [
{ type: 'required',
message: 'Time is required.'}
],
314
Ionic 5 • 7 Form validation
'FirstName': [
{ type: 'required',
message: 'First name is required.'},
{ type: 'minlength',
message: 'First name must be at least 2 chars long.'},
{ type: 'maxlength',
message: 'First name cannot be more than 30 chars long.'},
],
'LastName': [
{ type: 'required',
message: 'Last name is required.'},
{ type: 'minlength',
message: 'Last name must be at least 2 chars long.'},
{ type: 'maxlength',
message: 'Last name cannot be more than 30 chars long.'},
],
'Email': [
{ type: 'required',
message: 'Email is required.'},
{ type: 'pattern',
message: 'Must be a valid email address.'},
],
}
315
Ionic 5 • 7 Form validation
In one sentence:
In detail:
In order to use the imported FormBuilder we inject it as variable formBuilder
into the constructor:
constructor(... public formBuilder: FormBuilder) {
...
}
The group method expects an object and within this object a collection of child
controls.
Our first child control gets the name DesiredDate. Then, in square brackets, two
values follow.
316
Ionic 5 • 7 Form validation
The first value is required (although it can just be an empty string) and is the de-
fault value of the control. The second value is optional, and is a validation function
that is used to check the value of the control. A third value (we didn't use it) is also
optional, and is basically the same as the second except that it is for asynchronous
validation. This means if you need to perform a check that isn't instant (like check-
ing if a username already exists on a server) then you can use an asynchronous
validation function.
But what does the second value Validators.required mean? This is one of sev-
eral built-in validators that can be used by form controls. This one makes the input
to this control mandatory. What happens if the user violates this validation rule,
we'll see later.
Remember our requirements for user input to Desired Date:
Our second child control gets the name DesiredTime . But instead of using the
shorthand square bracket syntax we now use the long-making new
FormControl(...) . The expected values are exactly the same: first the default val-
ue of the control (again an empty string), the second value a validation function
(again the built-in required validator) and an optional third value for asynchronous
validation (again we didn't use it).
Both formulations of the controls are equivalent. Why the supposedly shorter isn't
always enough, we will see soon.
317
Ionic 5 • 7 Form validation
Our third and fourth child controls simply define default values. They don't need
further validation, because of their default values they're always valid.
FirstName: new FormControl('', Validators.compose([
Validators.minLength(2),
Validators.maxLength(30),
Validators.pattern('[a-zA-Z ]*'),
Validators.required]))
Now it get's interesting. Our fifth child control named FirstName receives several
validators, which specify that the first name must be at least 2 and a maximum of
30 characters long, contain only letters and is again mandatory.
We couldn't have coded this in shorthand. This is only possible with the Valida-
tors.compose method.
Very flexible is the pattern validator, which allows us to work with regular expres-
sions (RegEx patterns). This gives us a very powerful validation tool. You should
therefore get a little closer to RegEx patterns. Here are some good links:
https://www.w3schools.com/js/js_regexp.asp
https://www.w3schools.com/jsref/jsref_obj_regexp.asp
But now let's continue with our controls:
LastName: new FormControl('', Validators.compose([
Validators.minLength(2),
Validators.maxLength(30),
Validators.pattern('[a-zA-Z ]*'),
Validators.required]))
For our sixth child control named LastName , the composition of built-in validators
is the same as FirstName.
Email: new FormControl('', Validators.compose([
//Validators.email,
Validators.pattern(/^[a-zA-Z]{1,}[0-9]?([\.\_-]?
[a-zA-Z0-9]+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/),
Validators.required]))
318
Ionic 5 • 7 Form validation
Our seventh and last child component named Email owns a pattern and a re-
quired validator. We could also have used the built-in email validator (see
commented line), but it's definitively useless. That's why I built my own here. My
RegEx pattern seems pretty complicated, but believe me: It works very well!
So much for the preparation of the validation. Let's get to the preparation of the
messages.
We assign an object to this.validationMessages. This object contains a series of
message entries. Each message entry owns an array of objects. Each object owns a
type and a message property.
The name of the entry 'DesiredDate' must match a child control that we have
previously defined (here: DesiredDate ). Within the array we have one single object
that indicates via its type property which validator it wants to respond to (here:
required ). In other words: If the user violates the required rule of the Desired-
Date control, the corresponding message 'Date is required.' will be returned.
When we set up the UI, we'll see how everything fits together.
Because our child components Language and NeedBus will always be defaulted
with 'english' and false , there will never be a validation violation. That's why we
don't need any messages for this.
'FirstName': [
{ type: 'required',
message: 'First name is required.'},
{ type: 'minlength',
message: 'First name must be at least 2 chars long.'},
319
Ionic 5 • 7 Form validation
{ type: 'maxlength',
message: 'First name cannot be more than 30 chars long.'},
]
For our control FirstName we had composed a few validators. That's why we also
need a separate message for each of these validators. I think they are self-explana-
tory.
'LastName': [
{ type: 'required',
message: 'Last name is required.'},
{ type: 'minlength',
message: 'Last name must be at least 2 chars long.'},
{ type: 'maxlength',
message: 'Last name cannot be more than 30 chars long.'},
]
Nothing special here. If the user violates the rules formulated in the RegEx pattern,
he (later) gets the hint 'Must be a valid email address.' . I think, my own
RegEx pattern is better than the built-in Validators.email validator.
320
Ionic 5 • 7 Form validation
3. Setting up the UI
And now we design request.page.html :
<ion-header>
<ion-toolbar>
<ion-title>Request</ion-title>
<ion-buttons slot="end">
<ion-button (click)="cancel()">Cancel</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<form [formGroup]="validationForm">
321
Ionic 5 • 7 Form validation
<ion-item>
<ion-label position="floating">Date</ion-label>
<ion-datetime formControlName="DesiredDate"
min="{{day_after_tomorrow}}"
max="{{two_years_later}}"
display-format="DDD, MMMM DD YYYY"
picker-format="MMMM DD YYYY"
placeholder="Choose desired date!">
</ion-datetime>
</ion-item>
<div class="validation-errors">
<ng-container *ngFor="let validation
of validationMessages.DesiredDate">
<div class="error-message"
*ngIf="validationForm.get('DesiredDate')
.hasError(validation.type)
&& (validationForm.get('DesiredDate').dirty
||
validationForm.get('DesiredDate').touched)">
<ion-icon name="flash"></ion-icon>
{{ validation.message }}
</div>
</ng-container>
</div>
322
Ionic 5 • 7 Form validation
.hasError(validation.type)
&& (validationForm.get('DesiredTime').dirty
|| validationForm.get('DesiredTime').touched)">
<ion-icon name="flash"></ion-icon>
{{ validation.message }}
</div>
</ng-container>
</div>
</ion-item-group>
323
Ionic 5 • 7 Form validation
<ion-item-group>
<ion-item-divider>
<ion-label>Your contact information</ion-label>
</ion-item-divider>
324
Ionic 5 • 7 Form validation
{{ validation.message }}
</div>
</ng-container>
</div>
</ion-item-group>
</form>
</ion-content>
<ion-footer class="ion-padding">
<ion-button expand="block"
type="submit"
[disabled]="!validationForm.valid"
(click)="send(validationForm.value)">
Send request
</ion-button>
</ion-footer>
325
Ionic 5 • 7 Form validation
And to build the connection between each validation control and its corresponding
UI control we add the formControlName attribute with the respective name to
each UI control, e.g. formControlName=”DesiredDate” to the corresponding date-
time control, formControlName=”FirstName” to the corresponding input control.
The integration of the messages takes place via a div tag immediately after each
UI control.
Let's take a look at the example of DesiredDate:
<div class="validation-errors">
<ng-container *ngFor="let validation
of validationMessages.DesiredDate">
<div class="error-message"
*ngIf="validationForm.get('DesiredDate')
.hasError(validation.type)
&& (validationForm.get('DesiredDate').dirty
||
validationForm.get('DesiredDate').touched)">
<ion-icon name="flash"></ion-icon>
{{ validation.message }}
</div>
</ng-container>
</div>
Within the div tag we define an ng-container tag. This container is rendered in a
*ngFor loop as many times as it receives messages from validationMessages to
DesiredDate .
The container owns an inner div tag. We give the inner div tag the class name
“error-message” , so we can style it later via CSS. The inner div tag and its con-
tent will only be displayed, if some conditions are met. This is ensured by the
*ngIf directive.
326
Ionic 5 • 7 Form validation
is true if the user focused on the control and then focused on something else. For
example by clicking into the control and then pressing tab or clicking on another
control in the form.
Ok, this is the standard code construction for displaying our validation messages.
We use it with every control in the same way.
One last important thing we should look at is the “Send request” button in the
footer area:
<ion-button expand="block"
type="submit"
[disabled]="!validationForm.valid"
(click)="send(validationForm.value)">
Send request
</ion-button>
327
Ionic 5 • 7 Form validation
As you can see, a user no longer has a chance to send invalid data.
More informations about Reactive Form validations you can find here:
https://angular.io/guide/reactive-forms
328
Ionic 5 • 7 Form validation
Summary
In this chapter, you learned about form validation with Angular Reactive Forms.
In doing so, you have used the ReactiveFormsModule, whose FormBuilder allows
you to create a logical form structure and, with the help of validators, check their
contents for valid entries. A bunch of built-in validators are available out of the box
to support you.
RegEx can also be used to formulate innumerable own validation rules.
You also learned how to prepare validation messages and link all this to the UI.
Finally, you will be able to create well designed forms, giving the user a good UX
and your server always valid data.
329
Ionic 5 • 7 Form validation
330
Ionic 5 • 8 Theming, Styling, Customizing
8 Theming, Styling,
Customizing
8.1 Introduction
Ionic has been designed to make it easy to customize an app's design to its own
branding while always adhering to the standards of each platform.
Since Ionic 4, theming Ionic apps is now easier than ever. Because the framework
is built with CSS, it comes with pre-baked default styles which are extremely easy
to change and modify.
Ionic has nine default colors that can be used to change the color of many compo-
nents. Each color is actually a collection of multiple properties, including a shade
and tint , used throughout Ionic. For color management Ionic provides a very use-
ful Color Generator that we will get to know.
In this chapter we will cover some very practical examples that will give you a ba-
sic understanding of design customization in apps with Ionic. On this basis, you will
succeed effortlessly in developing and designing your own awesome app designs.
331
Ionic 5 • 8 Theming, Styling, Customizing
332
Ionic 5 • 8 Theming, Styling, Customizing
--ion-color-warning-contrast-rgb: 0, 0, 0;
--ion-color-warning-shade: #e0ac08;
--ion-color-warning-tint: #ffca22;
body {
--ion-color-primary: #428cff;
--ion-color-primary-rgb: 66, 140, 255;
--ion-color-primary-contrast: #ffffff;
--ion-color-primary-contrast-rgb: 255, 255, 255;
--ion-color-primary-shade: #3a7be0;
--ion-color-primary-tint: #5598ff;
--ion-color-secondary: #50c8ff;
--ion-color-secondary-rgb: 80, 200, 255;
--ion-color-secondary-contrast: #ffffff;
333
Ionic 5 • 8 Theming, Styling, Customizing
--ion-color-tertiary: #6a64ff;
--ion-color-tertiary-rgb: 106, 100, 255;
--ion-color-tertiary-contrast: #ffffff;
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
--ion-color-tertiary-shade: #5d58e0;
--ion-color-tertiary-tint: #7974ff;
--ion-color-success: #2fdf75;
--ion-color-success-rgb: 47, 223, 117;
--ion-color-success-contrast: #000000;
--ion-color-success-contrast-rgb: 0, 0, 0;
--ion-color-success-shade: #29c467;
--ion-color-success-tint: #44e283;
--ion-color-warning: #ffd534;
--ion-color-warning-rgb: 255, 213, 52;
--ion-color-warning-contrast: #000000;
--ion-color-warning-contrast-rgb: 0, 0, 0;
--ion-color-warning-shade: #e0bb2e;
--ion-color-warning-tint: #ffd948;
--ion-color-danger: #ff4961;
--ion-color-danger-rgb: 255, 73, 97;
--ion-color-danger-contrast: #ffffff;
--ion-color-danger-contrast-rgb: 255, 255, 255;
--ion-color-danger-shade: #e04055;
--ion-color-danger-tint: #ff5b71;
--ion-color-dark: #f4f5f8;
--ion-color-dark-rgb: 244, 245, 248;
--ion-color-dark-contrast: #000000;
--ion-color-dark-contrast-rgb: 0, 0, 0;
--ion-color-dark-shade: #d7d8da;
--ion-color-dark-tint: #f5f6f9;
--ion-color-medium: #989aa2;
--ion-color-medium-rgb: 152, 154, 162;
--ion-color-medium-contrast: #000000;
--ion-color-medium-contrast-rgb: 0, 0, 0;
--ion-color-medium-shade: #86888f;
--ion-color-medium-tint: #a2a4ab;
--ion-color-light: #222428;
--ion-color-light-rgb: 34, 36, 40;
--ion-color-light-contrast: #ffffff;
--ion-color-light-contrast-rgb: 255, 255, 255;
--ion-color-light-shade: #1e2023;
--ion-color-light-tint: #383a3e;
}
334
Ionic 5 • 8 Theming, Styling, Customizing
/*
* iOS Dark Theme
* -------------------------------------------
*/
.ios body {
--ion-background-color: #000000;
--ion-background-color-rgb: 0, 0, 0;
--ion-text-color: #ffffff;
--ion-text-color-rgb: 255, 255, 255;
--ion-color-step-50: #0d0d0d;
--ion-color-step-100: #1a1a1a;
--ion-color-step-150: #262626;
--ion-color-step-200: #333333;
--ion-color-step-250: #404040;
--ion-color-step-300: #4d4d4d;
--ion-color-step-350: #595959;
--ion-color-step-400: #666666;
--ion-color-step-450: #737373;
--ion-color-step-500: #808080;
--ion-color-step-550: #8c8c8c;
--ion-color-step-600: #999999;
--ion-color-step-650: #a6a6a6;
--ion-color-step-700: #b3b3b3;
--ion-color-step-750: #bfbfbf;
--ion-color-step-800: #cccccc;
--ion-color-step-850: #d9d9d9;
--ion-color-step-900: #e6e6e6;
--ion-color-step-950: #f2f2f2;
--ion-toolbar-background: #0d0d0d;
--ion-item-background: #1c1c1c;
--ion-item-background-activated: #313131;
}
/*
* Material Design Dark Theme
* -------------------------------------------
*/
.md body {
--ion-background-color: #121212;
--ion-background-color-rgb: 18, 18, 18;
--ion-text-color: #ffffff;
--ion-text-color-rgb: 255, 255, 255;
--ion-border-color: #222222;
--ion-color-step-50: #1e1e1e;
--ion-color-step-100: #2a2a2a;
335
Ionic 5 • 8 Theming, Styling, Customizing
--ion-color-step-150: #363636;
--ion-color-step-200: #414141;
--ion-color-step-250: #4d4d4d;
--ion-color-step-300: #595959;
--ion-color-step-350: #656565;
--ion-color-step-400: #717171;
--ion-color-step-450: #7d7d7d;
--ion-color-step-500: #898989;
--ion-color-step-550: #949494;
--ion-color-step-600: #a0a0a0;
--ion-color-step-650: #acacac;
--ion-color-step-700: #b8b8b8;
--ion-color-step-750: #c4c4c4;
--ion-color-step-800: #d0d0d0;
--ion-color-step-850: #dbdbdb;
--ion-color-step-900: #e7e7e7;
--ion-color-step-950: #f3f3f3;
--ion-item-background: #1a1b1e;
}
ion-title.title-large {
--color: white;
}
}
The simplest way to do a global theming is to change that particular primary color
variables. Let's say, the corporate identity color of our imaginary company “BoB
Tours” is #ffae00.
Let's try it now and set the following new colors value for primary :
/** Ionic CSS Variables **/
:root {
/** primary **/
--ion-color-primary: #ffae00;
--ion-color-primary-rgb: 255,174,0;
--ion-color-primary-contrast: #ffffff;
--ion-color-primary-contrast-rgb: 255,255,255;
--ion-color-primary-shade: #e09900;
--ion-color-primary-tint: #ffb61a;
336
Ionic 5 • 8 Theming, Styling, Customizing
You see, we defined --ion-color-primary with the color value #ffae00 , the
“BoB Tours” CI color. The color represents a warm yellow-orange.
Color Generator
But we also provided --ion-color-primary-shade and -ion-color-prima-
ry-tint with alternative values. Did I just think it up? No. In order to find suitable
shade and tint colors for a basic color I used Ionic's so-called Color Generator. You
can find it here:
https://ionicframework.com/docs/theming/color-generator
For a basic color, the Color Generator determines appropriate shade and tint
colors. The whole thing is then conveniently issued as CSS code. You only need to
copy it and paste/replace it into your variables.scss . That's how I did it.
337
Ionic 5 • 8 Theming, Styling, Customizing
These are the color values for white; the Color Manager had suggested black
(#000000 and 0, 0, 0 ) here. I didn't like that.
Using colors
What's up with the other colors of the $colors map like secondary , tertiary ,
danger etc.? How can we use this? Let's take a look at the example of our De-
tailsPage . Here, in the footer of details.page.html, we add the following:
338
Ionic 5 • 8 Theming, Styling, Customizing
<ion-header>
...
</ion-header>
<ion-content class="ion-padding">
...
</ion-content>
To use one of the colors of the $colors map from variables.scss we just as-
sign one of the variables to the attribute color.
339
Ionic 5 • 8 Theming, Styling, Customizing
https://ionicframework.com/docs/theming/basics
340
Ionic 5 • 8 Theming, Styling, Customizing
Global stylesheets
While Ionic Framework component styles are self-contained, there are several
global stylesheets that should be included in order to use all of Ionic's features.
Some of the stylesheets are required in order for an Ionic Framework app to look
and behave properly, and others include optional utilities to quickly style your app.
The following CSS file must be included in order for Ionic Framework to work
properly:
core.css (required)
This file is the only stylesheet that is required in order for Ionic components to work
properly. It includes app specific styles, and allows the color property to work
across components. If this file isn't included the colors won't show up and some el-
ements may not appear properly.
structure.css (recommended)
Applies styles to <html> and defaults box-sizing to border-box. It ensures
scrolling behaves like native in mobile devices.
typography.css (recommended)
Typography changes the font-family of the entire document and modifies the font
styles for heading elements. It also applies positioning styles to some native text el-
ements.
normalize.css (recommended)
Makes browsers render all elements more consistently and in line with modern
standards. It is based on Normalize.css.
display.css (recommended)
Adds utility classes to hide any element based on the breakpoint, see “8.4 CSS
Utilities”, starting on page 350, for usage information.
341
Ionic 5 • 8 Theming, Styling, Customizing
The following set of CSS files are optional and can safely be commented out or re-
moved if the application isn't using any of the features:
padding.css (optional)
Adds utility classes to modify the padding or margin on any element, see “8.4 CSS
Utilities”, starting on page 350, for usage information.
float-elements.css (optional)
Adds utility classes to float an element based on the breakpoint and side, see “8.4
CSS Utilities”, starting on page 350, for usage information.
text-alignment.css (optional)
Adds utility classes to align the text of an element or adjust the white space based
on the breakpoint, see “8.4 CSS Utilities”, starting on page 350, for usage informa-
tion.
text-transformation.css (optional)
Adds utility classes to transform the text of an element to uppercase, lowercase or
capitalize based on the breakpoint, see “8.4 CSS Utilities”, starting on page 350, for
usage information.
flex-utils.css (optional)
Adds utility classes to align flex containers and items, see “8.4 CSS Utilities”, starting
on page 350, for usage information.
342
Ionic 5 • 8 Theming, Styling, Customizing
<ion-content class="ion-padding">
...
</ion-content>
Somewhat more advanced and flexible would be to add a style class to favo-
rites.page.html :
<ion-header>
<ion-toolbar class="myToolbarStyle">
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>Favorites</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
...
</ion-content>
You must then write this style class with a style statement in the file favorites.-
page.scss :
.myToolbarStyle {
--background: var(--ion-color-primary);
}
Well, Ionic uses so-called CSS Custom Properties (CSS Variables) and they are writ-
ten by preceded -- .
343
Ionic 5 • 8 Theming, Styling, Customizing
And where do you find these variables? Of course in the excellent documentation
of Ionic:
https://ionicframework.com/docs/theming/css-variables
The Ionic documentation itself links to one more resource, where the people of
mozilla.org explain the basics to CSS Variables:
https://developer.mozilla.org/en-US/docs/Web/CSS/
Using_CSS_custom_properties
If you've read in there a little, you'll find your way quickly. Besides, we'll of course
do some more theming and styling in this book.
Now let's see what our newly styled toolbar looks like:
344
Ionic 5 • 8 Theming, Styling, Customizing
.myToolbarStyle {
--background: var(--ion-color-primary);
}
We would then have to complete this class in all page.html files of our app, as we
did in favorites.page.html earlier. But that has to be easier, right?
And yes, that is easier!
I n variables.scss we place the following line after the open curly bracket of
:root :
...
}
345
Ionic 5 • 8 Theming, Styling, Customizing
This directive changes the background of the toolbars on all pages of our app:
346
Ionic 5 • 8 Theming, Styling, Customizing
However, we have produced a small flaw: At first glance the color of the side
menu button, the font colors of the back buttons and the cancel button on the re -
quest and map page look like they have disappeared. In fact their color is now
identical to the background color of the headers.
We correct this in variables.scss with a further style statements:
/** Ionic CSS Variables **/
:root {
.button {
color: var(--ion-color-primary-contrast);
}
...
}
This sets the font color of all buttons, including the "disappeared" ones, to --ion-
color-primary-contrast . We remember: We manually set this variable to
#ffffff (white) (see “8. 2 Simple Theming”, section “Color Generator” on page
337). We can use .button as a class identifier, because Ionic automatically pro-
vides all the above components with the style class ”button” .
.button {
color: var(--ion-color-primary-contrast);
}
}
347
Ionic 5 • 8 Theming, Styling, Customizing
Here are the toolbars of our app with visible menu, back and cancel buttons.
348
Ionic 5 • 8 Theming, Styling, Customizing
You can see how we can give a custom look to our app with just a few changes.
That's it for the moment. I hope that you have now got a first feel for theming in
Ionic.
More informations about using CSS Variables you can find here:
https://ionicframework.com/docs/theming/css-variables
https://angularfirebase.com/lessons/css-variables-in-ionic-4/
349
Ionic 5 • 8 Theming, Styling, Customizing
Text modification
In favorites.page.html let's optimize the display of the reorder hint in the foot-
er:
<ion-header>
...
</ion-header>
<ion-content class="ion-padding">
...
</ion-content>
ion-text-center is an example for a text alignment utility class. The inline con-
tents are centered. Other text alignment classes are for example ion-text-start
or ion-text-end .
ion-text-uppercase is an example for a text transformation. It forces all charac-
ters to be converted to uppercase. Other text transformation classes are ion-
text-lowercase and ion-text-capitalize .
With a style attribute, any CSS statement (and variable) can be integrated into an
HTML element. So here we color the text in the primary color.
350
Ionic 5 • 8 Theming, Styling, Customizing
This is the hint modified by CSS utility classes and embedded styling:
<ion-content class="ion-padding">
...
<ion-list>
<ion-item *ngFor="let tour of tours"
[routerLink]="'/details/' + tour.ID"
routerDirection="forward">
...
<ion-label>
<h2 class="ion-text-wrap">{{tour.Title}}</h2>
<p>Duration: {{tour.Duration}} min</p>
</ion-label>
</ion-item>
</ion-list>
</ion-content>
351
Ionic 5 • 8 Theming, Styling, Customizing
Element Placement
The placement of elements can be done in several ways, namely with:
• Float Elements, e.g. ion-float-left , ion-float-right
• Responsive Float Classes, e.g. ion-float-{left} , ion-float-{right}
Element Display
The display CSS property ion-hide determines if an element should be visible or
not. The element will still be in the DOM, but not rendered, if it is hidden. There
are also additional classes to modify the visibility based on the screen size (Respon-
sive Display Attributes). Instead of just ion-hide for all screen sizes, use ion-hide-
{breakpoint}-{dir} to only use the class on specific screen sizes, where {break-
point} is one of the breakpoint names listed in Ionic Breakpoints (see below),
and {dir} is whether the element should be hidden on all screen sizes above ( up )
or below (down ) the specified breakpoint.
Content Space
The space between elements can be controlled by different parameters:
• Padding (default amount is 16px), e.g. ion-padding or ion-padding-top
• Margin (default is 16px), e.g. ion-margin or ion-margin-top
The padding area is the space between the content of the element and its border.
The margin area extends the border area with an empty area used to separate the
element from its neighbors.
Flex Properties
In Flex Properties we distinguish between:
• Flex Container properties,
e.g. ion-justify-content-center, ion-align-items-end
• Flex Item Properties, e.g. ion-align-self-center
Ionic Breakpoints
Ionic uses breakpoints in media queries in order to style an application differently
based on the screen size. The following breakpoint names are used in the utility
classes listed above, where the class will apply when the width is met.
352
Ionic 5 • 8 Theming, Styling, Customizing
We've already worked with breakpoints related to the Grid component (see “6.11
Grid”, starting on page 196).
More informations about CSS utilities and related topics you can find here:
https://ionicframework.com/docs/layout/css-utilities
https://ionicframework.com/docs/layout/css-utilities#ionic-breakpoints
353
Ionic 5 • 8 Theming, Styling, Customizing
Colors
In the section "8.2 Simple Theming" (starting on page 332) we have already talked
a bit about colors. Therefore I limit myself here to some additions.
You can define your own color by creating a class like this:
.ion-color-favorite {
--ion-color-base: #69bb7b;
--ion-color-base-rgb: 105,187,123;
--ion-color-contrast: #ffffff;
--ion-color-contrast-rgb: 255,255,255;
--ion-color-shade: #5ca56c;
--ion-color-tint: #78c288;
}
It's important to note that adding the class above doesn't automatically create the
Ionic CSS variables for use in an application's stylesheets. This means that the varia-
tions beginning with --ion-color-favorite don't exist by adding the .ion-
color-favorite class. These should be declared separately for use in an applica-
tion, preferably in the :root section of variables.scss :
:root {
--ion-color-favorite: #69bb7b;
--ion-color-favorite-rgb: 105,187,123;
--ion-color-favorite-contrast: #ffffff;
--ion-color-favorite-contrast-rgb: 255,255,255;
--ion-color-favorite-shade: #5ca56c;
--ion-color-favorite-tint: #78c288;
}
Now the favorite color can be used in CSS like below to set the background and
color on a div :
div {
background: var(--ion-color-favorite);
color: var(--ion-color-favorite-contrast);
}
354
Ionic 5 • 8 Theming, Styling, Customizing
The application colors are used in multiple places in Ionic. These are useful for
easily creating themes that match a brand. You can address them via the following
CSS variables:
Name Description
--ion-background-color Background color of entire app
--ion-background-color-rgb Background color of entire app, rgb format
--ion-text-color Text color of entire app
--ion-text-color-rgb Text color of entire app, rgb format
--ion-backdrop-color Color of the Backdrop component
--ion-overlay-background-color Background color of the overlays
--ion-border-color Border color
--ion-box-shadow-color Box shadow color
--ion-tab-bar-background Background of the Tab bar
--ion-tab-bar-background-focused Background of the focused Tab bar
--ion-tab-bar-border-color Border color of the Tab bar
--ion-tab-bar-color Color of the Tab bar
--ion-tab-bar-color-activated Color of the activated Tab
--ion-toolbar-background Background of the Toolbar
--ion-toolbar-border-color Border color of the Toolbar
--ion-toolbar-color Color of the components in the Toolbar
--ion-toolbar-color-activated Color of activated comps in the Toolbar
--ion-toolbar-color-unchecked Color of unchecked comps in the Toolbar
--ion-toolbar-color-checked Color of checked comps in the Toolbar
--ion-item-background Background of the Item
--ion-item-background-activated Background of the activated Item
--ion-item-border-color Border color of the Item
--ion-item-color Color of the components in the Item
--ion-placeholder-color Color of the placeholder in inputs
355
Ionic 5 • 8 Theming, Styling, Customizing
While the previously mentioned variables are useful for changing the colors of an
application, often times there is a need for variables used in multiple components.
The following global variables are shared across components to change global
padding settings and more:
Application Variables
Name Description
--ion-font-family Font family of the app
--ion-statusbar-padding Statusbar padding top of the app
--ion-safe-area-top Adjust the safe area inset top of the app
--ion-safe-area-right Adjust the safe area inset right of the app
--ion-safe-area-bottom Adjust the safe area inset bottom of the app
--ion-safe-area-left Adjust the safe area inset left of the app
--ion-margin Adjust the margin of the Margin attributes
--ion-padding Adjust the padding of the Padding attributes
Grid Variables
Name Description
--ion-grid-columns Number of columns in the grid
--ion-grid-padding-xs Padding of the grid for xs breakpoints
--ion-grid-padding-sm Padding of the grid for sm breakpoints
--ion-grid-padding-md Padding of the grid for md breakpoints
--ion-grid-padding-lg Padding of the grid for lg breakpoints
--ion-grid-padding-xl Padding of the grid for xl breakpoints
--ion-grid-column-padding-xs Padding of grid columns for xs breakpoints
--ion-grid-column-padding-sm Padding of grid columns for sm breakpoints
--ion-grid-column-padding-m d Padding of grid columns for md breakpoints
--ion-grid-column-padding-lg Padding of grid columns for lg breakpoints
--ion-grid-column-padding-xl Padding of grid columns for xl breakpoints
https://ionicframework.com/docs/theming/advanced
356
Ionic 5 • 8 Theming, Styling, Customizing
8.6 Fonts
Fonts can give an app an individual note. For large companies, they are usually
even an integral part of branding. Whether desired individuality or compelling cor-
porate design - in the use of other than the standard fonts, one or the other has to
be considered in Ionic apps.
357
Ionic 5 • 8 Theming, Styling, Customizing
358
Ionic 5 • 8 Theming, Styling, Customizing
@font-face {
font-family: 'Orkney';
font-style: bold;
src: url('../assets/font/OrkneyBold.ttf');
}
--ion-font-family: 'Orkney';
}
359
Ionic 5 • 8 Theming, Styling, Customizing
Here the declaration of the used fonts takes place. Every font-style ( normal , bold )
has to be declared individually. For example, if you want to use italicized text in
your app, you should also declare an italic font style (and of course copy the
corresponding font file into assets/font ).
<ion-header>
...
</ion-header>
<ion-content class="ion-padding">
<ion-card>
<ion-card-header>
<ion-card-subtitle>
{{tourtype}} / {{region}}
</ion-card-subtitle>
<ion-card-title style="font-weight:bold;">
{{tour.Title}}
</ion-card-title>
</ion-card-header>
<ion-card-content>
...
</ion-card-content>
</ion-card>
</ion-content>
360
Ionic 5 • 8 Theming, Styling, Customizing
361
Ionic 5 • 8 Theming, Styling, Customizing
Create a logo
In our app, we want to integrate an SVG graphic. The graphic contains the compa-
ny logo of our imaginary tourism company BoB Tours and will grace the head of
our “About this app” popover in the future.
We go through the following steps:
http://ionic.andreas-dormann.de/img/bob-tours-logo.svg
Let's open the SVG file and have a look at its content:
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.2, SVG Export Plug-In .
SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Logo"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 700 160" style="enable-background:new 0 0 700
160;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;stroke:#000000;stroke-width:8.8818;stroke-
miterlimit:10;}
.st1{fill:#FEF100;}
.st2{fill:#FEAD00;}
.st3{fill:#FEFEFE;}
362
Ionic 5 • 8 Theming, Styling, Customizing
</style>
<path id="border_1_" class="st0" d="M685.2,155H14.8c-5.4,0-
9.8-5.2-9.8-11.5v-127C5,10.2,9.5,5,14.8,5h670.4
c5.4,0,9.8,5.2,9.8,11.5v126.9C695,149.8,690.5,155,685.2,155z"/
>
<g id="text">
<path id="s" d="M638,134.2c-6.1,0-11.6-1.4-16.8-4.2c-5.4-3-
9.5-7.1-12.1-12.3c-2-4-3-8.8-3-14.2c0-1.2,0.4-2.2,1.3-3
c0.8-0.8,1.9-1.3,3-
1.3h11.8c2,0,3.4,0.9,4.2,2.8c0.5,1.1,0.9,2.9,1.1,5.4c0.2,2,1.3
,3.5,3.1,4.5c2.1,1,5.4,1.6,9.8,1.6
c8.4,0,12.6-2.9,12.6-8.7c0-3.5-3.3-7.6-10-12.2l-21.4-14.7c-
8.3-5.7-12.5-13.4-12.5-22.9c0-6.4,1.9-12.1,5.8-17.2
c5.2-6.9,13.4-10.3,24.8-
10.3c8.1,0,14.6,1.4,19.7,4.2c6.4,3.6,9.7,9.3,9.7,17v5.4c0,1.2-
0.4,2.2-1.3,3c-0.8,0.8-1.9,1.3-3,1.3
h-10.5c-2.9,0-4.3-2.2-4.3-6.6c0-2.1-0.9-3.7-2.8-4.9c-1.7-1-
3.8-1.5-6.2-1.5h-3.2c-2.8,0-5,0.7-6.8,2.2c-1.9,1.6-2.8,3.7-
2.8,6.3
c0,2.8,1.7,5.5,5.2,8.2c3.3,2.5,7.9,5.8,13.7,9.7c6.7,4.5,11.4,7
.7,13.9,9.5c4.6,3.4,7.8,6.8,9.5,10.2s2.6,8,2.6,13.8
c0,8.8-3.8,16-11.4,21.6C654.8,131.7,646.9,134.2,638,134.2z"/>
...
</g>
<g id="sun">
<path id="light-rays" class="st1" d="M154.6,39.5c2.9-2,5.6-
4.1,8.5-6.2c1.6-1.2,3.1-2.3,4.7-3.5c0.8-0.6,1.5-1.1,2.3-
1.6 ..."/>
<path id="inner-circle" class="st2" d="M175.8,78.7c-0.1,7.5-
1.9,14.9-6.2,21.1c-4.1,6-10,10.9-16.8,13.5
c-6.8,2.6-14.6,3.1-21.7,1.4c-7.5-1.7-14.1-6-19.1-11.8c-9.7-
11.3-11.5-28.5-3.9-41.4c3.7-6.3,9.5-11.7,16.1-14.8
c6.6-3.1,14.2-4,21.3-
3c14.9,2.3,27.4,14.7,29.8,29.6C175.6,75,175.8,76.9,175.8,78.7z
M129.1,50.5 M135.8,48.4 M128.6,51
M161.8,101.8L161.8,101.8L161.8,101.8L161.8,101.8z"/>
<path id="high-light" class="st3" d="M127.8,51.3c1-0.8,2.4-
1.2,3.6-1.6c1.5-0.5,3.1-1,4.6-1.2c3.2-0.6,6.5-0.6,9.8-0.2
c5.7,0.8,11.2,3.4,15.6,7.2c7.9,7,11.7,18,10.1,28.4c-0.5,3.3-
1.5,6.6-3.1,9.5c-0.8,1.6-1.8,3.1-2.8,4.5c-0.5,0.6-1,1.2-
1.5,1.8
363
Ionic 5 • 8 Theming, Styling, Customizing
c-0.3,0.3-1,1.4-1.5,1.4c0.1,0,1.5-2.1,1.6-2.3c0.8-1.2,1.5-
2.4,2.2-3.6c1.3-2.5,2.2-5.2,2.7-8c1-4.9,0.6-9.9-0.9-14.7
c-3.1-9.9-11.1-17.4-20.9-20.6c-3.1-1-6.4-1.6-9.7-1.7c-1.7,0-
3.3,0-5,0.2c-0.9,0.1-1.8,0.2-2.7,0.4
C129.2,51,128.5,51.3,127.8,51.3L127.8,51.3z"/>
</g>
</svg>
Following this header, as with all XML documents, is the root element; for SVG
documents this has the name svg :
<svg version="1.1"
id="Logo"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px"
viewBox="0 0 700 160">
The svg root element usually contains a version info and an id . In order to
uniquely associate the element and its content with the SVG namespace and give it
a defined meaning that has to do with the SVG recommendations, the namespace
is noted at the root element with the XML attribute structure xmlns . The attribute
viewBox can be used to specify which area of the drawing plane should be dis-
played in the display area.
Style statements determine what specific elements should look like. Here, for ex-
ample, a white fill and a black rounded border are defined in class .st0 .
<style type="text/css">
.st0{fill:#FFFFFF;
stroke:#000000;
stroke-width:8.8818;
stroke-miterlimit:10;}
.st1{fill:#FEF100;}
.st2{fill:#FEAD00;}
.st3{fill:#FEFEFE;}
</style>
364
Ionic 5 • 8 Theming, Styling, Customizing
After the style instructions follow the graphic elements. The element <path />
defines a curve consisting of an arbitrary number of subpaths, which in turn consist
of a combination of distances, elliptical arcs, square and cubic Bézier curves, which
are described by means of relative or absolute coordinates.
https://en.wikipedia.org/wiki/Scalable_Vector_Graphics
https://www.w3.org/TR/SVG/
We use the SVG file as source for an ion-img element (and replace the old h3 ele-
ment). Nothing special. But the special features of an SVG file, we will get to know
in the next section.
365
Ionic 5 • 8 Theming, Styling, Customizing
366
Ionic 5 • 8 Theming, Styling, Customizing
Ionic Animations is a new utility in Ionic 5 that allows developers to build complex
animations in a platform agnostic manner. Developers do not need to be using a
particular framework such as React or Angular, nor do they even need to be
building an Ionic app. As long as developers have access to v5.0 or greater of Ion-
ic Framework, they will have access to all of Ionic Animations.
Building efficient animations can be tricky. Developers are often limited by the li-
braries available to them as well as the hardware that their apps run on. On top of
that, many animation libraries use a JavaScript-driven approach to running anima-
tions where they handle the calculation of your animation's values at every step in
a requestAnimationFrame loop. This reduces the scalability of your animations as
the library is constantly computing values and using up CPU time.
Ionic Animations uses the Web Animations API to build and run your animations.
In doing this, we offload all work required to compute and run your animations to
the browser. As a result, this allows the browser to make any optimizations it needs
and ensures your animations run as smoothly as possible. While most browsers
support a basic implementation of Web Animations, we fallback to CSS Animations
for browsers that do not support Web Animations.
367
Ionic 5 • 8 Theming, Styling, Customizing
Open details.page.html and add the following (bold formatted) HTML code in
the footer area:
<ion-header>
...
</ion-header>
<ion-content class="ion-padding">
...
</ion-content>
368
Ionic 5 • 8 Theming, Styling, Customizing
<ion-fab-list side="start">
<ion-fab-button (click)="openSocial('facebook')"
color="tertiary">
<ion-icon name="logo-facebook"></ion-icon>
</ion-fab-button>
<ion-fab-button (click)="openSocial('instagram')"
color="danger">
<ion-icon name="logo-instagram"></ion-icon>
</ion-fab-button>
<ion-fab-button (click)="openSocial('twitter')"
color="secondary">
<ion-icon name="logo-twitter"></ion-icon>
</ion-fab-button>
<ion-fab-button (click)="openSocial('whatsapp')"
color="success">
<ion-icon name="logo-whatsapp"></ion-icon>
</ion-fab-button>
</ion-fab-list>
</ion-fab>
</ion-footer>
We now can use the “animatedButton” id to address this particular button in the
code behind. Don't forget to delete the *ngIf directive! It's not needed any more.
369
Ionic 5 • 8 Theming, Styling, Customizing
@Component({
selector: 'app-details',
templateUrl: './details.page.html',
styleUrls: ['./details.page.scss'],
})
export class DetailsPage implements OnInit {
tour = null;
isFavorite: boolean;
region: string;
tourtype: string;
showSocial: boolean;
constructor(
private activatedRoute: ActivatedRoute,
public btService: BobToursService,
public favService: FavoritesService,
private actionSheetCtrl: ActionSheetController,
private alertCtrl: AlertController,
private modalCtrl: ModalController,
private animationCtrl: AnimationController
) { }
ngOnInit() {
let id = this.activatedRoute.snapshot.paramMap.get('id');
this.tour = _.find(this.btService.tours,
['ID', parseInt(id)]);
this.isFavorite = this.favService.favIDs
.indexOf(parseInt(id)) != -1;
this.region = _.find(this.btService.regions,
{ 'ID': this.tour.Region }).Name;
this.tourtype = _.find(this.btService.tourtypes,
{ 'ID': this.tour.Tourtype }).Name;
}
async presentActionSheet() {
...
}
async presentAlert() {
370
Ionic 5 • 8 Theming, Styling, Customizing
...
}
this.showSocial = !this.showSocial;
const animatedButton =
document.getElementById('animatedButton');
if (this.showSocial) {
fadeOut.play();
} else {
fadeIn.play();
}
openSocial(app) {
...
}
async presentModal() {
...
}
async presentMap() {
...
}
371
Ionic 5 • 8 Theming, Styling, Customizing
Now we can use this controller in the toggleSocial() method to animate our
button. With
const animatedButton =
document.getElementById('animatedButton');
With
const fadeIn = this.animationCtrl.create()
.addElement(animatedButton)
.duration(400)
.fromTo('opacity', 0, 1);
we declare a fadeIn animation with the help of the create() method of Ionic's
AnimationController. addElement assigns the animation to our button, duration
sets the length of the animation to 400 milliseconds, fromTo defines the actual ani-
mation by changing the opacity from 0 to 1.
It's not surprising that the following fadeOut animation is coded in the same man-
ner with the exception of the fromTo instruction, which animates the opacity in
reverse order.
In the last piece of code
if (this.showSocial) {
fadeOut.play();
} else {
fadeIn.play();
}
we play the fadeIn or fadeOut animation depending on the (true or false ) val-
ue of this.showSocial .
372
Ionic 5 • 8 Theming, Styling, Customizing
SVG animation
In the previous section, we got to know the use of an SVG graphic (see " 8.7 Scal-
able Vector Graphics (SVG)" starting on page 362). How would you like it if we let
the sun rise in our SVG logo as soon as the user opens the page menu?
For this we open our logo file bob-tours-logo.svg and look for the following en-
try:
<g id="sun">
It is the identification of a group ( g), which consists of several path elements and
represents the solar part of our logo.
In SVG files, you can animate any group, as well as any path element. So you could
also animate each single letter of our logo individually.
c5.4,0,9.8,5.2,9.8,11.5v126.9C695,149.8,690.5,155,685.2,155z"/
>
<g id="text">
...
</g>
373
Ionic 5 • 8 Theming, Styling, Customizing
<g id="sun">
<animateTransform attributeType="xml"
attributeName="transform"
type="rotate"
from="0 139 79"
to="360 139 79"
dur="6s"
repeatCount="indefinite" />
<path id="light-rays" class="st1" ... />
<path id="inner-circle" class="st2" ... />
<path id="high-light" class="st3" ... />
</g>
</svg>
There is much more to discover here, such as the <animateMotion> tag, which
moves an element along a motion path or the <animateColor> tag, which modi-
fies the color value of particular attributes or properties over time. But that would
be beyond the scope of this book. That's why I've listed quite a few useful links for
you on the next page.
374
Ionic 5 • 8 Theming, Styling, Customizing
More informations about Ionic and SVG animations you can find here:
https://ionicframework.com/docs/utilities/animations
https://en.wikipedia.org/wiki/SVG_animation
https://css-tricks.com/guide-svg-animations-smil
https://www.hongkiat.com/blog/svg-animations
https://theartificial.com/blog/2018/05/23/svg-animation.html
375
Ionic 5 • 8 Theming, Styling, Customizing
--ion-toolbar-background: var(--ion-color-primary);
--ion-toolbar-color: var(--ion-color-primary-contrast);
ion-icon {
color: var(--ion-color-primary);
}
376
Ionic 5 • 8 Theming, Styling, Customizing
--ion-toolbar-background: var(--ion-color-primary);
--ion-toolbar-color: var(--ion-color-primary-contrast);
.button {
color: var(--ion-color-primary-contrast);
}
ion-card-subtitle {
color: rgba(var(--ion-color-primary-rgb), 0.5);
}
In both SCSS files, we set the background and text color for the toolbar . At first
glance, the definitions seem identical. But beware! In azure-style.scss we just
swap out the full color palette for the primary color. And we know meanwhile:
With this we change the appearance of the entire app radically. The seemingly
identical instructions in azure-style.scss therefore relate to a completely new
color palette.
377
Ionic 5 • 8 Theming, Styling, Customizing
For the rest, in azure-style.scss we define the colors of a few more elements,
such as ion-card-title , ion-card-content and ion-card-subtitle, as well as
ion-chip and ion-chip ion-icon .
@import './azure-style.scss';
@import './summer-style.scss';
//--ion-toolbar-background:
var(--ion-color-primary);
//--ion-toolbar-color:
var(--ion-color-primary-contrast);
//.button {
// color: var(--ion-color-primary-contrast);
//}
@font-face {
font-family: 'Orkney';
font-style: bold;
378
Ionic 5 • 8 Theming, Styling, Customizing
src: url('../assets/font/OrkneyBold.ttf');
}
--ion-font-family: 'Orkney';
379
Ionic 5 • 8 Theming, Styling, Customizing
<ion-app>
<ion-split-pane>
<ion-menu>
<ion-header>
...
</ion-header>
<ion-content translucent="true">
...
</ion-content>
<ion-footer>
...
</ion-footer>
</ion-menu>
<ion-router-outlet main></ion-router-outlet>
</ion-split-pane>
</ion-app>
</div>
We wrap the entire content of app.component.html into a div tag. This gives us
access to the highest hierarchical level of our app. We now assign the current style
to this new div level via
[class] = "settings.style"
380
Ionic 5 • 8 Theming, Styling, Customizing
Let's summarize: We have defined two themes and put them under src/theme in
their own SCSS file (azure-style.scss a n d summer-style.scss ) . I n vari-
ables.scss we imported these themes. In a div layer that encloses our app, a
class attribute via data binding ensures the change of themes/styles.
The actual change of the style value, we remember, is done by the user tapping
one of the two radio buttons “Azure-Style” or “Summer-Style” in the side menu.
The buttons are linked to the variable settings.style via the entry
<ion-list radio-group [(ngModel)] = "settings.style">
If their value changes, the class name changes in the div tag. This in turn leads to
rendering the entire app based on the associated theme.
https://ionicframework.com/docs/theming/themes
https://ionicframework.com/docs/theming/dark-mode
https://ionicframework.com/docs/theming/advanced
381
Ionic 5 • 8 Theming, Styling, Customizing
Responsive is duty
A responsive design is mandatory! This should not only apply to websites, but also
to apps. Unfortunately, there are still apps in the stores that look like a mouse cine-
ma on the tablet. This won't happen with our app, because with Ionic it's easy to
bring our app on the big stages of iPads & Co.
<ion-app>
<ion-split-pane contentId="main-content">
<ion-header>
...
</ion-header>
<ion-content translucent="true">
...
</ion-content>
<ion-footer>
...
</ion-footer>
</ion-menu>
<ion-router-outlet id="main-content">
</ion-router-outlet>
</ion-split-pane>
</ion-app>
</div>
382
Ionic 5 • 8 Theming, Styling, Customizing
I'm sure you wondered what the hell ion-split-pane is doing here, right?
First of all, how does ion-split-pane get into our app? That is answered quickly.
With
$ ionic start bob-tours sidemenu
we had chosen the sidemenu template, which has generated an app with just this
structure (see “3.5 The side menu app for our book project” on page 72). Of
course, you can retrofit any differently structured app yourself with an ion-split-
pane .
Breakpoints
By default, the SplitPane will expand when the screen is larger than 768px . To cus-
tomize this, pass a breakpoint in the when property. The when property can accept
a boolean value, any valid media query, or one of Ionic's predefined sizes.
<!-- can be "xs", "sm", "md", "lg", or "xl" -->
<ion-split-pane when="sm"></ion-split-pane>
383
Ionic 5 • 8 Theming, Styling, Customizing
Our app always adapts perfectly thanks to ion-split-pane . Hard to believe that
this is so easy, right?
384
Ionic 5 • 8 Theming, Styling, Customizing
Adjust/centralize logic
Our app has a little flaw – maybe you have already noticed it: The Badge num-
bers that indicate the number of available tours, appear and update only when we
switch from “Tour-Types” to “Regions” or vice versa in the side menu. But an up-
date should take place immediately as soon as the price range is changed. We'll
optimize that by removing the filter logic from the regions and tour types pages
and centralize them in bob-tours-service.ts .
We refactor our logic in the following steps:
@Injectable({
providedIn: 'root'
})
export class BobToursService {
baseUrl = 'https://bob-tours-app.firebaseio.com/';
385
Ionic 5 • 8 Theming, Styling, Customizing
async initialize() {
const loading = await this.loadingCtrl.create({
message: 'Loading tour data...',
spinner: 'crescent'
});
await loading.present();
await this.getRegions().then(data => this.regions = data);
await this.getTourtypes().then(data => this.tourtypes
= _.sortBy(data, 'Name'));
await this.getTours().then(data => {
this.tours = _.sortBy(data, 'Title');
this.all_tours = _.sortBy(data, 'Title');
this.filterTours( {lower: 80, upper: 400} );
this.favService.initialize(this.all_tours);
});
await loading.dismiss();
}
getRegions() {
let requestUrl = `${this.baseUrl}/Regions.json`;
return this.http.get(requestUrl).toPromise();
}
getTourtypes() {
let requestUrl = `${this.baseUrl}/Tourtypes.json`;
return this.http.get(requestUrl).toPromise();
}
getTours() {
let requestUrl = `${this.baseUrl}/Tours.json`;
return this.http.get(requestUrl).toPromise();
}
filterTours(price):number {
this.tours = _.filter(this.all_tours, function(tour) {
return tour.PriceG >= price.lower
&& tour.PriceG <= price.upper;
});
this.regions.forEach(region => {
const rtours = _.filter(this.tours,
['Region', region.ID]);
region['Count'] = rtours.length;
386
Ionic 5 • 8 Theming, Styling, Customizing
});
this.tourtypes.forEach(tourtype => {
const ttours = _.filter(this.tours,
['Tourtype', tourtype.ID]);
tourtype['Count'] = ttours.length;
});
return this.tours.length;
}
With implementing the (bold formatted) filter logic for this.regions and this.-
tourtypes in our filterTours() method we ensure that these variables are also
updated whenever one of the filter parameters is changed by the user.
2. Modify RegionsPage
In regions.page.ts we delete the following out-commented lines:
import { Component, OnInit } from '@angular/core';
import { BobToursService } from 'src/app/services/bob-
tours.service';
import _ from 'lodash';
@Component({
selector: 'app-regions',
templateUrl: './regions.page.html',
styleUrls: ['./regions.page.scss'],
})
export class RegionsPage implements OnInit {
//regions: any;
constructor(public btService:BobToursService) { }
387
Ionic 5 • 8 Theming, Styling, Customizing
ngOnInit() {
/* this.regions = this.btService.regions;
this.regions.forEach(region => {
const tours = _.filter(this.btService.tours,
['Region', region.ID]);
region['Count'] = tours.length;
}); */
}
We remove the variable regions and in ngOnInit() its corresponding filter logic,
because we centralized it in the BobToursService just now.
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let region of btService.regions"
[routerLink]="['/list', { Category: 'Region',
ID: region.ID,
Name: region.Name } ]"
routerDirection="forward"
[disabled]="region.Count==0">
<ion-icon name="{{region.Icon}}" slot="start"></ion-i-
con>
{{region.Name}}
<ion-badge slot="end">{{region.Count}}</ion-badge>
</ion-item>
</ion-list>
</ion-content>
388
Ionic 5 • 8 Theming, Styling, Customizing
3. Modify TourTypesPage
In tour-types.page.ts we delete the following out-commented lines:
import { Component, OnInit } from '@angular/core';
import { BobToursService } from 'src/app/services/bob-
tours.service';
import _ from 'lodash';
@Component({
selector: 'app-tour-types',
templateUrl: './tour-types.page.html',
styleUrls: ['./tour-types.page.scss'],
})
export class TourTypesPage implements OnInit {
// tourtypes: any;
constructor(private btService:BobToursService) { }
ngOnInit() {
/* this.tourtypes = this.btService.tourtypes;
this.tourtypes.forEach(tourtype => {
const tours = _.filter(this.btService.tours,
['Tourtype', tourtype.ID]);
tourtype['Count'] = tours.length;
}); */
}
We remove the variable tourtypes and in ngOnInit() its corresponding filter log-
ic, because we centralized it in the BobToursService.
389
Ionic 5 • 8 Theming, Styling, Customizing
</ion-header>
<ion-content class="ion-padding">
<ion-list>
<ion-item *ngFor="let tourtype of btService.tourtypes"
[routerLink]="['/list', { Category: 'Tourtype',
ID: tourtype.ID,
Name: tourtype.Name } ]"
routerDirection="forward"
[disabled]="tourtype.Count==0">
<ion-icon name="{{tourtype.Icon}}" slot="start">
</ion-icon>
{{tourtype.Name}}
<ion-badge slot="end">{{tourtype.Count}}</ion-badge>
</ion-item>
</ion-list>
</ion-content>
In multi-view mode, our app now reacts directly to changed user's price filtering:
390
Ionic 5 • 8 Theming, Styling, Customizing
https://ionicframework.com/docs/api/split-pane
https://ionicframework.com/docs/layout/structure#split-pane-layout
391
Ionic 5 • 8 Theming, Styling, Customizing
Summary
In this chapter, you have learned the basics of theming, styling and customizing an
Ionic app.
From simple theming with the Color Generator to targeted changes using CSS Vari-
ables and CSS Utilities to advanced techniques.
You have embedded your own fonts into the app, got to know the handling and
the animation of SVG graphics.
Even dynamic theming and responsive designs are no longer secrets for you.
392
Ionic 5 • 9 Ionic Native
9 Ionic Native
9.1 Introduction
One of the most common misconceptions about Ionic is that as an app developer
you have no access to the same native SDK features as native apps. That's not
right!
With Ionic Native you have complete native access to the hardware of a mobile
device. Taking pictures with the camera, connecting to other devices via Bluetooth,
authentication via the fingerprint scanner - all this and much more is possible.
393
Ionic 5 • 9 Ionic Native
Ionic Native makes it easy to add native device functionality to any Ionic app lever-
aging Cordova or Capacitor.
In the next division we'll show the usage of Ionic Native by implementing the Ge-
olocation Cordova plugin in our app. Note that there are community and
enterprise/premier editions of the plugins. Community Plugins are a collection of
open source Cordova plugins that make it easy to add native functionality to any
Ionic app. They're submitted and maintained by the Ionic community. We'll use the
free community edition of the Geolocation plugin.
In addition to Cordova, Ionic Native also works with Capacitor (see chapter “B2.
Ionic and Capacitor”, starting from page 565). Cordova and Capacitor can be used
together in an Ionic project.
More informations about Ionic Native you can find here:
https://ionicframework.com/docs/native
https://ionicframework.com/docs/native/community
394
Ionic 5 • 9 Ionic Native
9.2 Geolocation
The Geolocation plugin provides information about the location of a terminal such
as latitude and longitude. Global positioning system (GPS), network information
such as the IP address, RFID, WiFi and Bluetooth MAC addresses, as well as GSM /
CDMA cell IDs are used to achieve the most accurate results possible.
This API is based on the W3C Geolocation API Specification, and only executes on
devices that don't already provide an implementation.
For iOS you have to add this configuration to your config.xml file:
<edit-config file="*-Info.plist"
mode="merge"
target="NSLocationWhenInUseUsageDescription">
<string>
We use your location for full functionality of certain
app features.
</string>
</edit-config>
Installation
First we install the (community) plugins of Cordova and Ionic Native via terminal:
$ ionic cordova plugin add cordova-plugin-geolocation
$ npm install @ionic-native/geolocation
395
Ionic 5 • 9 Ionic Native
Important imports
Now it's important to import (what a wording ;-)) Geolocation in:
1. app.module.ts
2. map.module.ts and
3. map.page.ts and
4. not to forget to append /ngx to every import statement.
1. app.module.ts
We start with the import in app.module.ts:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
@NgModule({
declarations: [AppComponent, AboutComponent],
entryComponents: [AboutComponent],
imports: [
BrowserModule,
IonicModule.forRoot(),
396
Ionic 5 • 9 Ionic Native
AppRoutingModule,
HttpClientModule,
IonicStorageModule.forRoot(),
FormsModule,
ReactiveFormsModule
],
providers: [
StatusBar,
SplashScreen,
Geolocation,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrate-
gy }
],
bootstrap: [AppComponent]
})
export class AppModule { }
2. map.module.ts
Now we import it – in the same way – to map.module.ts:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
MapPageRoutingModule
],
declarations: [MapPage],
397
Ionic 5 • 9 Ionic Native
providers: [Geolocation]
})
export class MapPageModule { }
3. map.page.ts
Let's finally import Geolocation in map.page.ts and also add a function to use the
plugin:
import { Component, OnInit } from '@angular/core';
import { ModalController, LoadingController }
from '@ionic/angular';
@Component({
selector: 'app-map',
templateUrl: './map.page.html',
styleUrls: ['./map.page.scss'],
})
export class MapPage implements OnInit {
currentView = 'map';
constructor(
private modalCtrl: ModalController,
private loadingCtrl: LoadingController,
private geolocation: Geolocation) { }
ngOnInit() {
this.calcRoute();
}
// Calculates a route
from current user position to destination
async calcRoute() {
const loading = await this.loadingCtrl.create({
message: 'Calculate route...',
spinner: 'crescent'
});
await loading.present();
398
Ionic 5 • 9 Ionic Native
...
We import Geolocation and inject it with the variable geolocation into the con-
structor. We also import a LoadingController (see “6.19 Progress Indicators”,
starting from page 240) and inject it with the variable loadingCtrl into the con-
structor.
Now we make use of the native Geolocation component to get the current position
of our device:
const geo = await this.geolocation.getCurrentPosition();
For our card logic we limit ourselves to latitude and longitude. However, some de-
vices also output other parameters such as altitude.
399
Ionic 5 • 9 Ionic Native
When first retrieved, the browser, the emulator or the device asks us if we can
query the location. If we allow it, after some waiting we'll be rewarded with the
rather exact coordinates of our location.
https://ionicframework.com/docs/native/geolocation
400
Ionic 5 • 9 Ionic Native
Of course, there's also a native plugin in Ionic Native for the use of maps, namely
Google Maps. You can find it here:
https://github.com/ionic-team/ionic-native-google-maps
This plugin supports Android and iOS only.
https://developers.google.com/maps/documentation/JavaScript/tutorial
To implement a map functionality in our app, the following steps are required:
401
Ionic 5 • 9 Ionic Native
First you have to log in and pick the Maps option and then click CONTINUE.
Second you have to select a project. If you followed the database chapter (see "5.1
Database Backend with Google Firebase", starting from page 97), you already
have a “BoB-Tours-App” project. Otherwise, you have to create one now.
402
Ionic 5 • 9 Ionic Native
With a click on NEXT the process will be finished and you'll get your API KEY:
Copy this API KEY into the clipboard. We'll need it for the next step.
<head>
<meta charset="utf-8" />
<title>Ionic App</title>
<meta name="viewport"
content="viewport-fit=cover, width=device-width,
initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0,
user-scalable=no" />
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
<link rel="icon"
type="image/png"
href="assets/icon/favicon.png" />
403
Ionic 5 • 9 Ionic Native
</head>
<body>
<app-root></app-root>
</body>
</html>
Now replace YOUR_API_KEY with your own API KEY (from the clipboard).
With this script entry, we get access to the Google Maps JavaScript API, and thus a
whole range of useful map features, as we'll see shortly. The async attribute speci-
fies that the script will execute (asynchronously) as soon as it's available. With the
attribute defer we make sure that our app is fully rendered before the script is ex-
ecuted. In other words: load the script asynchronously ( async ), but don't execute it
before the page has been completely rendered ( defer ).
404
Ionic 5 • 9 Ionic Native
<ion-content>
<div id="map"
[class.hidden]="currentView=='description'">
</div>
<div id="description"
[class.hidden]="currentView!='description'"
padding>
</div>
</ion-content>
<ion-footer class="ion-padding">
<ion-segment [(ngModel)]="currentView"
(ionChange)="currentViewChanged($event)">
<ion-segment-button value="map">
<ion-label>Map</ion-label>
</ion-segment-button>
<ion-segment-button value="route">
<ion-label>Route</ion-label>
</ion-segment-button>
<ion-segment-button value="description">
<ion-label>Description</ion-label>
</ion-segment-button>
</ion-segment>
</ion-footer>
to show the current view value. You remember: we get it from an ion-segmen-
t-button when a user clicks on one. So that the values "map" , "route" or
405
Ionic 5 • 9 Ionic Native
"description" are not lowercase, we use the built-in pipe titlecase to capitalize
the first letter (see “2.8 Pipes”, starting from page 41).
In the content area we place two div tags, one for showing the map and one for
showing the description of a calculated route:
<div id="map"
[class.hidden]="currentView=='description'">
</div>
<div id="description"
[class.hidden]="currentView!='description'"
padding>
</div>
Perhaps you wanna save space in favor of the map or description. Then delete the
padding directive in the ion-footer tag.
#description {
margin: 8px;
}
.hidden {
display: none;
}
We make sure that our divs get the maximum height and a white background and
we define the conditionally assigned CSS class "hidden" with display: none.
Now we use Google Maps JavaScript API to draw a map and calculate the route
from the user's position to a destination. It would be a good idea to write your own
provider for this. But to make it clearer, we do that in map.page.ts :
406
Ionic 5 • 9 Ionic Native
@Component({
selector: 'app-map',
templateUrl: './map.page.html',
styleUrls: ['./map.page.scss'],
})
export class MapPage implements OnInit {
currentView = 'map';
constructor(
private modalCtrl: ModalController,
private loadingCtrl: LoadingController,
private geolocation: Geolocation,
private navParams: NavParams
) { }
ngOnInit() {
this.initMap();
//this.calcRoute();
}
// Initialize map
initMap() {
this.tour = this.navParams.data;
this.destination = new google.maps.LatLng(
this.tour.StartingPoint.Lat,
this.tour.StartingPoint.Lng
);
this.map = new
google.maps.Map(document.getElementById('map'), {
407
Ionic 5 • 9 Ionic Native
center: this.destination,
zoom: 16,
fullscreenControl: false
});
this.isCalculated = false;
}
// Calculates a route
from current user position to destination
async calcRoute() {
if (this.isCalculated) return;
408
Ionic 5 • 9 Ionic Native
dirDisplay.setDirections(result);
}
});
First, to get the tour data in our map popup, we use NavParams, which we import
from '@ionic/angular' and inject into the constructor as a variable navParams .
It's needed by the referenced Google Maps JavaScript API (see the Google Maps
JavaScript API documentation for more information about this topic).
In the initMap() method we grab the tour from the calling DetailsPage with
this.tour = this.navParams.data;
409
Ionic 5 • 9 Ionic Native
We form it from the starting point of a tour. We get the starting point from the
database. Let's have a short look at this:
Lat contains the latitude and Lng the longitude information of the starting point of
a tour. With these coordinates we can
With this destination information we can now create our map. This happens with:
this.map = new
google.maps.Map(document.getElementById('map'), {
center: this.destination,
zoom: 16,
fullscreenControl: false
});
410
Ionic 5 • 9 Ionic Native
Finally, we set the isCalculated flag to false . What we need it for, we'll see right
away.
In the existing calcRoute() method we check, if isCalculated is true to prevent
unnecessary calculations:
if (this.isCalculated) return;
With
this.position = new google.maps.LatLng(
geo.coords.latitude,
geo.coords.longitude
);
we create a LatLng object from the current device position. You remember: we
determined this position named geo using the Ionic Native Geolocation plugin be-
fore ( see “9.2 Geolocation” on page 395).
With
const dirDisplay = new google.maps.DirectionsRenderer();
411
Ionic 5 • 9 Ionic Native
In the callback function we check for status=='OK' and use the SetDirections
method with the result as argument for it.
Again: You can find all these parameters and methods in the Google Maps
JavaScript API documentation.
Because we now have a calculated route, we set the isCalculated flag to true .
Finally, in the currentViewChanged(ev) method we react to every button:
switch (ev.detail.value) {
case 'map': this.initMap(); break;
case 'route' : this.calcRoute(); break;
case 'description': this.calcRoute(); break;
}
412
Ionic 5 • 9 Ionic Native
413
Ionic 5 • 9 Ionic Native
@Component({
selector: 'app-map',
templateUrl: './map.page.html',
styleUrls: ['./map.page.scss'],
})
export class MapPage implements OnInit {
currentView = 'map';
modal: Components.IonModal;
ngOnInit() {
this.initMap();
}
// Initialize map
initMap() {
this.tour = this.navParams.data;
this.destination = new google.maps.LatLng(
this.tour.StartingPoint.Lat,
414
Ionic 5 • 9 Ionic Native
this.tour.StartingPoint.Lng
);
this.map = new google.maps.Map(
document.getElementById('map'), {
center: this.destination,
zoom: 16,
fullscreenControl: false
});
this.isCalculated = false;
this.addDestinationMarker();
}
// Calculates a route
from current user position to destination
async calcRoute() {
if (this.isCalculated) return;
415
Ionic 5 • 9 Ionic Native
dirService.route({
origin: this.position,
destination: this.destination,
travelMode: 'DRIVING'
},
function(result, status) {
if (status == 'OK') {
dirDisplay.setDirections(result);
}
});
// icon
const iconCar = {
url: 'https://ionic.andreas-dormann.de/img/car-point.svg',
scaledSize: new google.maps.Size(96, 96)
}
// marker
const marker = new google.maps.Marker({
position: this.position,
map: this.map,
icon: iconCar
});
// icon
const iconSun = {
416
Ionic 5 • 9 Ionic Native
url: 'https://ionic.andreas-dormann.de/img/sun-point.svg',
scaledSize: new google.maps.Size(96, 96)
}
// marker
const marker = new google.maps.Marker({
position: this.destination,
map: this.map,
icon: iconSun
});
// information window
const infoWindow = new google.maps.InfoWindow({
content: '<h3>' + this.tour.Title + '</h3>' +
'<p>Starting point of the tour:<br />' +
this.tour.StartingPoint.Location + '</p>',
maxWidth: 200
})
// click handler
marker.addListener('click', function() {
infoWindow.open(this.map, marker);
});
417
Ionic 5 • 9 Ionic Native
The first argument position is assigned to this.destination and the second ar-
gument map is assigned to this.map .
If the user clicks on the marker, the info window will open.
418
Ionic 5 • 9 Ionic Native
419
Ionic 5 • 9 Ionic Native
420
Ionic 5 • 9 Ionic Native
If we plan a route, we need another marker, which we build in the method addPo-
sitionMarker() . We also want to look at this method bit by bit:
const iconCar = {
url: 'http://ionic.andreas-dormann.de/img/car-point.svg',
scaledSize: new google.maps.Size(96, 96)
}
The first argument position is assigned to this.position and the second argu-
ment map is again assigned to this.map .
That's it.
The marker is built on every call to the calcRoute() method.
This little configuration takes care of suppressing the Google Maps default markers
“A” and “B”. We have our own markers.
421
Ionic 5 • 9 Ionic Native
Let's marvel at the route display with our own markers at the beginning and end
of the route:
422
Ionic 5 • 9 Ionic Native
More informations about Ionic Native and related topics you can find here:
https://ionicframework.com/docs/native
https://ionicframework.com/docs/native/native-core
https://ionicframework.com/blog/ionic-native-mocks/
423
Ionic 5 • 9 Ionic Native
Summary
In this chapter, you have developed a basic understanding of Ionic Native.
Using the example of Geolocation you have used a native plugin. Furthermore,
you have enhanced our app with a great map functionality using the Google Maps
JavaScript API.
At the end I gave you a hint how you can develop using native plugins in the
browser with Ionic Native Mocks.
424
Ionic 5 • 10 Communication & Messaging
10 Communication
& Messaging
Today, almost every device communicates with everyone (Internet of Things). But
even more than ever, people communicate via special apps and software with
family members or on business. Smartphones are handheld messengers rather
than phones. They include WhatsApp, Snapchat, Skype, and TeamSpeak. And
surely you also have an idea, how the users should communicate with the apps
developed by you.
&sub-
to set the email subject, URL encode for longer sen-
ject=
tences, so replace spaces with %20, etc.
&body= to set the body of the message, you can add entire
sentences here, including line breaks. Line breaks
should be converted to %0A.
425
Ionic 5 • 10 Communication & Messaging
Email a request
In our app, we want to send the request for a tour via email using the path de-
scribed above.
426
Ionic 5 • 10 Communication & Messaging
@Component({
selector: 'app-request',
templateUrl: './request.page.html',
styleUrls: ['./request.page.scss'],
})
export class RequestPage implements OnInit {
validationForm: FormGroup;
validationMessages: any;
constructor(
private modalCtrl: ModalController,
private navParams: NavParams,
private toastCtrl: ToastController,
public formBuilder: FormBuilder
) {
this.tour = navParams.data;
}
ngOnInit() {
...
}
// Prepare form validation & messages
prepareFormValidation() {
...
}
427
Ionic 5 • 10 Communication & Messaging
this.request = request;
const br = '%0A';
window.location.href = email;
this.confirm();
this.modalCtrl.dismiss();
}
// User clicked 'Cancel'
cancel() {
this.modal.dismiss();
}
428
Ionic 5 • 10 Communication & Messaging
We build a complete email text using the request object (the user's input data from
the form).
To insert a line break, we use the variable br and assign it the URL encoded ex-
pression '%0A' . URL encoding replaces unsafe ASCII characters with a '%'
followed by two hexadecimal digits and should be displayed correctly in all mail
clients.
At the end we send the email with
window.location.href = email;
429
Ionic 5 • 10 Communication & Messaging
?body= to set the body of the message, you can add entire
sentences here, including line breaks. Line breaks
should be converted to %0A.
Examples:
In both cases, the app will launch your message app on the smartphone and you
can send the prepared SMS.
Phone calls
Via href you can also start phone calls. The phone call structure is:
Example:
window.location.href = 'tel:1-234-56789';
Geo
Another use of href is the use of geo: . This allows you to call an external map
app with predefined coordinates from your app. The appropriate syntax is:
Example:
window.location.href = 'geo:50.941278,6.958281';
430
Ionic 5 • 10 Communication & Messaging
Installation
Like the native plugin Geolocation used in the previous chapter (see chapter "9.2
Geolocation" starting on page 395), an installation of the plugins of Cordova and
Ionic Native is required.
@NgModule({
declarations: [AppComponent, AboutComponent],
entryComponents: [AboutComponent],
imports: [
BrowserModule,
IonicModule.forRoot(),
AppRoutingModule,
HttpClientModule,
IonicStorageModule.forRoot(),
431
Ionic 5 • 10 Communication & Messaging
FormsModule,
ReactiveFormsModule
],
providers: [
StatusBar,
SplashScreen,
Geolocation,
SocialSharing,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrate-
gy }
],
bootstrap: [AppComponent]
})
export class AppModule {}
import { SocialSharing }
from '@ionic-native/social-sharing/ngx';
@Component({
selector: 'app-details',
templateUrl: './details.page.html',
styleUrls: ['./details.page.scss'],
})
export class DetailsPage implements OnInit {
tour = null;
isFavorite: boolean;
region: string;
tourtype: string;
showSocial: boolean;
constructor(
private activatedRoute: ActivatedRoute,
432
Ionic 5 • 10 Communication & Messaging
ngOnInit() {
...
}
// Action Sheet
async presentActionSheet() {
...
}
// Alert
async presentAlert() {
...
}
433
Ionic 5 • 10 Communication & Messaging
this.socialSharing.shareViaFacebook(msg, img);
break;
case 'instagram':
this.socialSharing.shareViaInstagram(msg, img);
break;
case 'twitter':
this.socialSharing.shareViaTwitter(msg, img);
break;
case 'whatsapp':
this.socialSharing.shareViaWhatsApp(msg, img);
break;
}
})
.catch(() => {
this.errorOpenSocial(app, msg, sbj, img);
});
}
434
Ionic 5 • 10 Communication & Messaging
we first check if a social app is available and we are able to share content about it.
Depending on which app the user has selected, we then use a switch construct to
use the different methods of SocialSharing to send the message.
435
Ionic 5 • 10 Communication & Messaging
More informations about the Social Sharing native plugin you can find here:
https://ionicframework.com/docs/native/social-sharing
https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin
436
Ionic 5 • 10 Communication & Messaging
10.3 Notifications
Notifications are messages sent by apps that pop up on user's device, and their
function is to deliver information and improve engagement.
We distinguish two types of notifications:
• Local Notifications
• Push Notifications (also called Server Push Notifications)
Local Notifications
Local Notifications can be easily implemented and are a good introduction to the
world of notifications. We'll soon use this kind of notification for our app.
Push Notifications
A Push Notification allows to deliver information from the app to the mobile de -
vice (or computer) without a request from the user. Setting up push notifications
can be truly frustrating and time consuming. Since this topic would go beyond this
basic book, I have listed some links to good tutorials for those who are interested
in push notifications at the end of this section.
437
Ionic 5 • 10 Communication & Messaging
import { Geolocation }
from '@ionic-native/geolocation/ngx';
import { SocialSharing }
from '@ionic-native/social-sharing/ngx';
import { LocalNotifications }
from '@ionic-native/local-notifications/ngx';
@NgModule({
declarations: [AppComponent, AboutComponent],
entryComponents: [AboutComponent],
imports: [
BrowserModule,
IonicModule.forRoot(),
AppRoutingModule,
HttpClientModule,
IonicStorageModule.forRoot(),
FormsModule,
ReactiveFormsModule
],
providers: [
StatusBar,
SplashScreen,
Geolocation,
SocialSharing,
LocalNotifications,
{ provide: RouteReuseStrategy,
useClass: IonicRouteStrategy },
],
bootstrap: [AppComponent]
})
export class AppModule {}
438
Ionic 5 • 10 Communication & Messaging
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss']
})
export class AppComponent implements OnInit {
...
constructor(
private platform: Platform,
private splashScreen: SplashScreen,
private statusBar: StatusBar,
public btService: BobToursService,
private popoverCtrl: PopoverController,
private storage: Storage,
private alertCtrl: AlertController,
private router: Router
private localNotifications: LocalNotifications,
) {
this.initializeApp();
}
...
439
Ionic 5 • 10 Communication & Messaging
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss']
})
export class AppComponent implements OnInit {
...
// Loading settings
loadSettings() {
...
}
ngOnInit() {
...
}
440
Ionic 5 • 10 Communication & Messaging
441
Ionic 5 • 10 Communication & Messaging
In this new method we first check, if notifications are activated in the user settings.
You remember? The user can check/uncheck notifications via a checkbox in our
side menu (see “6.7 Checkbox” on page 177):
if (this.settings.notifications == true) {
If this condition is true, we call the schedule method of the Local Notifications
plugin. This method expects a JSON object with an id , a title , a text , a data
object and a trigger :
this.localNotifications.schedule({
id: 1,
title: 'BoB Tours recommends:' ,
text: 'Find a tour and enjoy life! Tap here...',
data: { path: '/slideshow' },
trigger: { every: ELocalNotificationTriggerUnit.WEEK }
});
While the id , title , and text properties are self-explanatory, I should explain the
data object. data can be everything. Here you can give hidden information to a
message, which you would like to use individually in an app. We use data here to
give a path to a particular page, here to our slideshow page. Our trigger every
is a so-called repeating trigger. Through the ELocalNotificationTriggerUnit
enumeration with the value WEEK , we specify that a notification is triggered every
week (starting from the time the schedule method is activated).
as the value for every . I didn't want to wait a whole week, until a message ap-
peared ;-) Be careful with SECOND as trigger unit. Maybe this could overwhelm your
system.
An interesting trigger variant seems to be the location based triggers, where a noti-
fication is triggered depending on the position of a mobile device.
The plugin has a set of other properties, all of which are described in the following
GitHub link to the Cordova plugin documentation.
442
Ionic 5 • 10 Communication & Messaging
When the user turns off the notifications, the cancelAll() method of the Local
Notifications plugin causes all notifications to be canceled.
sets up an event handler. It's an Observable that we can subscribe to. Whenever
the user clicks on a message, we evaluate the content of the notification. At first we
are interested in the value of data :
let path = notification.data
? notification.data.path
: '/';
We check if notification.data has any value at all. If so, we take the path prop-
erty, if not, we set an empty path.
this.alertCtrl.create({
header: notification.title,
message: 'Be inspired by the following slideshow
and book a tour!',
buttons: [{
text: 'Good idea!',
handler: () => this.router.navigateByUrl(path)
}]
})
.then(alert => alert.present());
The button handler ensures that our app changes to the specified path. Finally, af-
ter creating the alert, we present it.
That's it.
443
Ionic 5 • 10 Communication & Messaging
If you use this command for the first time, it will take a while, because Ionic/Cordo-
va first has to set up the Android platform with all the necessary plugins.
After starting the emulator we can see the new notifications functionality in action:
Perhaps your notifications are reported by "MyApp". That's because you probably
have not yet added an individual AppID to your app. We will do that in chapter
"12 Build, Deploy & Publish" (starting from page 511).
444
Ionic 5 • 10 Communication & Messaging
https://ionicframework.com/docs/native/local-notifications
https://github.com/katzer/cordova-plugin-local-notifications
https://ionicframework.com/docs/native/push
https://devdactic.com/push-notifications-ionic-onesignal/
https://www.freecodecamp.org/news/how-to-get-push-notifications-
working-with-ionic-4-and-firebase-ad87cc92394e/
445
Ionic 5 • 10 Communication & Messaging
Summary
In this chapter you got to know the basics of Communication & Messaging.
446
Ionic 5 • 11 Debugging & Testing
This command detects problems and provides guidance on how to fix them.
If you've developed our app according to the book, the command should give
you the following result:
4 Detecting issues: 1 / 1 complete - done!
[WARN] Package ID unchanged in config.xml.
- Detected 1 issue.
- 0 issues can be fixed automatically
447
Ionic 5 • 11 Debugging & Testing
Please don't alarm, if the doctor also determines a finding in your system.
The issue discovered here is harmless and states that we have not yet assigned an
Application ID for our app. We will do this later in "12.3 config.xml" (on page
520).
This shows more serious issues. Those labeled as treatable can be fixed automat-
ically with
$ ionic doctor treat [id]
For [id] then the appropriately named issue must be used, for example
$ ionic doctor treat ionic-installed-locally
https://ionicframework.com/docs/cli/commands/doctor-check
448
Ionic 5 • 11 Debugging & Testing
Most likely, the first thing that comes to mind with TypeScript is the optional static
type system that it provides. Types can be added to variables, functions, proper-
ties, etc. This will help the compiler and show warnings about any potential errors
in code, before an app is ever run. Types also help when using libraries and frame-
works, as they let developers know exactly what type of data APIs expect.
One of the biggest advantages of TypeScript is its code completion and Intel-
liSense. IntelliSense provides active hints as code is added. Since Ionic itself is
written in TypeScript as well, editors can present all the methods available and
what they expect as arguments. All the best IDE’s available today have support for
code completion, including VSCode, Atom, WebStorm, Sublime text, and even
command line editors, such as Vim/Neovim!
To be honest, for simplicity's sake I haven't taken it so seriously with strict typing in
this book so far. I would like to change that now explicitly. And I recommend that
you use the advantages of TypeScript in your own Ionic App projects as well! The
good reasons for doing this I have told you now.
Create classes
The most obvious typing we should do is create classes for the data supplied by
the database: Tour , Tourtype , Region (we can abstract the last two as Category ).
These commands create the classes Category , and Tour , each in their own sub-
folders, within src/app/models .
449
Ionic 5 • 11 Debugging & Testing
450
Ionic 5 • 11 Debugging & Testing
We've now defined some classes that owns public properties, each of them as-
signed to a specific data type. If you look closely to the Tour class, you'll notice the
StartingPoint property, which is of type Point . JavaScript/TypeScript doesn't
know this data type. This type is custom. That's why we have to define it ourselves
within tour.ts (or maybe in an extra class file).
Use classes
We now want to use the classes in our app code for typing.
Let's start with favorites.service.ts : and modify it (see bold formatted code):
import { Injectable } from '@angular/core';
import { Storage } from '@ionic/storage';
initialize(tours: Array<Tour>) {
this.favTours = [];
this.storage.ready().then(() => {
this.storage.get('FavoritesIDs').then(ids => {
451
Ionic 5 • 11 Debugging & Testing
this.favIDs = ids;
if (this.favIDs == null) {
this.favIDs = [];
} else {
this.favIDs.forEach(favID => {
let tour = tours.filter(t => t.ID == favID)[0];
tour.isFavorite = true;
this.favTours.push(tour);
});
}
});
});
}
add(tour:Tour) {
this.favIDs.push(tour.ID);
this.favTours.push(tour);
this.storage.set('FavoritesIDs', this.favIDs);
}
remove(tour:Tour) {
let removeIndex:number = this.favIDs.indexOf(tour.ID);
if (removeIndex != -1) {
this.favIDs.splice(removeIndex, 1);
this.favTours.splice(removeIndex, 1);
this.storage.set('FavoritesIDs', this.favIDs);
}
}
reorder(ev) {
this.favTours = ev.detail.complete(this.favTours);
this.favIDs = this.favTours.map(tour => tour.ID);
this.storage.set('FavoritesIDs', this.favIDs);
}
452
Ionic 5 • 11 Debugging & Testing
Wherever we know that an object of the type Tour is expected, we typify it explic-
itly. Now, all parts of the app that use our FavoritesService know exactly where the
specific Tour data type is expected. The service itself knows that from now on.
And now the code checker knows - already before the code execution - that our
data type Tour doesn't have the property isFavorite :
Why hasn't this been noticed so far? Well, in the initialize method, tours was
of the type any . This type is very variable and a property can be attached dynami
cally at runtime, which has worked so far. Now we have a static data type.
The solution to get rid of this new error is, of course, simple: We extend tour.ts
by the the property IsFavorite:
export class Tour {
public Description: string;
public Duration: number;
public ID: number;
public Image: string;
public IsFavorite: boolean;
public MaxPersons: number;
public PriceF: number;
public PriceG: number;
public Region: string;
public StartingPoint: Point;
public Title: string;
public Tourtype: string;
}
453
Ionic 5 • 11 Debugging & Testing
In this occasion, I have also made the conventional capitalization of the property.
To customize all the codes involved, the function Find in Files (see Edit Menu)
in combination with the Replace All button is helpful:
454
Ionic 5 • 11 Debugging & Testing
baseUrl = 'https://bob-tours-app.firebaseio.com/';
async initialize() {
const loading = await this.loadingCtrl.create({
message: 'Loading tour data...',
spinner: 'crescent'
});
await loading.present();
await this.getRegions().then(data => this.regions = data);
await this.getTourtypes()
.then(data => this.tourtypes = _.sortBy(data, 'Name'));
await this.getTours().then(data => {
this.tours = _.sortBy(data, 'Title');
this.all_tours = _.sortBy(data, 'Title');
this.filterTours({lower: 80, upper: 400});
this.favService.initialize(this.all_tours);
});
await loading.dismiss();
}
455
Ionic 5 • 11 Debugging & Testing
456
Ionic 5 • 11 Debugging & Testing
All of our variables (regions , tourtypes , tours and all_tours ) had been typed
as any so far. Again, it is better to type this strict. We do this by stating that the first
two are Arrays with elements of type Category and the other two are Arrays with
elements of type Tour .
Finally we also typify the tour parameter of the callback function within the filter
function of type Tour .
You like to do some more typing? Feel free to find more code that needs typing.
This is a good exercise in preparation for real live app projects.
More informations about typing with classes in TypeScript you can find here:
https://www.typescriptlang.org/docs/handbook/classes.html
457
Ionic 5 • 11 Debugging & Testing
There are many ways to open DevTools, because different users want quick access
to different parts of the DevTools UI.The most common way is via the menu: Click
Customize and control Google Chrome (the symbol with the three vertically ar-
ranged dots at the top right) and then select More Tools > Developer Tools .
There are keyboard shortcuts, of course: Use the keyboard shortcut CTRL+Shift+J
(on Windows) or CMD+Option+J (on Mac).
The DevTools have nine different areas. Here is an overview in alphabetical order:
• Application
• Audits
• Console
• Elements
• Memory
• Networks
458
Ionic 5 • 11 Debugging & Testing
• Performance
• Security
• Sources
We'll take a look at each of these areas in the context of our app.
Application
In the Application area you'll find various useful functions such as Clear storage
> Clear site data for resetting the app storage.As we have already seen in chap-
ter “5 Services & Storage” (see divisions "5.8 Local Storage" starting from page
136 and “5.9 Ionic Storage” starting from page 146), we can also see the storage
here and read out its keys and values. And we can also edit these with a little
knowledge about Web SQL queries.
459
Ionic 5 • 11 Debugging & Testing
In the Storage section we expand the storage engine, which is displayed on the
Clear storage page (see previous figure). That's Web SQL for me. We click the
corresponding domain table (for me _ionicstorage > _ionickv ) to show the
keys and values within the storage. For me it looks like that:
We now click on the domain level ( _ionicstorage ). Here's the Web SQL Console
where we can type Web SQL statements.
460
Ionic 5 • 11 Debugging & Testing
With
SELECT * FROM _ionickv
we display the contents of the _ionickv table. With
UPDATE _ionickv SET value = '[1,2,3,4,5,6,7]' WHERE id=2
we set our new bunch of FavoritesIDs . You have to admit, that this way is faster
than making all the input manually via the app, right?
Note: Successfully executed queries are colored blue. Errors are colored red.
If you switch back to the table view, you will find the new FavoritesIDs here. And
after a browser refresh, the corresponding tours are also listed in the app.
461
Ionic 5 • 11 Debugging & Testing
Audits
Via the audit panel you can identify and fix common problems that affect your ap-
p's performance, accessibility and user experience.
Let's activate all audit options and start a first run by clicking “Run audits”.
462
Ionic 5 • 11 Debugging & Testing
While the Performance metrics of our app leaves something to be desired, at Ac-
cessibility, Best Practices and SEO we already have decent values. In the
Diagnostics section, you can view more detailed information and suggestions for
improving the various parameters.
The Progressive Web App section hasn't been measured yet because, among oth-
er things, there is no Web App Manifest file yet. We'll change that later.
Console
The Console is probably the most used area of DevTools. The Console has two
main uses: viewing logged messages and running JavaScript.
In this book we've often logged messages to the Console to make sure that our
code is working as expected.
463
Ionic 5 • 11 Debugging & Testing
if (this.isCalculated) return;
464
Ionic 5 • 11 Debugging & Testing
Let's start the app, search for a tour, switch to it's details, open the map page and
calculate the route from our current position to the starting point of the tour.
At the desired positions, the respective console logs are output and provide de-
tailed information about the objects to be inspected.
There are a number of ways to configure the information that the Console issues,
for example, to hide warnings. Since this isn't a book on Chrome DevTools, please
refer to the documentation provided by Google for details, which I have listed as a
link at the end of this section.
465
Ionic 5 • 11 Debugging & Testing
Elements
The Elements panel has excellent tools to inspect and edit pages and styles and of-
fers the following options:
• inspect and edit on the fly any element in the DOM tree
• view and change the CSS rules applied to any selected element in the Styles
pane
• view and edit a selected element's box model in the Computed pane
• view any changes made to your page locally in the Sources panel
Let's try a few of these options to introspect the style of the icons in the side menu
and invert their colors. Start our app and open the side menu. Change the style to
“Summer-Style” With DevTools activated switch to the Elements panel and click the
Select an element in the page to inspect it button (at the upper left cor-
ner). Mark one of the icons. Now your element window should look something like
this:
Within the Document Object Model (DOM) of the page, the svg element (icon) is
highlighted. It's surrounded by a div tag, which in turn is integrated into an ion-
item tag. So we can see well what the Ionic framework rendered from the original
466
Ionic 5 • 11 Debugging & Testing
In the Style Panel on the right, we can also see the CSS instructions applied to the
element:
We can easily change these values by first editing the color and then deactivate
the background-color - directly in the Style pane:
These CSS modifications are not permanent, changes are lost when you reload the
page. But as you know from chapter “8 Theming, Styling, Customizing” (see divi-
sion “8.9 Dynamic Theming” starting from page 376), you can add some code to
summer-style.scss :
ion-content ion-icon {
color: var(--ion-color-primary);
background-color: var(--ion-color-primary-contrast);
}
467
Ionic 5 • 11 Debugging & Testing
Memory
Do you have any idea how much memory an Ionic App consumes? No? That
doesn't matter. Soon you will know! With Chrome's Memory tools you can
• find out how much memory your page is currently using with the Chrome Task
Manager
• visualize memory usage over time with Timeline recordings
• identify detached DOM trees (a common cause of memory leaks) with Heap
Snapshots
• find out when new memory is being allocated in your JavaScript heap with Allo-
cation Timeline recordings
Let's use the Chrome Task Manager as a starting point to our memory issue inves-
tigation. The Task Manager is a realtime monitor that tells us how much memory a
page is currently using.
Via the Chrome main menu we select More tools > Task Manager . On the table
header we right-click and enable JavaScript Memory.
The Memory Footage column represents native memory. DOM nodes are stored
in native memory. If this value is increasing, DOM nodes are getting created.
The JavaScript Memory column represents the JS heap. This column contains two
values. The value we're interested in is the live number (the number in parenthe-
468
Ionic 5 • 11 Debugging & Testing
ses). The live number represents how much memory the reachable objects in our
app are using. If this number is increasing, either new objects are being created, or
the existing objects are growing.
For me the Task Manager shows a live memory usage of 27,500K after starting
the app. If you do actions in the app, this value will increase. With me the memory
usage levels off at 34,000K to 36,000K, but after the the slideshow starts, it tem-
porarily rises over 40,000K.
The Memory panel has sophisticated tools for profiling memory problems.
469
Ionic 5 • 11 Debugging & Testing
Networks
Use the Network panel when you need to make sure that resources are being
downloaded or uploaded as expected. The most common use cases for the Net-
work panel are:
• making sure that resources are actually being uploaded or downloaded at all
• inspecting the properties of an individual resource, such as its HTTP headers,
content, size, and so on
We already used the Network panel in chapter “5 Services & Storage” (see division
“5.2 An HTTPClient Service”, starting from page 101) to see, what our newly creat-
ed service does.
Let's activate the DevTools and switch to the Network panel, click the Show over-
view button and see, what happens during the app start.
For me, at the application start 13/50 requests were executed, 6.8 MB data trans-
ferred and 6.8 MB of resources loaded. The whole thing took 5.25 s, with the
DOMContent having taken 593 ms load time (blue) and the remaining load (red)
849 ms. These details you can find in the footer of the Network panel.
470
Ionic 5 • 11 Debugging & Testing
In the overview timeline, mark the green bar so that the start mark is just before
the bar and the end marker is just behind the bar (see next figure – red frame):
We have hereby selected a temporal excerpt of the charging process of the app,
which we can take a closer look at. In the so-called Waterfall (on the right) we
see very well that first Regions.json , then Tortypes.json and then Tours.json
are loaded from the database. Sequential processes.
This is exactly what we coded in the initialize() method within bob-
tours.service.ts :
async initialize() {
...
await this.getRegions()
.then(data => this.regions = data);
await this.getTourtypes()
.then(data => this.tourtypes = _.sortBy(data, 'Name'));
await this.getTours()
.then(data => {
this.tours = _.sortBy(data, 'Title');
this.all_tours = _.sortBy(data, 'Title');
this.filterTours({lower: 80, upper: 400});
this.favService.initialize(this.all_tours);
});
...
By using the async/await concept, we wait to load the next data until the previ-
ous ones are fully loaded. Is that optimal? No.
471
Ionic 5 • 11 Debugging & Testing
If you've read chapter "2 Angular Essentials" (see division “2.11 Async / Await”,
starting from page 51), you may remember the ability to parallelize loads. That's
exactly what we want to do here now.
472
Ionic 5 • 11 Debugging & Testing
Now it will be exciting to see what the Network Panel will record, when we start
the app. Let's again take a closer look at the time span in which the data is loaded
from the database (green bars):
Whereas the previous serial load lasted 389 ms (130+125+134 ms), the parallel
loading of the data is done in 134 ms now - almost a third of the time.
You see, the Network Panel is a good helper to make loading processes visible
and perhaps pointing out ways to optimize it.
A tip on this topic: If you're looking for ways to improve page (not data) load per-
formance, don't start with the Network panel. There are many types of load
performance issues that aren't related to network activity. Start with the Audits pan-
el because it gives you targeted suggestions on how to improve your page.
Performance
With the help of the Performance panel yo can:
• record runtime performance
• simulate a mobile CPU
• analyze the results as frames per seconds (FPS)
• find bottlenecks
To get more comfortable with the Performance panel, practice makes perfect. So
let's try to profile our app and analyze the results. We do this step by step:
473
Ionic 5 • 11 Debugging & Testing
After recording, select an area of interest in the overview by dragging. Then, zoom
and pan the timeline with the mouse wheel or WASD keys.
It's pretty cool to move your mouse over the timeline and watch the operation like
a video and to get so much information about your app's performance. Some
people think that's a bit overwhelming. And yes, admittedly, to be able to do a tar-
geted analysis, I strongly recommend the DevTools documentation on this topic:
https://developers.google.com/web/tools/chrome-devtools/
evaluate-performance/
474
Ionic 5 • 11 Debugging & Testing
Security
Use the Security Panel in Chrome DevTools to make sure HTTPS is properly im-
plemented on a page. Every website should be protected with HTTPS, even sites
that don't handle sensitive user data.
To easily check which HTTP(S) addresses an app is calling, all you need to do is
start the app, enable DevTools, and switch to the Security panel:
475
Ionic 5 • 11 Debugging & Testing
Sources
Use the Chrome DevTools Sources panel to:
• view files.
• edit CSS and JavaScript.
• create and save Snippets of JavaScript, which you can run on any page. Snip-
pets are similar to bookmarklets.
• debug JavaScript.
• set up a Workspace, so that changes you make in DevTools get saved to the
code on your file system.
Use the Page pane to view all of the resources that the page has loaded.
• The top-level, such as top in the figure above, represents an HTML frame.
You'll find top on every app/page that you visit. top represents the main
document frame.
• The second-level, such as localhost:8100 in the figure above, represents an
origin.
• The third-level, fourth-level, and so on, represent directories and resources that
were loaded from that origin.
476
Ionic 5 • 11 Debugging & Testing
As you can see, in the domain localhost:8100 the precompiled JS files like 0.js ,
1.js , 2.js etc. can be found.
For example, if you dare to take a closer look at 0.js , you'll begin to suspect
something about how Ionic and Webpack will neatly transform your original Type-
Script code in the background.
A very useful way to use the Sources area is using the keyword debugger . When
most browsers encounter a debugger statement, running of JavaScript is stopped,
and the browser will load its debugger, so does Chrome. This can be used to set
"breakpoints" in the app. For example, if a function that isn't returning the correct
value, the debugger can be used to step through the code and inspect variables.
When an app runs, it will pause at this function. From there, the developer tools
can be used to run pieces of JavaScript, line by line, and inspect where exactly the
function breaks.
if (this.isCalculated) return;
477
Ionic 5 • 11 Debugging & Testing
spinner: 'crescent'
});
await loading.present();
debugger;
...
}
Start our app, activate DevTools and switch to the Sources panel. Now pick a tour,
click Options > Map and then Route .
Here we end up exactly at the point in the code that we have set with the debug-
ger instruction.
When we unfold the tree in the Page pane, we see that we are in the second level,
in the origin webpack:// . Here we find our project structure with our ts files.
478
Ionic 5 • 11 Debugging & Testing
But we now have several options to continue executing the code. You can use the
little toolbar on the top right to:
479
Ionic 5 • 11 Debugging & Testing
When we have objects with a lot of fields (and many rows) if you console.log it
out, you have to dig deep within these objects to uncover the values you're look-
ing for.
However, Chrome provides a handy function console.table , which will nicely for-
mat our data into a pretty table! Example:
console.table(geo);
480
Ionic 5 • 11 Debugging & Testing
How it works: You'll notice that the first argument to console.log is a string, as
normal, but with a special %c , which is where the CSS will be placed. We pass the
CSS as a string as the second argument.
You can even load images this way - if you use a background image.
Try the following:
let src = "https://i.imgur.com/UYkfHNy.png";
let img = new Image();
img.onload = () =>
console.log(
"%c+",
"background: url(" +
src +
"); background-size: 473px 315px; color transparent;
padding: 150px 200px"
);
img.src = src;
How it works: We're using a background URL within the CSS. You might notice
that getting the sizing right is a little tricky. If you're going to be doing this a lot,
checkout console.image.
481
Ionic 5 • 11 Debugging & Testing
482
Ionic 5 • 11 Debugging & Testing
And if we wanted to inspect this product's ingredients? We can again use con-
sole.table !
More informations about the Chrome DevTools you can find here:
https://developers.google.com/web/tools/chrome-devtools/
483
Ionic 5 • 11 Debugging & Testing
https://developer.android.com/studio
After an extensive build process an apk file is created. This is executed immediate-
ly in the emulator after it has been started. The optionally specified --livereload
option makes use of the dev server from ionic serve for live reload functionality.
484
Ionic 5 • 11 Debugging & Testing
Maybe you get the following error during the build process:
[ERROR] native-run wasn't found on your PATH.
Please install it globally:
npm i -g native-run
In this case install native-run and then repeat the emulate statement.
The emulator provides almost all of the capabilities of a real Android device. You
can simulate incoming phone calls and text messages, specify the location of the
device, simulate different network speeds, simulate rotation and other hardware
sensors, access the Google Play Store, and much more.
Testing your app on the emulator is in some ways faster and easier than doing so
on a physical device. For example, you can transfer data faster to the emulator
than to a device connected over USB.
The emulator comes with predefined configurations for various Android phone,
tablet, Wear OS, and Android TV devices.
More informations about the Android emulator you can find here:
https://ionicframework.com/docs/installation/android
https://developer.android.com/studio/run/emulator
Android Studio
Android Studio can also be used to launch the emulator and debug an app. Open
up Android Studio and choose File > Open from menu to open ../path-
to-app/platforms/android/ . After creating a virtual device for the emulator via
the Android Virtual Device (AVD) Tools you can click Run > Debug... from menu
to launch the app. Console output and device logs will be printed inside of Android
Studio's Gradle Console and Event Log windows.
https://developer.android.com/studio/debug
https://developer.android.com/studio/run/managing-avds
485
Ionic 5 • 11 Debugging & Testing
https://www.virtualbox.org/
You're able to download and install a free version of Genymotion for personal use
(after registration) from here:
https://www.genymotion.com/
Within Genymotion you can install various virtual mobile devices like a Samsung
Galaxy S3, for example. You can determine a specific Android version, the number
of processors, memory size, to show Android navigation bar, to use virtual key-
board for text input and much more. A virtual device is started as a virtual
machine in VirtualBox.
Now you just need to drag-and-drop the apk file created in the build/emulate
p r o c e s s ( s e e platforms/android/app/build/outputs/apk/debug/app-de-
bug.apk ) to the virtual device and our app starts within the Genymotion
environment.
https://cloud.geny.io/
486
Ionic 5 • 11 Debugging & Testing
487
Ionic 5 • 11 Debugging & Testing
After an extensive build process an apk file is created. Passing in the optional –
livereload flag will enable live reload.
Maybe you get the following error during the build process:
[ERROR] native-run wasn't found on your PATH.
Please install it globally:
npm i -g native-run
In this case install native-run and then repeat the emulate statement.
When I wrote this book, there was an issue with the build process - related to
Xcode's new 'Modern' build system - that I solved by creating the following
build.json file in the root directory of the app project:
{
"ios": {
"debug": {
"buildFlag": [
"-UseModernBuildSystem=0"
],
"developmentTeam": "YOUR_DEV_TEAM_ID"
},
"release": {
"buildFlag": [
"-UseModernBuildSystem=0"
],
"developmentTeam": "YOUR_DEV_TEAM_ID"
}
}
}
488
Ionic 5 • 11 Debugging & Testing
Xcode
Xcode can be used to launch the emulator and debug an app. Open up Xcode
and open ../path-to-app/platforms/ios/myApp.xcodeproj. After the app
loads, console output and device logs will be printed inside of Xcode's output win-
dow.
More informations about the iOS Simulator and Xcode you can find here:
https://developer.apple.com/library/archive/documentation/IDEs/Concep -
tual/iOS_Simulator_Guide/Introduction/Introduction.html
489
Ionic 5 • 11 Debugging & Testing
More informations about Ionic and emulators/simulators you can find here:
https://ionicframework.com/docs/cli/commands/cordova-emulate
490
Ionic 5 • 11 Debugging & Testing
During the creation of this book, prices ranged from $29 to $199 per month (en-
terprise fees on request).
https://www.browserstack.com
491
Ionic 5 • 11 Debugging & Testing
1. The smartphone is initially connected via USB to the desktop PC. It is important
to ensure that the drivers of the device are properly installed, otherwise it could
not be detected.
Under Settings > About <device> yo'll find a Build Number. This is tapped
several times (usually 7 times) until a message appears that you have now en-
abled the Developer options. Here you can turn on the USB Debugging.
3. We open Chrome on the desktop and add the following line to the address
bar: chrome://inspect/#devices
492
Ionic 5 • 11 Debugging & Testing
4. Here you can activate the detection of USB devices. After activation, the smart-
phone connected via USB cable should now also be recognized and a dialog
for authorization should be displayed. Now we are ready for debugging!
After finishing the building process our app appears on the display of the
Android device.
6. In Chrome we have a new entry for the running app. We can click on inspect
to start our live debugging now:
In a separate Chrome window, we can now follow all actions that we make on
the Android device:
More informations about Remote Debugging Android Devices you can find here:
https://developers.google.com/web/tools/chrome-devtools/remote-debugging/
493
Ionic 5 • 11 Debugging & Testing
- In Xcode' menu select Window > Devices and Simulators > Devices >
Connect via network .
- Unplug the USB cable, a “globe” icon next to your device name indicates
that it is now connected wirelessly.
- Install Safari Technology Preview.
- Follow the next instructions, use Safari Technology Preview
in place of Safari.
Now Safari on your Mac should be able to see your app running
on your device.
After finishing the build process your app appears on the display of the device.
6. Click on your app running on the device that you want to debug.
It will open a Web Inspector window for your app, showing the usual
developer tools.
494
Ionic 5 • 11 Debugging & Testing
https://developers.google.com/web/tools/chrome-devtools/remote-debugging/
https://geeklearning.io/live-debug-your-cordova-ionic-application-with-visual-studio-code/
https://ionicframework.com/blog/10-awesome-vs-code-extensions/
495
Ionic 5 • 11 Debugging & Testing
11.6 Testing
Testing Principles
When testing an application, note that testing can determine if a system is malfunc-
tioning. However, it's impossible to prove that a nontrivial system is completely
error free. For this reason, the goal of testing isn't to check the correctness of the
code, but to find problems in the code. This is a subtle but important distinction.
An Ionic/Angular app project is automatically set up for unit testing and end-to-end
testing of the app. Ionic uses the same setup that is used by the Angular CLI. So,
for detailed information on testing Ionic/Angular apps refer to:
https://angular.io/guide/testing
If we go to prove that the code is correct, we will stick to the happy path through
the code. If we look for problems, we are more likely to be more verbose and find
the errors that are lurking there.
It's also best to test an application from the beginning. This allows errors to be de -
tected early if they are easier to fix. In this way, the code can also be safely
redesigned as new features are added to the system.
What we can achieve with testing:
Testing Practices
With unit testing we take an isolated look at a single unit of code (component,
page, service etc.). We can achieve isolation from the rest of the system by injec-
tion of mock objects in place of the code's dependencies.
We use Jasmine (https://jasmine.github.io/) to create such mock objects, TestBed
(https://angular.io/api/core/testing/TestBed) t o w r i t e t e s t s a n d K a r m a
(https://karma-runner.github.io) to run tests. Don't worry: With Ionic 5 everything
we need for automated testing is included by default.
496
Ionic 5 • 11 Debugging & Testing
This runs unit tests in our current project. Let's see what happens...
In my project the following is output in the terminal (severely shortened):
12% building 19/19 modules 0 active
29 07 2019 17:48:01.629:WARN [karma]:
No captured browser, open http://localhost:9876/
29 07 2019 17:48:01.634:INFO [karma-server]:
Karma v4.1.0 server started at http://0.0.0.0:9876/
29 07 2019 17:48:01.634:INFO [launcher]:
Launching browsers Chrome with concurrency unlimited
12% building 22/22 modules 0 active
29 07 2019 17:48:01.679:INFO [launcher]:
Starting browser Chrome
29 07 2019 17:48:06.562:WARN [karma]:
No captured browser, open http://localhost:9876/
29 07 2019 17:48:06.688:INFO [Chrome 75.0.3770 (Mac OS X 10.10.5)]:
Connected on socket vWkCY8uBd2xIWdZTAAAA with id 16782258
Chrome 75.0.3770 (Mac OS X 10.10.5) AppComponent should create the
app FAILED
Error: StaticInjectorError(DynamicTestModule)[HttpClient]:
StaticInjectorError(Platform: core)[HttpClient]:
NullInjectorError: No provider for HttpClient!
at
NullInjector.push../node_modules/@angular/core/fesm5/core.js.NullInje
ctor.get (node_modules/@angular/core/fesm5/core.js:8896:1)
at resolveToken
(node_modules/@angular/core/fesm5/core.js:9141:1)
at tryResolveToken
(node_modules/@angular/core/fesm5/core.js:9085:1)
at StaticInjector.push../node_modules/@angular/core/fes-
m5/core.js.StaticInjector.get
(node_modules/@angular/core/fesm5/core.js:8982:1)
...
We've started a test process. Karma has started a server (at the address
http://0.0.0.0:9876 ) and has completed 17 tests, 14 of which failed.
497
Ionic 5 • 11 Debugging & Testing
After completion of the test, this is also displayed in a separate browser window
from the Karma server:
What does all this mean? Do you remember the spec files automatically generat-
ed by Ionic when creating a page/component? These spec files contain a whole
series of so-called test cases, at least one case per file.
498
Ionic 5 • 11 Debugging & Testing
it() defines a specific test or “spec”, and it lives inside of a suite ( describe() ).
This is what defines the expected behavior of the code you are testing, e.g. “it
should do this”, “it should do that”.
expect() defines the expected result of a test and lives inside of it() .
Back to the previous picture of our first test run: There were 17 test cases tested,
of which 14 failed. Of course that's not a good result. We will now apply and/or
adapt and skip some of these test cases to improve our test results.
describe('AppComponent', () => {
beforeEach(async(() => {
statusBarSpy
= jasmine.createSpyObj('StatusBar', ['styleDefault']);
splashScreenSpy
499
Ionic 5 • 11 Debugging & Testing
= jasmine.createSpyObj('SplashScreen', ['hide']);
platformReadySpy = Promise.resolve();
platformSpy
= jasmine.createSpyObj('Platform',
{ ready: platformReadySpy });
popoverSpy
= jasmine.createSpyObj('Popover', ['']);
localNotifySpy
= jasmine.createSpyObj('LocalNotify', ['']);
TestBed.configureTestingModule({
declarations: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{ provide: StatusBar, useValue: statusBarSpy },
{ provide: SplashScreen, useValue: splashScreenSpy },
{ provide: Platform, useValue: platformSpy },
{ provide: PopoverController, useValue: popoverSpy },
{ provide: LocalNotifications, useValue: localNotifySpy }
],
imports: [
RouterTestingModule.withRoutes([]),
HttpClientTestingModule,
IonicStorageModule.forRoot()
],
}).compileComponents();
}));
...
});
With this additions we ensure that the test unit AppComponent includes all refer-
enced providers. To eliminate all other No provider errors, I have modified the
following test files:
• bob-tours.service.spec.ts
• favorites.service.spec.ts
And - for reasons of simplification – I simply deleted the following files:
• details.page.spec.ts
• favorites.page.spec.ts
500
Ionic 5 • 11 Debugging & Testing
• list.page.spec.ts
• map.page.spec.ts
• regions.page.spec.ts
• request.page.spec.ts
• slideshow.page.spec.ts
• tour-types.page.spec.ts
To be able to test on the same level as me now, you can download the code of
the current project status from the book website (see "1.4 The book‘s website” on
page 20) in section 11.06.01.
A new Karma test with npm test should yield something like the following now:
501
Ionic 5 • 11 Debugging & Testing
We open a single test case with the callback method it . In this case we check if
the app can be created. We use TestBed's createComponent method in combina-
tion with debugElement.componentInstance to get the apps' instance. With
expect we check the result by using a so-called matcher function, in this case toBe-
Truthy() . This Boolean matcher is used in Jasmine to check whether the result is
equal to true or false.
There’s a whole range of these methods available for testing different scenarios, for
example:
• expect(fn).toThrow(e);
• expect(instance).toBe(instance);
• expect(mixed).toBeDefined();
• expect(mixed).toBeFalsy();
• expect(number).toBeGreaterThan(number);
• expect(number).toBeLessThan(number);
• expect(mixed).toBeNull();
• expect(mixed).toBeTruthy();
• expect(mixed).toBeUndefined();
• expect(array).toContain(member);
• expect(string).toContain(substring);
• expect(mixed).toEqual(mixed);
• expect(mixed).toMatch(pattern);
And if you want to get really advanced you can even define your own custom
matchers.
502
Ionic 5 • 11 Debugging & Testing
await platformReadySpy;
expect(statusBarSpy.styleDefault).toHaveBeenCalled();
expect(splashScreenSpy.hide).toHaveBeenCalled();
});
Here we check if the platform is ready. Because it's an asynchronous call, we use a
platformSpy and a platformReadySpy . We have defined these before in the be-
foreEach() part of the test description. With await we wait for the result and then
check whether Statusbar and SplashScreen have been called. For this we use the
matcher function toHaveBeenCalled() .
At this point we should complete the test case with the two following checks:
expect(popoverSpy).toBeDefined();
expect(localNotifySpy).toBeDefined();
With that we check, if also the objects popoverSpy and localNotifySpy we have
just added have been defined.
Here it's checked if the application has the expected menu labels. As Karma
showed, this automatically generated test case failed!
Let's formulate the requirement for our menu labels ourselves: We expect the
menu items 'Favorites' , 'Regions' , 'Tour-Types' and 'Slideshow', a total of
four entries. They entries can be found by the tag ion-menu-toggle.
503
Ionic 5 • 11 Debugging & Testing
If we save these changes and Karma is run again, this test case should now pass
successfully.
This test case checks if all expected menu urls or better said, routes are present in
the component. As Karma showed, this automatically generated test case failed,
too.
Again, let's formulate the test conditions for the required routes ourselves: The ap-
propriate routes to the menu items are '/favorites' , '/regions' , '/tour-
types' and '/slideshow' , a total of four routes.
504
Ionic 5 • 11 Debugging & Testing
To illustrate how to get the correct query selector for the querySelectorAll(...)
method and the correct name for the getAttribute(...) method, here's the De-
vTools > Elements panel with all the information we need:
505
Ionic 5 • 11 Debugging & Testing
If we save these changes and Karma is run again, this test case should now pass
successfully, too.
All our tests were successful, which karma acknowledged with nine green dots, the
summary “9 specs, 0 failures” and the corresponding text information about each
test unit and its test cases:
Congratulation! You have successfully created and performed some unit tests.
We won't go deeper into the topic of testing, because it isn't really an Ionic topic.
The concrete implementation of tests depend heavily on the respective JS frame-
work, in our case, on Angular. But I'd like to finally share a few words about of two
other types of tests:
• Testing Services
• End-to-End Testing
506
Ionic 5 • 11 Debugging & Testing
Testing Services
Testing services can be a challenge and that has a specific reason: In most cases,
JavaScript doesn't get information in sync - and that's the same with most services.
Either they return you an observable or a promise object, and only later will you
provide the information you requested. To solve this problem, refer to Jasmine's
test doubles. These are wrapper objects and functions that are used to read func-
tion calls (spy) as well as to control behavior (stubs).
So you get a reference to the service stub via the injector and create a Jasmine
stub using the get method of the service. This stub returns a promise, which is im-
mediately resolved with an array of two task objects. Despite this immediate
resolution, this is an asynchronous operation.
Ionic has already automatically generated the basic framework for testing for our
services.
Here's the code skeleton from bob-tours.service.spec.ts:
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule }
from '@angular/common/http/testing';
describe('BobToursService', () => {
beforeEach(() => TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
IonicStorageModule.forRoot()
]
}));
507
Ionic 5 • 11 Debugging & Testing
End-to-End Testing
End-to-end tests - short E2E tests - use Protractor (https://www.protractortest.org).
With E2E testing, you no longer only check units such as classes or individual func-
tions, but entire workflows in your application.
The setup and the configuration are easy and if you master JavaScript, you have a
very easy start. There is good material for the training and so the tool is ready to
use.
There are some technical requirements for testing an application with Protractor:
• The Angular application must be executable, only then Protractor can run the
tests.
• The screen elements should be well and - if possible - logically defined with ID
and name.
This is necessary in order to be able to access certain elements in the test code in
order to query their state, for example. If this is ignored during development, the
application's front-end code must first be adapted by assigning IDs and names for
the corresponding elements.
Although we only scratched the surface of the topic testing, I'm sure, this entry
gives you a first idea on how useful it is to test an app.
More informations about Testing an Ionic/Angular app you can find here:
https://angular.io/guide/testing
508
Ionic 5 • 11 Debugging & Testing
Summary
In this chapter you've learned a lot of different new things:
You've learned to play doctor and give your Ionic environment a health check.
You've learned a bit about strict typing and how helpful that can be, not only to
support a very convenient code completion.
When debugging your app, you now know how to use Chrome and its DevTools.
You can watch your app in the Android Emulator and/or iOS Simulator and test
native features in it.
Finally, you got into unit testing with Jasmine, TestBed and Karma and understand
why tests can be the way to improve software quality and prevent software de-
fects.
509
Ionic 5 • 11 Debugging & Testing
510
Ionic 5 • 12 Build, Deploy & Publish
Build
means: Process all of my code/artifacts and prepare them for deployment. Mean-
ing compile, generate code, package, etc. The process of creating executable
software packages is usually automated by build tools, which also ensure the con-
sistency of frameworks between different builds.
Deploy
means: Take all of my artifacts and either copy them to a server/mobile device, or
execute them on a server/mobile device. It should truly be a simple process.
Publish
is just a method of deployment. In the context of an app publication, we bring the
stores of Apple (App Store) and Google (Google Play) to the scene. We will discuss
this in more detail later in own sections. So much in advance: When you publish an
application you perform two main tasks:
• You prepare the application for release.
• You release the application to users.
511
Ionic 5 • 12 Build, Deploy & Publish
As the Ionic documentation says, ionic build (without any options) performs an
Ionic build, which compiles web assets and prepares them for deployment. It uses
the Angular CLI, what you can easily tell from the first line of output after starting
the command:
> ng run app:build
Use ng build --help to list all Angular CLI options for building your app. See the
ng build docs for explanations:
https://angular.io/cli/build
512
Ionic 5 • 12 Build, Deploy & Publish
We could grab the files in the www folder to put them on a web server and start a
web application. But we shouldn't do that because the code isn't yet optimized for
production!
Take a look at the files of the www folder, for example main.js :
As you can see, there is (in my file from line 602) in a slightly different, but read-
able form, our source code. I don't think that you want to publish the readable
version of it for everyone on a web server - unless you want to share your app as
open source with the world.
So, what to do?
513
Ionic 5 • 12 Build, Deploy & Publish
514
Ionic 5 • 12 Build, Deploy & Publish
Sometimes it can happen that a project with ionic serve/build runs smoothly,
but generates errors with ionic build –-prod . In Angular this particular error is
considered as ng run/serve run time cache issue and is related to the Ionic/Angu-
lar Lazy Loading mechanism (see “2.5 Routing and Lazy Loading”, starting from
page 36).
An indication of the solution is already given in the terminal text above. We need
to take the declaration and import of RequestPage to a higher level. Of course, we
should use the app level, app.module.ts , for that. And we should do the same for
MapPage.
We proceed as follows:
1. Remove the entries from MapPage and RequestPage in details.module.ts
2. Add entries to MapPage and RequestPage in app.module.ts
Let's start with details.module.ts and remove the bold formatted parts:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from
'@angular/forms';
import { Routes, RouterModule } from '@angular/router';
import { IonicModule } from '@ionic/angular';
import { DetailsPage } from './details.page';
import { RequestPage } from './../request/request.page';
import { MapPage } from './../map/map.page';
515
Ionic 5 • 12 Build, Deploy & Publish
@NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
IonicModule,
RouterModule.forChild(routes)
],
declarations: [DetailsPage, RequestPage, MapPage],
entryComponents: [RequestPage, MapPage]
})
export class DetailsPageModule {}
import { AboutComponent }
from './components/about/about.component';
516
Ionic 5 • 12 Build, Deploy & Publish
@NgModule({
declarations: [AppComponent, AboutComponent],
entryComponents: [AppComponent, AboutComponent,
RequestPage, MapPage],
imports: [
BrowserModule,
IonicModule.forRoot(),
AppRoutingModule,
HttpClientModule,
IonicStorageModule.forRoot(),
FormsModule,
ReactiveFormsModule,
RequestPageModule,
MapPageModule
],
providers: [
StatusBar,
SplashScreen,
Geolocation,
SocialSharing,
LocalNotifications,
{ provide: RouteReuseStrategy,
useClass: IonicRouteStrategy },
],
bootstrap: [AppComponent]
})
export class AppModule {}
Here we import both the pages and the modules of MapPage and RequestPage,
enter the pages as entryComponents and the modules as imports into @NgMod-
ule .
517
Ionic 5 • 12 Build, Deploy & Publish
We now see a successful, error free build process . And also ionic serve works
already fine after this little refactoring.
Now let's take a look at the www folder:
We've received a collection of more than two hundred small JS files. This has been
done with the help of Webpack (see “1.1 The idea behind Ionic”, section “Web-
Pack” on page 14). The JS files have been split into "chunks" which are only
loaded when needed. And when we look at one of them, it's easy to see what
compiling, concatenating and minifying have done:
(window.webpackJsonp=window.webpackJsonp||[]).push([[9],
{0:function(t,e,n){t.exports=n("zUnb")},"0AIG":function(t,e,n)
{"use strict";n.d(e,"a",(function(){return r}));const
r=(t,e,n,r,s)=>o(t[1],e[1],n[1],r[1],s).map(o=>i(t[0],e[0],n[0
],r[0],o)),i=(t,e,n,r,i)=>i*(3*e*Math.pow(i-1,2)+i*(-
3*n*i+3*n+r*i))-t*Math.pow(i-1,3),o=(t,e,n,r,i)=>s((r-=i)-
3*(n-=i)+3*(e-=i)-(t-=i),3*n-6*e+3*t,3*e-
3*t,t).filter(t=>t>=0&&t<=1),s=(t,e,n,r)=>{if(0===t)return((t,
e,n)=>{const r=e*e-4*t*n;return r<0?[]:[(-e+Math.sqrt(r))/
(2*t),(-e-Math.sqrt(r))/(2*t)]})(e,n,r);const i=(3*(n/=t)-
(e/=t)*e)/3,o=(2*e*e*e-
9*e*n+27*(r/=t))/27;if(0===i)return[Math.pow(-
o,1/3)];if(0===o)return[Math.sqrt(-i),-Math.sqrt(-i)];const
s=Math.pow(o/2,2)+Math.pow(i/3,3);if(0===s)return[Math.pow(o/2
,.5)-e/3];if(s>0)return[Math.pow(-o/2+Math.sqrt(s),1/3)-Math-
.pow(o/2+Math.sqrt(s),1/3)-e/3];const a=Math.sqrt(Math.pow(-
i/3,3)),l=Math.acos(-o/(2*Math.sqrt(Math.pow(-
i/3,3)))),u=2*Math.pow(a,1/3);return[u*Math.cos(l/3)-
518
Ionic 5 • 12 Build, Deploy & Publish
e/3,u*Math.cos((l+2*Math.PI)/3)-
e/3,u*Math.cos((l+4*Math.PI)/3)-e/3]}},"1M1q":function(t,e,n)
{"use strict";n.d(e,"a",(function(){return c}));var
r=n("mrSG"),i=n("U8oy"),o=n("LvDl"),s=n.n(o),a=n("8Y7J"),l=n("
IheW"),u=n("sZkV");let c=(()=>{class t{constructor(t,e,n)
{this.http=t,this.favService=e,this.loadingCtrl=n,this.baseUrl
="https://bob-tours-app.firebaseio.com/"}initialize(){return
r.a(this,void 0,void 0,(function*(){yield this.loadingCtr-
l.create({message:"Loading tour
data...",spinner:"crescent"}),yield
this.getRegions().then(t=>this.regions=t),yield this.getTour-
types().then(t=>this.tourtypes=s.a.sortBy(t,"Name")),yield
this.getTours().then(t=>{this.tours=s.a.sortBy(t,"Title"),this
.all_tours=s.a.sortBy(t,"Title"),this.filterTours({lower:80,up
per:400}),this.favService.initialize(this.all_tours)})}))}ge-
tRegions(){return this.http.get(`$
{this.baseUrl}/Regions.json`).toPromise()}getTourtypes(){re-
turn this.http.get(`$
{this.baseUrl}/Tourtypes.json`).toPromise()}getTours(){return
this.http.get(`${this.baseUrl}/Tours.json`).toPromise()}fil-
terTours(t){return this.tours=s.a.filter(this.all_tours,
(function(e){return
e.PriceG>=t.lower&&e.PriceG<=t.upper})),this.regions.forEach(t
=>{const e=s.a.filter(this.tours,
["Region",t.ID]);t.Count=e.length}),this.tourtypes.forEach(t=>
{const e=s.a.filter(this.tours,
["Tourtype",t.ID]);t.Count=e.length}),this.tours.length}}re-
turn t.ngInjectableDef=a.Pb({factory:function(){return new
t(a.Qb(l.c),a.Qb(i.a),a.Qb(u.Fb))},token:t,providedIn:"root"})
,t})()},"2QA8":function(t,e,n){"use strict";n.d(e,"a",(func-
tion(){return r}));const r=(()=>"function"==typeof Symbol?
Symbol("rxSubscriber"):"@@rxSubscriber_"+Math.random())
()},"2Vo4":function(t,e,n){"use strict";n.d(e,"a",(function()
{return o}));var r=n("XNiG"),i=n("9ppp");class o extends
r.a{constructor(t){super(),this._value=t}get value(){return
this.getValue()}_subscribe(t){const e=super._subscribe(t);re-
turn e&&!e.closed&&t.next(this._value),e}getValue())) [...]
The data volume of the entire www folder is now 15.3 MB after ionic build
--prod . For comparison: After a simple ionic build it is 46.9 MB.
519
Ionic 5 • 12 Build, Deploy & Publish
12.3 config.xml
Numerous aspects of the behavior of an app can be controlled through the global
configuration file config.xml . It also provides advanced Cordova API functions,
plug-ins and platform-specific settings.
The structure of the config.xml file as a platform-independent XML file was speci-
fied here in detail by the W3C under the title Packaged Web Apps (Widgets):
https://www.w3.org/TR/widgets/
Although the specification has been marked as obsolete since October 2018, it still
has significance for Ionic/Cordova projects. In that regard, reference should also be
made to the following document:
https://cordova.apache.org/docs/en/9.x/config_ref/index.html
Mandatory entries
You have to make the following adjustments in the config.xml of an Ionic project:
widget id
In the <widget> element, you create an App Reverse Domain ID in the id at-
tribute, which is a globally unique identifier for your app. By default, an Ionic app
has the ID io.ionic.starter . Change this into something like com.exam-
ple.myapp !
version
In the version attribute, you assign a full version number in major/minor/patch no-
tation (for example, 1.0.0 ). You need to count this version number up every time
you update to a Store.
name
In the <name> element, give the app a formal name, as it appears on the home
screen of a device and in the App Store interfaces, fore example BoB Tours .
description / author
The <description> and <author> elements provide metadata and contact infor-
mation that can be viewed in the App Store.
520
Ionic 5 • 12 Build, Deploy & Publish
Intent Whitelist
In the config.xml you'll find some allow-intent elements by default. It is ad-
vised to narrow this down based on each app's needs. Without any <allow-
intent> tags, no requests to external URLs are allowed.
Global Preferences
You can find some preference elements. Here you can set different default set-
tings for the app, such as the display duration of the SplashScreen (default is 3000
ms). Details can be found here:
https://cordova.apache.org/docs/en/9.x/config_ref/index.html
Platform elements
When using the CLI to build applications, it is sometimes necessary to specify pref-
erences or other elements specific to a particular platform. Use the element to
specify configuration that should only appear in a single platform-specific con-
fig.xml file. A multitude of standard values for icons and splash screens for
Android and iOS are already listed there.
521
Ionic 5 • 12 Build, Deploy & Publish
Of course, there are Photoshop templates with automated format output or similar.
With Ionic, that's all a thing of the past. Because the framework itself offers a termi-
nal command with which the creation of icons and splash screens for Android and
iOS can be done quickly.
https://ionic.andreas-dormann.de/resources/icon.png
https://ionic.andreas-dormann.de/resources/splash.png
You can copy these files into the project's resources folder.
For me that was 18 graphics for the target platform Android and 45 graphics for
iOS, which were generated from the two PNG files and then updated config.xm
to reflect the changes in the generated images.
522
Ionic 5 • 12 Build, Deploy & Publish
If you're testing our app in the emulator or on the device, you can be surprised by
our new icon and splash screen:
More informations about creating resources for Ionic apps you can find here:
https://ionicframework.com/docs/cli/commands/cordova-resources
523
Ionic 5 • 12 Build, Deploy & Publish
Similarly to the web build process (see “12.2 The web build process”, starting from
page 512), this command creates a new folder for Android in the platforms fold-
er.
We get more new folders and files. We are especially interested in the APK file:
524
Ionic 5 • 12 Build, Deploy & Publish
Production build
To get a release build we use additionally the --prod and -release options:
$ ionic cordova build android --prod --release
This will minify our app’s code as Ionic’s source and also remove any debugging
capabilities from the APK (short for: Android Package Kit). This is generally used
when deploying an app to the Google Play Store.
Hint: An alternative workflow is the use of Ionic's new Capacitor (see " B2. Ionic and
Capacitor", starting from page 565).
525
Ionic 5 • 12 Build, Deploy & Publish
You’ll first be prompted to create a password for the keystore. Then answer the
rest of the questions and when it’s all done, you should have a file called my-re-
lease-key.jks created in the current directory.
Make sure to save this file somewhere safe, if you lose it you won’t be able to sub-
mit updates to your app!
To sign the unsigned APK, we run the jarsigner tool which is also included in the
JDK:
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -key-
store my-release-key.jks app-release-unsigned.apk my-alias
This signs the APK in place. Finally, we need to run the zipalign tool to optimize
the APK. The zipalign tool can be found in /path/to/Android/sdk/build-
tools/VERSION/zipalign . For example, on OS X with Android Studio installed,
zipalign is in ~/Library/Android/sdk/build-tools/VERSION/zipalign:
To verify that our APK is signed we run apksigner . The apksigner can be also
found in the same path as the zipalign tool:
apksigner verify BobTours.apk
Now we have our final release binary called BobTours.apk and we can release this
on the Google Play Store!
526
Ionic 5 • 12 Build, Deploy & Publish
https://play.google.com/apps/publish/
If you want to publish a paid app or plan to sell in-app purchases, you need to
create a payments center profile, i.e. a merchant account. You can do that via Down-
load reports > Financial > Set up a merchant account . A merchant
account will let you you manage your app sales and monthly payouts, as well as
analyze your sales reports right in your Play Console.
527
Ionic 5 • 12 Build, Deploy & Publish
The information required for your store listing is divided into several categories:
Product details
There are three fields here that you need to fill out:
Your app’s title and description should be written with a great user experience in
mind. Use the right keywords, but don’t overdo it. Make sure your app doesn’t
come across as spam-y or promotional, or it will risk getting suspended.
Graphic assets
Under graphic assets, you can add screenshots, images, videos, promotional
graphics, and icons that showcase your app’s features and functionality. Some parts
under graphic assets are mandatory, like screenshots, a feature graphic, and a high
528
Ionic 5 • 12 Build, Deploy & Publish
resolution icon. Others are optional, but you can add them to make your app look
more attractive to users.
Here are some hints for creating the many graphical assets: Screenshots are easiest
to create from the Chrome DevTools (More options > Capture full size
screenshot ). Note the maximum aspect ratio of 2:1! A helpful tool for creating
icons is certainly Image Asset Studio, which is part of Android Studio.
Make sure that the collection of your screenshots provides a good overview of the
different features of your app. For our BoB Tours app I did it like this (using Dev -
Tools' Pixel 2 settings):
You can read more about the specific requirements for each graphic asset here:
https://play.google.com/apps/publish/
Categorization
This part requires you to select the appropriate type and category your app be-
longs to. From the drop-down menu, you can pick either Application or Game for
the application type. There are various categories for each type of app available on
529
Ionic 5 • 12 Build, Deploy & Publish
the Play Store. Pick the one your app fits into best. In order to rate your content,
you’ll need to upload an APK first. You can skip this step for later.
Contact Details
This part requires you to enter contact details to offer your customers access to
support regarding your app. You can add multiple contact channels here, like an
email, website, and phone number, but providing a contact email is mandatory for
publishing an app.
Privacy Policy
For apps that request access to sensitive user data or permissions, you need to en-
ter a comprehensive privacy policy that effectively discloses how your app collects,
uses, and shares that data.
You must add a URL linking to your privacy policy in your store listing and within
your app. Make sure the link is active and relevant to your app.
You’re now done with the store listing. Go ahead and click on Save Draft to save
your details. You can always skip some steps and come back to them later before
you publish your app.
Here, you need to select the type of release you want to upload your first app
version to. You can choose between
• an internal test track
• a closed track
• an open track
• a production release.
The first three releases allow you to test out your app among a selected group of
users before you make it go live for everyone to access. This is a safer option be-
530
Ionic 5 • 12 Build, Deploy & Publish
cause you can analyze the test results and optimize or fix your app accordingly if
you need to before rolling it out to all users.
However, if you create a production release, your uploaded app version will be-
come accessible to everyone in the countries you choose to distribute it in.
Once you’ve picked an option, click on Manage and then Create release .
Next, follow the on-screen instructions to add your APK files, and name and de-
scribe your release.
531
Ionic 5 • 12 Build, Deploy & Publish
Next, click on Review to be taken to the Review and rollout release screen.
Here, you can see if there are any issues or warnings you might have missed out
on.
Finally, select Confirm rollout . This will publish your app to all users in your tar-
get countries on Google Play.
More about publishing an Android app on Google Play you can read here:
https://support.google.com/googleplay/android-developer
532
Ionic 5 • 12 Build, Deploy & Publish
Similarly to the web build process (see “12.2 The web build process”, starting from
page 512), this command creates a new folder for iOS in the platforms folder. It
takes a few minutes to install and configure the iOS platform.
From here, you can open the .xcworkspace file in ./platforms/ios/ to start
Xcode and debug the app within this IDE.
533
Ionic 5 • 12 Build, Deploy & Publish
Then you use an older version of Xcode and should update to version 9.0.0 or
greater, as this message says.
Production build
To get a release build we use additionally the --prod option:
$ ionic cordova build ios --prod
This will generate the minified code for the web portion of an app and copy it over
the iOS code base.
From here, you can open the .xcworkspace file in ./platforms/ios/ to start the
app in Xcode to prepare it for publication. For details read the next division.
Hint: An alternative workflow is the use of Ionic's new Capacitor (see " B2. Ionic and
Capacitor", starting from page 565).
534
Ionic 5 • 12 Build, Deploy & Publish
More informations about Apple's developer memberships you can find here:
https://developer.apple.com/de/support/compare-memberships
535
Ionic 5 • 12 Build, Deploy & Publish
Click on Manage Certificates..., then on the + button in the lower left corner
and then click on iOS Distribution . You can repeat this step for iOS Develop-
ment for the dev build of your app.
After finishing these step you can close this Xcode dialog.
536
Ionic 5 • 12 Build, Deploy & Publish
App Information
In App Store Connect, from the upper menu select My Apps .
In the My Apps area click the + button on the top left corner to add a new entry.
As platform select iOS . Type in the Name for the app as it will appear on the App
Store. This can't be longer than 30 characters. Choose the Primary Language . If
localized app information isn’t available in an App Store territory, the information
from your primary language will be used instead. More informations about localiz-
ing can read here:
https://help.apple.com/app-store-connect/#/deve6f78a8e2
Select your app's Bundle ID (see step 3). Type in a SKU (Stock Keeping Unit). It's a
unique ID for your app that isn't visible on the App Store. Maybe you use the
Bundle ID again.
Click Create . On the next page you can correct the entries, if necessary.
537
Ionic 5 • 12 Build, Deploy & Publish
A word to the app's rating: Based on your application's content and functionality, it
is given a rating. This rating isn't only useful for telling users about your application's
content and features, but is also used by the operating system for the parental
controls features.
It's strongly recommended that you don't try to outsmart the rating system. Apple
is well aware of this strategy and will reject your application if it doesn't agree with
the rating that you have set. There are many other things here that you may need
to adjust based on your app, but we won't go over them since they are pretty self-
explanatory.
The information that you enter in this step can be modified once your application
is live in the App Store. In other words, you can change the price and availability of
an application without having to submit an update.
538
Ionic 5 • 12 Build, Deploy & Publish
Here you add app previews and screenshots. Screenshots must be in the JPG or
PNG format, and in the RGB color space. App previews (videos) must be in the
M4V , MP4 , or MOV format and can’t exceed 500 MB.
Hint: Starting March 2019, all new apps and app updates for iPhone, including
universal apps, will require iPhone 11/Pro and XS/XR Max screenshots.
Here you can read more about the specifications for all screenshot and preview
formats:
https://help.apple.com/app-store-connect/#/devd274dd925
https://help.apple.com/app-store-connect/#/dev4e413fcb8
539
Ionic 5 • 12 Build, Deploy & Publish
Fill in all other informations, such as Promotional Text , Description and Key-
words .
You must also provide an App Store icon, which is used to represent your app in
different sections of the App Store. Follow the Human Interface Guidelines when
creating your App Store icon:
https://developer.apple.com/design/human-interface-guidelines/
The individual options are briefly explained on the page. For testing you can use
iOS App Development (without registration of the devices) or Ad Hoc (with registra-
tion of a limited number of test devices). For a publication in the store, of course,
fits the option AppStore .
540
Ionic 5 • 12 Build, Deploy & Publish
Click Continue .
Choose Product > Archive . If the archive builds successfully, it appears in the Ar-
chives organizer . (To open the Archives organizer , choose Window >
Organizer and click Archives .)
541
Ionic 5 • 12 Build, Deploy & Publish
Click Next and select Upload (to send the app to the App Store Connect) or Ex-
port (to sign and export the app without uploading). Click Next again and follow
the further instructions.
If the submission process went without problems, your application's status will
change to Waiting for Review . It takes several days for Apple to review your
app, and the time it takes tends to fluctuate over time.
Conclusion
The submission process is quite lengthy for a new application, but submitting an
update to the App Store is much less cumbersome. Keep in mind that the submis-
sion process is much more involved if your application is localized in various
languages as your application's metadata needs to be localized as well. However,
localizing your application is well worth the effort as it often results in higher sales
and positive customer feedback.
542
Ionic 5 • 12 Build, Deploy & Publish
https://electronjs.org/
Electron enables you to create desktop applications with pure JavaScript by pro-
viding a runtime with rich native (operating system) APIs. You could see it as a
variant of the Node.js runtime that is focused on desktop applications instead of
web servers.
543
Ionic 5 • 12 Build, Deploy & Publish
This doesn't mean Electron is a JavaScript binding to graphical user interface (GUI)
libraries. Instead, Electron uses web pages as its GUI, so you could also see it as a
minimal Chromium browser, controlled by JavaScript.
https://electronjs.org/docs/tutorial/first-app
MacOS App
Requirements
There are two hard requirements for publishing an app on the macOS app store
Publishing
The Electron team has a detailed guide on how to publish an app for macOS.
Please review the docs here:
https://electronjs.org/docs/tutorial/mac-app-store-submission-guide
Windows App
Requirements
There are two hard requirements for publishing an app on the Windows app store
544
Ionic 5 • 12 Build, Deploy & Publish
Publishing
Like MacOS, Electron has a detailed guide on how to publish an app for Windows.
Please review the docs here:
https://electronjs.org/docs/tutorial/windows-store-guide
https://capacitor.ionicframework.com/docs/
Capacitor is a cross-platform app runtime that makes it easy to build web apps
that run natively on iOS, Android, Electron, and the web. It was created by the Ion-
ic team. They call these apps "Native Progressive Web Apps" and want them to
represent the next evolution beyond Hybrid apps.
Capacitor has preliminary support for Electron. At the time of going to print this
book, Electron support for Capacitor was in preview, and lagged behind iOS,
Android, and Web support.
I recommend to read the documentation of the Ionic team here:
https://capacitor.ionicframework.com/docs/electron
545
Ionic 5 • 12 Build, Deploy & Publish
The concept has existed since 2003, but only in recent years, thanks to the latest
standards in web development, are Progressive Web Apps actually being used -
and growing!
Especially for someone who doesn't come from the field of web development or
programming, it can be difficult to understand the functionality and meaning be-
hind a Progressive Web App (PWA). Therefore, in the following I'll try to explain
everything about the PWA, its functions and its advantages as simply as possible.
• Installable: PWAs can be installed with one or two clicks on cell phones, tablets
or desktops, making them work (almost) like normal apps.
• Offline: PWAs also work offline. Even if you don't have internet, you can easily
work with the PWA. The PWA doesn't run like other web apps on the server,
but directly on your device.
• Faster: The PWA loads everything or almost everything that it needs to work,
the very first time you call or install it. Thus, it can be loaded much faster for any
further use. The data that needs to be retrieved from the Internet and that can't
be stored is retrieved in the background so that the user can continue working
while the data is being synchronized.
546
Ionic 5 • 12 Build, Deploy & Publish
The fact that a PWA is executed in the browser has some consequences. For ex-
ample, there are often limits to the amount of disk space you can use on the
device, or there are limits to what features of the device you can access.
With better standards and developments in web and app development, the differ-
ences between PWAs and apps are shrinking. Nowadays, many apps are no
longer "normal" apps, but so-called "apps", which are ultimately executed in the
browser, but in a different way so that they don't have such limitations as a PWA.
PWAs only need a browser for them to work, since they are ultimately websites.
They use only "web technologies". They have certain restrictions on what they can
do with the device and can't be installed through the App Store.
Hybrid apps, on the other hand, are websites that are programmed in combination
with certain native app technologies and combined in one app. They use "web and
native app technologies" and are thus hardly distinguishable from normal apps.
They barely have any restrictions on what they can do on the device they're in-
stalled on and can be installed through their app store.
An overview of the statistics clearly shows why PWAs are the best option for cer-
tain projects. According to a comScore Mobile Metrix study in 2017, tablet and
mobile users spend 87% of their time on apps and 13% on websites. This means
that users clearly prefer apps to websites. The problem is, that statistics also show
547
Ionic 5 • 12 Build, Deploy & Publish
that 80% of the time people spend on apps, they spend with the top 3 apps.
Likewise, on average, each user installs almost no new apps a month, but visits
over 100 websites.
Here's what we can conclude: Users want an app, but it's hard to get them to in -
stall it. Therefore, a PWA is the absolute right solution! They may not be able to
search and install the App in the App Store, but they will definitely visit your web-
site shortly. And then, as soon as they visit them, the browser shows a button to
install the app or PWA. With just a quick visit to your website and with no more
than 1 or 2 clicks, the user already has the app!
And even if the users don't install the app, the PWA will be stored in the browser's
cache, so everything will go super fast for future visits. And here I come to other
statistics: According to a study by Google 40% of users leave sites that take more
than 3 seconds to load before they are fully loaded.
• Google is very strong in the use of PWAs with many tutorials and open source
docs to support the developers:
https://developers.google.com/web/progressive-web-apps/
• In the meantime, Microsoft has made it possible to install PWAs under
Windows 10 directly via the Microsoft Store, but it also supports developers
with its own tools:
https://developer.microsoft.com/en-us/windows/pwa
• And last but not least, Apple has silently added basic support for PWAs on iOS:
https://brainhub.eu/blog/pwa-on-ios-13/
Also check out Ionic's PWA overview for more info:
https://ionicframework.com/pwa
548
Ionic 5 • 12 Build, Deploy & Publish
Congrats! You now have a high performing, great looking PWA starter app that
scores 100 on Lighthouse, right out-of-the-box!
Once this package has been added run ionic build --prod and the www directo-
ry will be ready to deploy as a PWA.
Note: By default, the @angular/pwa package comes with Angular logo for the app
icons. Be sure to update the manifest to use the correct app name and also replace
the icons.
If an app is being deployed to other channels such as Cordova or Electron, you
can remove the "serviceWorker": true flag from the angular.json file. The
service worker can be generated though by running:
$ ionic build --prod --service-worker
Note: Features like Service Workers and many JavaScript APIs (such as geolocation)
require the app be hosted in a secure context. When deploying an app through a
hosting service, be aware that HTTPS will be required to take full advantage of Ser
vice Workers.
549
Ionic 5 • 12 Build, Deploy & Publish
The Firebase CLI provides a variety of tools for managing, viewing, and deploying
to Firebase projects.
With the Firebase CLI installed run
$ firebase init
in the project. This will generate a firebase.json config file and configure the app
for deployment.
Note: firebase init will present a few questions, including one about redirecting
URLs to /index.html. Make sure to choose yes for this option, but no to over-
writing your index.html . This will ensure that routing, hard reload, and deep
linking work in the app.
550
Ionic 5 • 12 Build, Deploy & Publish
The last thing needed is to make sure caching headers are being set correctly. To
do this, add the following snippet to the firebase.json file to the hosting proper-
ty:
"headers": [
{
"source": "/build/app/**",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000"
}
]
},
{
"source": "sw.js",
"headers": [
{
"key": "Cache-Control",
"value": "no-cache"
}
]
}
]
For more information about the Firebase CLI and all firebase.json properties,
see the Firebase documentation:
https://firebase.google.com/docs/cli
https://firebase.google.com/docs/hosting/full-config#section-firebase-json
551
Ionic 5 • 12 Build, Deploy & Publish
Also note that only client-side code is visible to your users. All the logic of an appli-
cation which is server-side won't be exposed.
552
Ionic 5 • 12 Build, Deploy & Publish
553
Ionic 5 • 12 Build, Deploy & Publish
The following pages walks you through the process of setting up your application
with Ionic Appflow (free Starter version), including how to connect your application
to Appflow, how to set up the Appflow SDK (Deploy plugin) for live updates, and
how to configure your first automated Android and iOS builds using native build
environments.
I describe the whole thing using our already created BoB Tours app.
What we'll do:
https://dashboard.ionicframework.com/login
Switch to the Apps section. Click Create a New App (on the bottom – you'll only
see this button, if you don't have any apps yet) or Add App (on the right side).
Enter a name for your app and click Create app (if you want to create a blank
Ionic Hub app) or Link an existing Ionic app instead . I click the latter here.
Now we'll need to decide how Appflow will access your source code. The Ionic
team suggests using GitHub, Bitbucket, or Bitbucket Server if you're using them al-
ready. The integration with these services are easy to configure and you'll get some
554
Ionic 5 • 12 Build, Deploy & Publish
additional benefits like being able to view the commits your builds came from and
the changes on your Git remote.
If you're not using one of these providers, you can also choose to push directly to
Ionic. But: Ionic git isn't intended to be a Git host and the Ionic team strongly sug -
gests using GitHub, Bitbucket or another official git hosting service to backup your
source code repository.
To keep it simple here, I use GitHub. There I've already created a repository for
the BoB Tours app (you'll find many instructions on how to create a GitHub reposi-
tory on the web).
I navigate to Settings > Git > GitHub on the Appflow Dashboard. If this is your
first time connecting (like me) you'll need to click the Connect to GitHub button.
Then I choose the repository to link from the list of available repos.
Linking the repository creates a web-hook for the repository and sends events to
Appflow. So Appflow has access to any commits that are pushed to this repository.
Here you can read more about connecting your repo:
https://ionicframework.com/docs/appflow/quickstart/connect
555
Ionic 5 • 12 Build, Deploy & Publish
In the next dialog I click Install Instructions and choose one of the offered
update options for my app:
556
Ionic 5 • 12 Build, Deploy & Publish
I copy the code form the dialog and run it in terminal in my apps directory.
$ ionic deploy add \
$ --app-id="MY_APP_ID" \
$ --channel-name="Production" \
$ --update-method="background"
--channel-name is the name of the Channel you'd like the app to listen to for up-
dates.
https://ionicframework.com/docs/appflow/deploy/api#plugin-variables
Now I've installed the plugin and commit the changes to config.xml and pack-
age.json :
$ git add .
$ git commit -m "Added Appflow SDK"
Here you can read more about installing the Appflow SDK:
https://ionicframework.com/docs/appflow/quickstart/installation
3. Commit to Appflow
In order for Appflow to access your code base you'll need to push a commit so
that it shows up in your Dashboard.
If you are using an integration with GitHub, Bitbucket or Bitbucket Server, a new
commit will show up every time you push to your git host.
If you are using Ionic as your git remote, you'll need to push to your commit to
Appflow directly to see your commit in the Dashboard.
557
Ionic 5 • 12 Build, Deploy & Publish
Once you've done a git push , you should see your commit available in the Com-
mits tab of the Dashboard:
To do this, in the Commit tab click the Start web build icon:
We can pick a Channel to automatically assign the build to once it completes suc-
cessfully. I leave this option blank now.
Once the build begins we'll be able to watch it's progress and look at the logs if we
encounter errors. If you have trouble getting a successful build in this step, here
you can find answers to common Deploy build errors:
https://ionic.zendesk.com/hc/en-us/categories/360000410494-Appflow-
Builds
558
Ionic 5 • 12 Build, Deploy & Publish
My Deploy was, fortunately, successfully built. Now I can assign it to the Produc-
tion channel (I configured the Appflow SDK for; see step 2) by clicking Assign to
channel (in the top right corner).
For an application to receive a live update from Deploy, we'll need to run the app
on a device or an emulator. The easiest way to do this is simply to use the ionic
cordova run command to launch a local app in an emulator or a device connect-
ed to your computer.
If the app is configured correctly to listen to the channel you deployed it to, the
application should update immediately on startup if you're using the auto update
method.
If you're using the background update method, just stay in the app for 30 seconds
or so while the update is downloaded in the background. Then, close the applica-
tion, reopen it again and you should see your update applied!
Here you can read more about deploying a Live Update:
https://ionicframework.com/docs/appflow/quickstart/deploy
Conclusion
Here ends our entry into Ionic Appflow.
Of course, the platform has a lot more to offer, such as Building Native Binaries
and Automation. But the advanced features are reserved for an Add-On ($49/
month) or higher versions like Growth ($120/month). For businesses that need to
manage multiple apps and regularly publish updates, it can be a good investment.
The Ionic team keeps its promise with Appflow, to help development teams contin-
uously build and ship their iOS, Android, and web apps faster than ever.
The excellent documentation for Ionic Appflow is also not objectionable:
https://ionicframework.com/docs/appflow
559
Ionic 5 • 12 Build, Deploy & Publish
Summary
In this chapter you've learned a lot again:
You've created different builds to deploy and publish apps for iOS, Android, Desk-
top (with Electron) and PWAs for the Web and got to know the difference
between Debugging and Production Builds.
You have learned that the config.xml plays an important role.
You've created icons and splash screens in all required resolutions for the various
platforms.
You've got an overview of the publishing workflows for Google Play and App
Store.
Finally, you got to know and use Ionic's new DevOps and Continuous Delivery
platform Appflow.
560
Ionic 5 • Bonus: Ionic and other frameworks
Web Components
Ionic has achieved its independence from frameworks by rebuilding its compo-
nents as web components. It's a W3C specification that is supported by nearly all
modern browsers like Google Chrome, Mozilla Firefox, Microsoft Edge, Safari and
Opera..
The goal of web components is to build reusable components for the Web. So that
they are cleanly reusable, the focus is primarily on encapsulation. Web Compo-
nents primarily consists of three Web APIs to achieve these goals:
• Custom Elements
• Shadow DOM
• HTML Templates
With Custom Elements, web developers can create new HTML tags, beef-up exist-
ing HTML tags, or extend the components other developers have authored.
Shadow DOM is a functionality that allows the web browser to render DOM ele-
ments without putting them into the main document DOM tree. This makes it
possible to attach a parallel and hidden DOM tree to any element. This creates a
high encapsulation between this Shadow DOM and the actual DOM.
HTML Template is a way to insert chunks of HTML that are stamped at will.
Ionic's web components were built with Stencil, a very cool toolchain for creating
web components and PWAs developed by the Ionic team:
https://stenciljs.com/
561
Ionic 5 • Bonus: Ionic and other frameworks
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<script>
hello = async function () {
562
Ionic 5 • Bonus: Ionic and other frameworks
alert('Hello World!');
};
</script>
<ion-app>
<ion-content text-center>
</ion-app>
</body>
</html>
Within the head section of the index.html file we do all the imports from cdn.js-
delivr.net to be able to use Ionic, its stylesheet and even the icons.
Within the body part we first create a simple JavaScript function to call an alert .
Then we declare an Ionic app using the <ion-app> element. Within this element
we place the <ion-content> area. We can use Ionic's CSS Utilities (see “8.4 CSS
Utilities”, starting from page 350) to center all the content.
And because we have also imported the Ionicons (see “6.12 Icons”, starting from
page 200), we can spend the button a nice hand icon next to the caption.
563
Ionic 5 • Bonus: Ionic and other frameworks
Here is our little Ionic web app without any framework in action:
You see that the application looks the same as if we had set it up within an Angu-
lar environment – we’ve just embedded the web components directly into the
index.html file.
564
Ionic 5 • Bonus: Ionic and other frameworks
Such a reliable partner exists since 2008 and belongs to the name Cordova (see
“1 Introduction”, section “Cordova” on page 9 and also “9 Ionic Native”, starting
from page 393).
But native platforms continue to evolve faster and faster. Whether it's new OS ver-
sions of iOS and Android, new modern plugin systems like CocoaPods and Gradle
or simply new hardware. Cordova sometimes has problems to come along.
With Capacitor, the Ionic team claims to have the “spiritual successor to Cordova”
that makes use of all the new modern native tools. Ionic calls such apps built with
Capacitor “Native Progressive Web Apps” and in their opinion they “represent the
565
Ionic 5 • Bonus: Ionic and other frameworks
next evolution beyond hybrid apps”. A statement that makes one sit up. And that's
why I decided to take a closer look at Capacitor in this book.
Layer 1 contains your code, written in HTML, CSS and JavaScript, usually using a
JS framework like Angular, React or Vue.
Layer 2 makes up the set of UI Controls, in our case, of course, Ionic.
Layer 3 is the job of Capacitor. It containerizes your web app and puts it into a
managed native WebView (if running natively), then it exposes native functionality
to your web app in a cross-platform way. Capacitor then provides a simple way to
expose custom native functionality to your web app (through plugins).
Layer 4 are the distribution platforms.
566
Ionic 5 • Bonus: Ionic and other frameworks
Explanations to these topics can be found in the following video post by Matt
Netkow, Head of Developer Relations at the Ionic team:
https://youtu.be/tDW2C6rcH6M
567
Ionic 5 • Bonus: Ionic and other frameworks
Capacitor in action
We will now dare to migrate our BoB Tours app to Capacitor!
For this we open our current project (from chapter 12) in Visual Studio Code. In
the terminal we enter the following:
$ ionic integrations enable capacitor
where appName is the name of our app, and appId is the domain identifier of our
app. For my app I type
$ npx cap init “BoB Tours” de.dormann.bobtours
Note: npx is a utility available in npm 5 or above that executes local binaries/scripts
to avoid global installs.
568
Ionic 5 • Bonus: Ionic and other frameworks
Use the native IDEs to change these properties after initial configuration.
We must build our Ionic project at least once before adding any native platforms.
$ ionic build
This creates the www folder that Capacitor has been automatically configured to use
as the webDir in capacitor.config.json.
As the very first success message after the integrations instruction reveals, we
can now add, for example, the Android platform with the following terminal com-
mand:
$ npx cap add android
This installs the Android dependencies and a native Android project directly in the
root of a project. These separate native project artifacts should be considered part
of your Ionic app (i.e., check them into source control, edit them in their own IDEs,
etc.).
In the same way you can create a native iOS project with
$ npx cap add ios
569
Ionic 5 • Bonus: Ionic and other frameworks
Note: Make sure you update CocoaPods using pod repo update before starting a
new project, if you plan on building for iOS using a Mac.
Our Cordova plugins for Keyboard, WebView Splash Screen and the Status Bar
are no longer needed (and are therefore skipped) because the Capacitor app uses
their native equivalents.
https://capacitor.ionicframework.com/docs/cordova/using-cordova-plugins/
570
Ionic 5 • Bonus: Ionic and other frameworks
https://capacitor.ionicframework.com/docs/cordova/
migrating-from-cordova-to-capacitor
Well documented
As we know it from the Ionic team, Capacitor is well documented. It includes basics
about the development workflow, building and running your app, guides about an
Ionic Framework Camera App, Firebase Push Notifications, a variety of community
guides and all important platform-specific stuff. You can find the entry point to the
documentation here:
https://capacitor.ionicframework.com/docs
571
Ionic 5 • Bonus: Ionic and other frameworks
Conclusion
Capacitor was released by the Ionic team in May 2019 with the ambition to be-
come a modern successor to Cordova and the new official container for every new
Ionic app. The work on it has already started in 2017. And it was worth it.
Meanwhile the Ionic team has developed numerous APIs: Accessibility, Back-
ground Task, Camera, Clipboard, Console, Device, Filesystem, Geolocation, Haptics,
Local Notifications, Modal, Motion, Network, Push Notification, Share, Splash
Screen, Status Bar, Storage , Toast.
And if that weren't enough, the community until now has contributed more than
30 (!) other APIs: AdMob, Data Storage SQLite, DatePicker, Downloader, Email,
Facebook Login, Fancy Geo, Fancy Notifications, Filesharer, Fingerprint Auth, Fire-
base Analysis, Firebase Auth, Fused Location, Geofende Tracker, Google Sign-In,
Heartland Form, Image Cache, Image Crop, Intercom, Media Operations, NFC,
oAuth2, Single SignOn, SMS, Twilio iOS, Twitter Kit, Video Player, Video Recorder,
YouTube, Zebra, Zip.
So there's almost no app that you can't do with Ionic and Capacitor.
With Capacitor Ionic’s mission expanded to helping teams build great apps every-
where. And I say: Mission succeeded!
572
Ionic 5 • Bonus: Ionic and other frameworks
In fact, the only timeless aspect that can be compared is the paradigm underlying
the two technologies: why do they each work the way they do it, and what does
that mean for the users' productivity? So the following is an attempt to contrast
React and Angular with their core concepts to provide the basis for an informed
decision.
573
Ionic 5 • Bonus: Ionic and other frameworks
React originally used the Apache 2.0 license with an additional patent clause, which
led to some ambiguity. In 2017, Facebook changed the license from React and
has since used the MIT license. At the beginning React used the version numbers
0.1 to 0.14, but then Facebook changed to 15.x. The current version line is 16.x.
Known React users are Airbnb, Asana, Atlassian, Codecademy, Dropbox, Face-
book, Instagram, Intercom, Netflix, Microsoft, The New York Times, WhatsApp and
Yahoo.
Angular saw the light of day three years later, in 2016, and was originally devel-
oped by Google. In fact, his story started six years earlier, in 2010, with a previous
framework called AngularJS, but the similarity ends in the name. The concepts of
Angular and AngularJS are quite different.
The framework uses the MIT license, and therefore the code was released as open
source. It can also be found on GitHub:
https://github.com/angular
Since AngularJS used versions 1.x, Angular started with number 2. Version 3 was
skipped, but since version 4, Google is simply increasing the version number on
each release. The current version is 7.
Some of the more popular users of Angular are AWS, Google, Freelancers, iStock-
photo, Nike, PayPal, Telegram, Udemy, Upwork, Weather and YouTube.
Scope
React is a library, not a framework. That means it's just a small building block for
an application that you need to assemble and combine with many other low-level
574
Ionic 5 • Bonus: Ionic and other frameworks
building blocks. The library is only responsible for the V in MVC, which means that
it only takes care of the efficient rendering of a user interface.
There are only a few guidelines on how a React application should be structured
or what additional services should be used. While this allows for a great deal of
flexibility, it also means that you must first assemble a technology stack before you
can start working on an application. On the other hand, if you want React to be re-
placed with something else at a later date, it's very easy because it doesn't have
more than a dozen aspects attached to React. You could say React is like Lego for
adults.
Angular is a framework, not a library. It includes many services besides the actual
view management, such as dependency injection, routing, validation, an HTTP
client, and so on.
MV*
React uses the Model View Controller (MVC) pattern, Angular the Model View
ViewModel (MVVM) pattern.
React and Angular have different concepts for managing an UI. They both follow
an MV* pattern, but they ultimately use different variants.
React is committed to the classic MVC pattern and, as shown above, only looks af-
ter the V of MVC. It offers only one-sided data mining (referred to in React as
“unidirectional data flow”). This means that React always controls the view. Changes
made by the user must be explicitly performed via React before the view can be
updated.
This makes UI changes very predictable and deterministic, and it also makes their
behavior easy to understand: the UI always represents the state dictated by the
React application, and the only one allowed to change that state is the React appli-
cation.
575
Ionic 5 • Bonus: Ionic and other frameworks
In contrast, Angular follows the design pattern MVVM and provides the state of the
view as a so-called ViewModel. While changes to the ViewModel change the inter-
face, the reverse is true. This means that there is more than one way to change the
state of the application: either programmatically or through user actions.
This makes UI changes difficult to predict and, in a sense, nondeterministic. In addi-
tion, this bidirectional data binding approach leads to situations with subtle nuances
in state management, for example when the user clicks on a checkbox and runs an
onCheck() handler: If you now access the value of the checkbox, it will contain the
previous one or the new value? This makes it more difficult to think about UIs de-
veloped in Angular than in React.
DOM
React uses a virtual DOM for runtime, while Angular directly accesses the DOM.
As changes are made to the state of the application, React and Angular must
somehow apply these changes to the DOM displayed in the browser.
React uses a virtual DOM and manages a copy of the current DOM in RAM,
which it can access much faster than the actual DOM. Any changes to the state
that cause re-rendering will go to the virtual DOM first. The library then transfers
only changes in the virtual DOM to the current DOM in the browser. This makes
updating the user interface extremely fast because React only needs to update
those parts that have actually changed.
Angular accesses the DOM directly. Thus, if something has changed in the state of
the application, the framework must either re-render the entire DOM (which is
slow) or search the DOM for the component to be updated (which is also slow).
Angular's approach is slower than that of React. And so Angular is less able to
handle complex masks that may contain thousands of controls.
Paradigms
React and Angular use different paradigms, which leads to a different way the de-
veloper can handle them. React uses the functional paradigm, while Angular works
with the object-oriented paradigm.
576
Ionic 5 • Bonus: Ionic and other frameworks
As described, React manages a virtual DOM. For React, it's important to be able to
quickly find and compare elements in it. React uses the concept of immutability, so
it only needs to compare references and not (nested) values.
However, this means that the developer must know the difference between the
stack and the heap, and between value and reference types, as well as know how
to update them. Strictly speaking, “update” is the wrong term, because existing val-
ues in React are always replaced by new ones because of the aforementioned
immutability.
Immutability, along with the unidirectional flow of data, is a concept of functional
programming and shows very clearly where React has its roots.
Syntax
React uses primarily JavaScript native language features, Angular also introduces
proprietary elements.
Angular and React have very different ideas about how to syntactically write code.
React uses JSX, which extends JavaScript with an XML data type. This way you
can also use HTML in JavaScript, which becomes a part of the JavaScript code
through JSX. Along with approaches like JSS (CSS in JavaScript), this results in a
single JSX file containing the structure, styles, and behaviors of a component.
Because JSX is basically JavaScript, you can use all the tools that the JavaScript
ecosystem knows about, including testing, “lints”, and distributing components over
npm .
577
Ionic 5 • Bonus: Ionic and other frameworks
It also means that existing JavaScript knowledge can be reused and you don't
have to learn any additional concepts other than JSX syntax. For example, anyone
who knows how to write a loop in JavaScript knows how to formulate a loop in
JSX. This makes getting into JSX very easy.
In contrast, Angular emphasizes HTML and enriches HTML with its own elements
and attributes. For example, if you want to loop through Angular, you need to
know the Angular construct for loops, which is available in the form of its own *ng-
For attribute (see “2 Angular Essentials”, section “2.7 *ngFor”, starting from page
40). This means that the developer can't reuse familiar things, but must re-learn
them for Angular in an Angular-specific way. This makes getting into Angular hard-
er than getting into React.
In addition, Angular uses TypeScript, a superset of JavaScript that fits well in the
Angular object-oriented paradigm. This is for those who already work with Type-
Script - but for everyone else it's an additional hurdle to take.
Native applications
React and Angular both have concepts on how to build native applications.
From time to time you don't need a web application, but a native application is
sufficient. Both React and Angular have solutions.
React offers an implementation called React Native that lets you write native appli-
cations for iOS and Android using the library and JavaScript.
Angular also uses a similar approach with Native Script, which can also be used to
write native applications for iOS and Android using Angular and JavaScript.
Both React and Angular receive regular updates, but the way they treat each up-
date is very different. React updates are usually backward compatible, while
Angular updates are not compatible.
For example, Facebook is investing heavily to communicate legacy features early
and to offer upgrade paths that sometimes even have automated tools. Updates
are backward compatible with most aspects. This means that updating an applica-
tion from one version of React to another is usually painless and can occur in a
timely manner.
578
Ionic 5 • Bonus: Ionic and other frameworks
In contrast, new versions of Angular are usually incompatible with older ones and
contain many major changes. This means that an application will remain with a cer-
tain Angular version over time, or the programmer will have to put some effort
into updating each time a new Angular version is released. This makes updates - in
connection with the fact that Angular code is usually more extensive than React
code – annoying.
Use
After comparing concepts and paradigms up to now, one important aspect is the
use of React and Angular. To get an honest and generally accepted impression of
how the technologies are being used, there are several useful surveys. Let's take a
closer look at the survey "The state of JavaScript 2018":
https://2018.stateofjs.com/front-end-frameworks/overview/
The results for React indicate that the popularity of React has increased over time.
64.8 percent of the developers who participated in the survey used React and
would use it again. 19.1 percent want to learn it; 9.2 percent are not interested in
learning React. 6.7 percent have used the library before but would not do it again.
The results for Angular show that only 23.9 percent of the developers who partici-
pated in the survey and used Angular would use Angular again (again for
comparison: the corresponding value for React is 64.8 percent). Even more im-
pressively, a third (33.8 percent) of the developers who used Angular would not
use it again (6.7 percent at React). The proportion of developers interested in An-
gular has fallen from 39.7 percent in 2016 to just 10.4 percent in 2018. If you
put it all together, Angular seems to be on the decline. Today.
579
Ionic 5 • Bonus: Ionic and other frameworks
The country-specific development is interesting. When used per country shows that
the world can be arranged by technology: the US, Scandinavia, China and Australia
prefer React, Europe and South America prefer Angular.
In it's conclusion the survey says:
“Once again the frontend space is all about React and Vue.js. Vue's story in particu
lar is worth considering: two years ago, 27% of respondents had never even heard
of the library. Today, that fraction has fallen to just 1.3%! So while React still has a
much larger share of the market, Vue's meteoric rise certainly shows no sign of stop
ping. In fact, Vue has already overtaken its rival for certain metrics such as total
GitHub stars.
The other story of those past couple years is the fall of Angular. While it still ranks
very high in terms of raw usage, it has a fairly disappointing 41% satisfaction ratio.
So while it probably isn't going anywhere thanks to its large user base, it's hard to
see how it will ever regain its place atop the frontend throne.
[..]
Update: many people have pointed out that Angular's poor satisfaction ratio is prob
ably in part due to the confusion between Angular and the older, deprecated
AngularJS (previous surveys avoided this issue by featuring both as separate items).
So while Angular did “fall” –relatively speaking– from its dominance from a few
years back, it might very well regain ground once the dust clears.”
580
Ionic 5 • Bonus: Ionic and other frameworks
Summary
When comparing React and Angular, it makes sense not to focus on their functions
as features can change very quickly over time. Instead, it makes more sense to
compare React and Angular from a conceptual point of view, and many differ-
ences can be made here.
The main difference is that React follows the functional approach with all its conse-
quences, and Angular follows the object-oriented approach. While object-oriented
programming certainly had its time, it continues to struggle with modern demands
such as automatically parallelized code.
Because React is based on functional concepts, it offers better performance in
terms of speed, usability and maintainability. In addition, React is lighter weight than
Angular and also results in much slimmer code. Besides, it doesn't enforce the use
of TypeScript. Angular, on the other hand, is practically useless if you try to use it
without TypeScript (which, as described, has its own complexity and involves a cer-
tain amount of effort).
581
Ionic 5 • Bonus: Ionic and other frameworks
Now we can create a blank React project with Ionic and all its dependencies with:
$ ionic start myReactIonicApp blank --type=react
$ cd myReactIonicApp
This may take several minutes. After finishing this process we can run the app as
usual with:
$ ionic serve
The first three lines are pulling in some dependencies. The first being React itself.
This allows us to write components in an HTML-like syntax called JSX. JSX stands
for JavaScript XML or JavaScript Syntax Extension and is an extension of the usual
JavaScript grammar for React.
The second import is for ReactDOM. The ReactDOM.render method is the brows-
er/DOM specific way of taking our components and rendering it to a specified
DOM node.
The third import is the root component for our app, simply named App . This is our
first React component and will be used in the bootstrapping process for our React
app.
582
Ionic 5 • Bonus: Ionic and other frameworks
Let's have a closer look at the root component. It's located in src/app.tsx ;
import React from 'react';
import { Redirect, Route } from 'react-router-dom';
import { IonApp, IonRouterOutlet } from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';
import Home from './pages/Home';
/* Theme variables */
import './theme/variables.css';
583
Ionic 5 • Bonus: Ionic and other frameworks
As an attentive reader of my book and "co-developer" of the BoB Tours App, you
will find some familiar, but not others. So here are a few explanations:
The first group of imports provide us with some React specific stuff: React itself in
order to use JSX and Route from react-router-dom , a routing library built on top
of React which is used to create the routing in react apps. Then follow IonApp ,
IonReactRouter and IonRouterOutlet as Ionic components we use in or app
component. IonReactRouter wraps ReactRouter's BrowserRouter component.
More informations about this topic you can find here:
https://ionicframework.com/docs/react/navigation
The last import is Home . This component is our first page (a component with a
route/URL) that we can navigate to.
Importing the Core CSS is required for the Ionic components to work properly.
We can optionally import the CSS Utilities (see “8.4 CSS Utilities”, starting from
page 350), if we want to use them.
The last import provides the variables.css we already know from Ionic/Angular
apps as variables.scss (see “8.3 Local and global (S)CSS files”, starting from
page 341), which we can use to influence the theming of an app.
This React component sets up the initial routing for our app, as well as include
some core Ionic components for animations and layout ( IonRouterOutlet and
IonApp ). One thing that stands out is that in React, to do data-binding, the value is
passed in curly braces {} . So in the Route component, we can set the value of
584
Ionic 5 • Bonus: Ionic and other frameworks
component to the Home component. This is how React will know that that value is
not a string, but a reference to a component.
Let's take a look at this Home component and open the file src/pages/Home.tsx :
import { IonContent, IonHeader, IonPage, IonTitle,
IonToolbar } from '@ionic/react';
import React from 'react';
import ExploreContainer from '../components/ExploreContainer';
import './Home.css';
The code explained: For this Home page we again do some imports, the React
component and some specific Ionic components ( IonContent , IonHeader , IonTi-
tle , IonToolbar ). We also import a container element ( ExploreContainer ) and
the corresponding CSS file for this page ( Home.css ). We use these quite similar to
Ionic/Angular components (see chapter “6 UI Components”, starting form page
151). This code should be easy to read and understand, right?
585
Ionic 5 • Bonus: Ionic and other frameworks
//import ExploreContainer
from '../components/ExploreContainer';
import './Home.css';
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>BoB Tours goes React</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent className="ion-padding">
<IonList>
<IonItem onClick={() => about()}>
<IonIcon icon={informationCircle}
color="primary"
slot="start" />
<IonLabel>About this app</IonLabel>
</IonItem>
<IonListHeader>Price Range</IonListHeader>
<IonRange dualKnobs={true} snaps={true} step={20}
min={80} max={400} value={240}>
<IonLabel slot="start">80</IonLabel>
<IonLabel slot="end">400</IonLabel>
</IonRange>
586
Ionic 5 • Bonus: Ionic and other frameworks
As you can see, we now have some more Ionic component imports.
import { IonContent, IonHeader, IonTitle, IonToolbar, IonList,
IonItem, IonCheckbox, IonLabel, IonRadioGroup, IonRadio,
IonRange, IonListHeader, IonIcon } from '@ionic/react';
Every component we use here on the page must be explicitly imported in React.
This also applies to the icon, which is also included as a component.
import { informationCircle } from 'ionicons/icons';
Just after the React.FunctionComponent constructor and before the return part
of our page we created a little function:
const about = () => {
alert('This is my 1st React/Ionic App!');
}
587
Ionic 5 • Bonus: Ionic and other frameworks
This simply shows a browser alert. Nothing special (it's more interesting, in which
way the function is called).
IonItem can be clicked, because here we assign onClick our about() function.
This is followed by an IonIcon with the color "primary" (see its definition in vari-
ables.css ) in the slot "start", i.e. on the left in the IonItem element. Note that
unlike Ionic/Angular, the icon is not set by the name , but the icon attribute, as ref-
erence to the previously imported icon component informationCircle ! This topic
has already caused many questions in community forums.
It shouldn't go unmentioned that in Ionic/React all XML tags without content can be
formulated in the condensed XML special notation: <IonIcon ... /> . That should
be better not be done in Ionic/Angular.
In most cases, the implementation works as described in the Ionic component doc-
umentation, of course with the React-compliant syntax.
588
Ionic 5 • Bonus: Ionic and other frameworks
Last but not least we use a labeled IonCheckbox and set its slot attribute to end
(the right end of the IonItem element) and its checked attribute to {true} to pre-
select it:
<IonItem>
<IonLabel>Allow messages</IonLabel>
<IonCheckbox slot="end" checked={true} />
</IonItem>
589
Ionic 5 • Bonus: Ionic and other frameworks
Let's expand this demo app. We'll now add a page component with some naviga-
tion functionality. First, we add little code to Home.tsx :
import React from 'react';
import { IonContent, IonHeader, IonTitle, IonToolbar, IonList,
IonItem, IonCheckbox, IonLabel, IonRadioGroup, IonRadio,
IonRange, IonListHeader, IonIcon, IonPage }
from '@ionic/react';
import { informationCircle, play } from 'ionicons/icons';
...
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>BoB Tours goes React</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent className="ion-padding">
<IonList>
<IonItem href=”/slideshow”>
<IonIcon icon={play}
color="primary"
slot="start" />
<IonLabel>Slideshow</IonLabel>
</IonItem>
<IonItem onClick={() => about()}>
<IonIcon icon={informationCircle}
color="primary"
slot="start" />
<IonLabel>About this app</IonLabel>
</IonItem>
...
</IonList>
</IonContent>
</IonPage>
);
};
590
Ionic 5 • Bonus: Ionic and other frameworks
Of course we still have to build this site. For this we create the file src/pages/
Slideshow.tsx :
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Slideshow</IonTitle>
<IonButtons slot="start">
<IonBackButton defaultHref="/home" />
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContent className="ion-padding ion-text-center">
<h1>Slideshow page</h1>
<p>This is a page component.</p>
</IonContent>
</IonPage>
);
};
591
Ionic 5 • Bonus: Ionic and other frameworks
For our navigation to work, we have to specify a route for our new slideshow
page in App.tsx :
import React from 'react';
...
import Slideshow from './pages/Slideshow';
...
const App: React.FunctionComponent = () => (
<IonApp>
<IonReactRouter>
<IonPage>
<IonRouterOutlet>
<Route path="/home" component={Home} exact={true} />
<Route path="/slideshow" component={Slideshow} />
<Route exact path="/" render={() =>
<Redirect to="/home" />} />
</IonRouterOutlet>
</IonPage>
</IonReactRouter>
</IonApp>
);
Here's our app with navigation between the two pages in action:
592
Ionic 5 • Bonus: Ionic and other frameworks
Conclusion
Our first adventure with Ionic/React wasn't that hard, was it?
Apart from a slightly different syntax, the way how to build Ionic UIs with React is
very similar. Of course, apps are not just about surfaces and we didn't go deeper
here. But in this bonus chapter I wanted to give you at least a first impression of
maybe the new dream team Ionic and React.
I think, friends of React have no excuse any more not to combine their React skills
with Ionic's excellent possibilities and its approximately 70 UI components to create
awesome apps.
https://ionicframework.com/docs/react/overview
593
Ionic 5 • Bonus: Ionic and other frameworks
Web frontend frameworks seem to pop up quickly and disappear very quickly.
There are only a few exceptions. Starting with its initial release, AngularJS had tak -
en the web frontend framework world by storm, and at the same time was largely
responsible for the aforementioned fluctuation. At least with the successor, which is
called only Angular (see chapter “2 Angular Essentials”, starting from page 25), it
referred many other frameworks on the siding. Only React (see “3. Ionic and Re-
act”, starting from page 573) from Facebook came up with a completely different
approach to the hype and spread of Google's giants. At first very small and incon-
spicuous, but in recent years increasingly self-confident Vue.js (pronounced as the
view) as a third party in the league and is so many annoyed Angular developers
ever suggested as the perfect alternative.
https://github.com/vuejs/vue
594
Ionic 5 • Bonus: Ionic and other frameworks
I'll compare Angular and Vue.js on the basis of typical criteria that should generally
be considered when using third-party components. It's therefore not only on the
typical developer feeling, but also on hard facts such as the long-term usability. In
doing so, attention should be paid to the most objective description, which feeds
from the practical application of both frameworks in real projects. It is therefore im-
portant that you have some basic understanding of MVVM or MV* frameworks
and can at least associate topics such as binding, routing, and the like.
Criteria
The use of foreign frameworks or libraries should be well thought out. Because in
the long run, dependencies arise on things that you may not have any influence
on. For example, what happens if the framework isn't developed further in the fu-
ture because the original programmer simply doesn't feel like doing it anymore?
What happens if the license terms change or contain errors that are simply not re-
solved? Going too blue-eyed at the choice of a framework can lead to serious
problems and may even mean more work than you can save on the component.
This is even more true in the context of Vue.js and Angular, as they serve as appli-
cation frameworks on the client side. Not only do they call our code like a test
framework would do, but they provide the infrastructure to put all of the client's
components together. So if you want to switch from one framework to another at
a later time, this often means a complete rewrite.
For this reason, not only the handling of the frameworks is discussed below, but
also the following criteria are considered:
• Business Conditions
• Architecture
• Rich Client Features
• Tooling
• Test Automation
• Documentation
• Ecosystem
• Learnability
• Scalability
• Future Security
595
Ionic 5 • Bonus: Ionic and other frameworks
Business Conditions
Let's start with a category that's usually not considered in such detail: the economic
conditions. In this, it should be clarified to what extent the tools themselves incur
costs in their use that are not caused by the actual programming with them any-
way. These include, for example, acquisition costs, license fees, ongoing operating
costs and the like. In the case of Vue.js and Angular, the category is at first very
easy to estimate because both frameworks are very similar here. They are both
under the MIT license and can be used free of charge. In addition, because they
are available as open source at GitHub, have a vibrant community, and thus have
the philosophy to work together to fix errors, lower economic risk can be expected
in the event of misconduct. Furthermore, both frameworks are used comparatively
frequently and also by large companies. At Angular this is of course Google, but
also Microsoft. At Vue, for example, BMW and Apple are mentioned. Consequent-
ly, the chance of making serious mistakes is comparatively low as there are many
users who are likely to spot such errors early on. In addition, planning for the next
versions is easy to see in both cases, and you may even have the option to help
yourself with the fix if something doesn't work as expected.
But there is a bigger difference in the organization. While behind Angular with
Google is a large company that uses the framework itself in its own products, the
operators of Vue.js are a grouping of individuals. Both have their own advantages
and disadvantages.
With a large enterprise one can assume a certain financial strength and has the
certainty that the investment security doesn't depend on the motivation of individ-
ual developers. Also in this case, typical team-internal problems, such as disputes
or other discord, the product can't do so much harm, as it may be the case with
pure hobby projects. On the other hand, however, one depends on the decisions
of those large companies, and these decisions don't necessarily always have to be
comprehensible. This can be seen, for example, in cooperation with the communi-
ty, flexibility in proposing changes or when the operator decides to make the
previously provided tool available at no cost.
But how does this actually work in the case of Vue.js? This doesn't purely build on
the motivation of a small group of people. Because this group is very large with 20
active core members (as of June 2019), on the other hand the framework is fi-
nanced by donations via Patreon or sponsoring of companies, which allows some
596
Ionic 5 • Bonus: Ionic and other frameworks
of these developers a full-time job exclusively for Vue.js. Thus, the framework has
reached a size and organization that distinguishes it from many other free projects.
Architecture
The basic architecture of both frameworks is quite similar. In their basic features,
they represent component-oriented MV* frameworks, but they also offer many in-
frastructure components outside the pure representation. Accordingly, they
severely separate the representation from the processing layer and offer possibili-
ties for linking a backend or for outsourcing the processing in such a way that they
are not necessarily linked to the representation of the component.
Component-oriented in this context also means that they no longer require think-
ing in the form of documents, as happens with jQuery (see “2. Ionic and ”, starting
from page 565). Rather, they divide the components of the page into individual,
self-contained units that can be nested at will.
597
Ionic 5 • Bonus: Ionic and other frameworks
Thus, there are already two reasons why Vue has a raison d'être besides Angular
and React. On the one hand, unlike Angular and React, it relies on HTML and
JavaScript, and thus on languages that Web developers are already familiar with,
and on the other hand, web pages can be implemented step-by-step component-
oriented and ultimately lead to a single page application. Not to be forgotten in this
context is that the focus on JavaScript greatly simplifies the use of other JavaScript
frameworks. Although the latter aspect has to be somewhat restricted, this is no
longer a big problem with TypeScript and in the future Vue.js will also offer the di-
rect use of TypeScript instead of JavaScript.
Components in detail
Of course, if components are so important, then the question arises of how these
components are structured and how the two frameworks differ in their implemen-
tation. To explain this, we use the presentation from the documentation of Angular,
as shown in the following figure and listings A and B:
598
Ionic 5 • Bonus: Ionic and other frameworks
In both frameworks, the template, i.e. the presentation layer, and the logic are usu-
ally stored in a file, although it is possible to provide each component in a separate
file. For example, listings A and B make use of this feature, and therefore only
compare the code of the logic layer of the same component while paging the rep-
resentation for clarity. This offloading usually needs to be centrally configured or, as
in the case of Angular, specified directly in the component. The communication
then takes place via bindings, which are marked in the template by double curly
brackets (see “2.6 Content Projection”, starting from page 39).
Listings A and B therefore have the notificationText property. In both cases the
template was bound to this property via a data binding and therefore via {{noti-
ficationText}} . The spelling is now almost standard and thus doesn't differ. An
event binding also contains a reference to the publish function, with which the en-
tered text is then transferred to another component or to a service. Not shown in
the example, but implemented in almost the same way for both frameworks, there
are features for displaying lists, manipulating the visibility of elements, and ad-
vanced input, such as mouse gestures or touch controls. Also, data binding doesn't
necessarily have to be for a simple property or function. For example, these prop-
erties can also be compound: if a property changes, such as the year of birth, then
they are automatically updated, for example, to calculate age. In addition, watchers
can be defined, i.e. functions that are called automatically when the value of a
property changes.
With that we are already at the differences. As the previous figure makes clear,
Angular also offers the possibility to outsource business logic in the form of ser-
vices. This is also necessary because Angular works object oriented and makes
very strong use of Dependency Injection (see “2.4 Dependency Injection”, starting
from page 34). So if you want to use functionality in a component, you need to re-
quest that logic in the form of other components or services through its
constructor. In order to avoid collisions and to allow code to be reused and even
reloaded with delay, Angular offers the concept of the modules most likely to recall
namespaces or packages in C#.
All this doesn't exist in Vue.js. Here we work mostly functional and based on
JavaScript components, and even if TypeScript is configured as a language, it is
rather the type system used, but not specifics such as Dependency Injection. Vue
components react differently than their Angular equivalents and don't require con-
599
Ionic 5 • Bonus: Ionic and other frameworks
structors. Rather, just code is imported here, and then just that code is used. Differ-
ent types of instances, as is the case with classes, don't have to be taken into
account, and if you really want to provide services, then you just don't create a
service, but simply create another function, which can possibly also work as an ob-
ject with further sub functions. Again, Vue.js seems to be a lot easier than Angular,
you don't have to worry about many things. Even in reality, this impression initially
seems to be right; but with a growing amount of source code and a larger number
of components you also want some more rules in Vue.js. Because in large projects,
an error quickly arises, which is discovered only at runtime thanks to JavaScript, or
it comes to collisions of identically named components.
While Angular is quite rigid in its architecture and the extensive use of various
TypeScript features ago, Vue.js shines with freedom, which is also known from the
use of JavaScript. However, this supposed advantage of Vue.js occasionally be-
comes detrimental in larger projects, as long as one doesn't have to apply the
necessary discipline and order that Angular enforces right from the start.
Lifecycle hooks
Both Angular and Vue.js are more than just simple MV* frameworks. Rather, both
can be used as application frameworks to map general requirements that arise in
the context of single page applications. These include the previously noted binding
and dependency injection, but also topics such as lifecycle hooks, which means that
they can intervene in events that can occur during the lifetime of a component.
Such events are, for example, the instantiation of the component, its integration into
the DOM or the removal from it.
Both frameworks offer almost the same amount of possibilities. However, they dif-
fer again in the implementation. At Vue.js, the very easy way is to deposit
appropriate methods in the component. So if there is a mounted function (see next
figure), it will be called as soon as the component is inserted into the DOM, in the
case of created you get a constructor and so on.
Angular gets the constructor as part of TypeScript and via the constructor key-
word. All other hooks must be implemented based on interface definitions, for
example with the method ngOnInit in which the initialization code is then stored.
Whereby, this isn't true. Because it is one of the best practices to use the appropri -
ate interfaces. In fact, Angular only needs to create the appropriate methods. The
600
Ionic 5 • Bonus: Ionic and other frameworks
reason for this is in the transpilation: The TypeScript code of Angular is translated
into normal JavaScript code and JavaScript knows no interfaces like those from
TypeScript. That's why Angular uses the same approach as Vue.js at runtime, and
so you would actually only need the ngOnlnit function.
601
Ionic 5 • Bonus: Ionic and other frameworks
All of these features translate both frameworks, with Vue.js using an additional
package called Vue Router:
https://router.vuejs.org/
The Vue router is maintained by the Vue core team, but it is deployed separately
because it isn't needed, for example, if you only use Vue for individual compo-
nents, not for entire applications. In the implementation, the two frameworks are so
similar that these points should not be described in more detail here. What you
need is there, works without problems, blends in well with the overall solution and
leaves nothing to be desired.
State Management
602
Ionic 5 • Bonus: Ionic and other frameworks
also provides an implementation of the Flux architecture pattern, which was first
known in connection with React:
https://vuex.vuejs.org/
The next figure shows the basic sequence of an action as just described. At the
center is the actual status, in this example the data that contains the list of all user
objects. This status is stored in a store at Flux. The UI components or their views
bind to the corresponding data fields in the store. This automatically updates them
as those dates change.
The change isn't made directly. Instead, actions are called that the store provides.
These actions, in turn, synchronize via a dispatcher to the status in the store. If you
change the user name in the detail view in the example, you would pass the
changed data to an action, this action is directed to the dispatcher and tells him to
update the corresponding data field in the store. The dispatcher makes the change
and ensures that all views linked to the data field are subsequently updated.
The following listing shows how this is implemented within Vuex. Dispatching works
through so-called mutations, but otherwise it isn't different:
Const store = new Vuex.Store({
state: {
users: []
},
mutations: {
setUsers(state, users) {
.users = users
},
updateUser(state, user) {
...
}
},
getters: {
603
Ionic 5 • Bonus: Ionic and other frameworks
Furthermore, there is the possibility to define filters in the form of getters. Accord-
ingly, accesses to the actual backend are usually found in the actions. If they've this
data, with which they have to adjust the internal status, they pass on the changes
to the mutations, which in turn change the status in the store.
604
Ionic 5 • Bonus: Ionic and other frameworks
I didn't make a direct comparison to Angular at this point, because Angular itself
doesn't offer comparable services. Instead, you have to use external libraries such
as @ngrx/store, which are not supported by the Angular team:
https://ngrx.io/guide/store
Other features
Let's have a look at common features of application frameworks like form handling,
translation and backend connection.
As far as form handling is concerned, the question arises as to how forms are con-
structed and validated at runtime. Angular offers a very extensive library and
various options for displaying error texts, for example (see chapter “7 Form valida-
tion”, starting from page 307). Vue.js only has the ability to transfer data between
the surface and the logic of a component using special model properties in the
components. In addition, there is nothing that can't be done via HTML anyway. For
validation, you usually have to either use a third-party library or just lend a hand.
The same applies to the translation. While Angular provides an implementation for
i18n on its own, Vue.js doesn't have its own solution.
Tooling
The “tooling” category concerns tool support for the frameworks that need to be
compared. In the early days of Angular first-time developers came up with horror
stories on how often the configuration has been changed. How hard the develop-
ment environments did with TypeScript and how often you found yourself with
bad error messages in situations that you yourself were not responsible for. That is
passé, and so you hardly notice anything from tools like Webpack, NPM or others
This is true not only on Angular, but also on Vue.js, the latter having the added
605
Ionic 5 • Bonus: Ionic and other frameworks
benefit of being more deeply embedded in the overall ecosystem through the use
of JavaScript.
Even with developer tools, both frameworks are quite similar. For Angular there's
Augury (see “4.5 Pro Tip - Install the Augury Chrome Plugin” on page 94), even if
it isn't from Google itself. For Vue.js, however, there are the VueDevtools:
https://github.com/vuejs/vue-devtools
These two are extensions for common browsers, with which additional information
can be determined, for example via routing, in the case of Angular Dependency
Injection or, in the case of Vuex, the status of existing stores. Furthermore, both
frameworks have their own command line interfaces, even if Google's CLI is clearly
ahead of the pack and Vue.js spends a lot of work on NPM. A nice feature of the
Vue.js CLI, though, is its plugin system. Thus, dedicated extensions of the core Vue
components can be reloaded as plugins.
When installed, the developer is then conveniently guided through the various se-
lection and configuration options. The actions that can be performed with the CLI
need not be controlled by Vue.js. Because with the Vue UI Vue.js offers a user in-
terface, as shown in the figure above. Dependencies can then be installed, analyzed
and managed. The same applies to plugins, and not just to a project, but to all Vue
projects that are set up on the same computer.
606
Ionic 5 • Bonus: Ionic and other frameworks
Test Automation
The subject of test automation has to be considered in two parts. On the one
hand, it's important to understand the extent to which the framework supports au-
tomated testing itself, by preventing code that's difficult to test, and secondly, how
well the framework's own components can be manipulated within tests.
In both cases, Vue.js had a relatively poor reputation until about mid-2019. Thus,
there were no tools that replaced parts of the framework such as the router or the
Vuex stores with test doubles or manipulated to query status information. Also,
there wasn't enough documentation on how to do it all yourself. As a result, the
test automation felt more like a loveless appendage, although it should actually
have an even greater importance in the context of JavaScript. Because the dynam-
ic typing and the general character of the ecosystem can easily mask mistakes and
make them obvious only at runtime.
However, this disadvantage was alleviated now by publishing the Vue Test Utils in a
stable and practicable state:
https://vue-test-utils.vuejs.org/
Thus, with the greatly improved documentation and the general benefits of other
frameworks like Jest or Mocha, the automation of unit testing is no longer a prob-
lem. The typical e2e tests can also be easily realized using tools such as CypressJS
or the good old Selenium.
For Angular there is also the specialized tool Protractor, which in turn is based on
Selenium. Otherwise, other common test tools can be used here as well. In a direct
comparison, the automation of test cases with Angular applications is easier to im-
plement overall and has a more mature tooling. Ionic takes advantage of this
strength with immediate support for Jasmine, TestBed and Karma (see “11.6 Test-
ing”, starting from page 496). This is again due to the use of TypeScript and the
associated better interface descriptions as well as the explicit visualization of exter-
607
Ionic 5 • Bonus: Ionic and other frameworks
Documentation
Of course, it can happen in both Angular and Vue.js in detail that one doesn't find
information. But in these cases, the respective community is happy to help. At
Vue.js you have to pay special attention to the forum and the corresponding chat.
Because here's the actual music. It may be because Vue.js isn't as widely used as
Angular, but it's already noticeable that questions are answered more slowly and
less frequently on StackOverflow. Here Angular has a natural advantage as a top
dog. Of course, where there are more people using the tools, there are also more
people who can answer questions. It's therefore all the more important to know
that there's a Discord chat for Vue.js, where the actual professionals are frolicking.
Ecosystem
We are already in the ecosystem, because that goes beyond the actual communi-
ty. Here, it should be considered to what extent the frameworks are extended by
additional tools, services and functionality that don't originate from the actual oper-
ator.
At the same time, it's also important to consider how much the framework de-
pends on these things. After all, if you first have to load many different
dependencies in order to be able to use the framework at all, then of course there
are also many possible problem points. On the other hand, it makes sense, of
608
Ionic 5 • Bonus: Ionic and other frameworks
course, if an application framework doesn't invent everything itself and instead ac-
cesses such standard solutions at points that can also be solved universally and
independently of the framework. The framework should therefore provide a start-
ing point for other services, where appropriate, and then, if possible, have a
description of the use of those services.
The first place where these dependencies are already noticed is the test automa-
tion. By relying on external tools, one must, of course, be familiar with them as well,
and the documentation, release cycles and general stability of these tools may dif-
fer significantly from those of the actual application framework.
Especially in the E2E testing of Vue.js applications, this is noticeable. Here you will
usually only find descriptions for general web pages, and the peculiarities in the
context of Vue won't be discussed further. This of course makes the training a little
more difficult and can have serious disadvantages when changing the supported
tools. While this scenario may sound unrealistic, both Angular and Vue.js have
changed their recommendations for specific frameworks in recent years, and have
changed the default application templates as well. This may not be earth-shattering
in detail, but is proof of the susceptibility to interference and the effects of such de -
pendencies.
It's better if the framework isn't based on other things, but these things are based
on the framework. This is the case, for example, with component libraries. Optimiz-
ing those for Angular or Vue.js can greatly simplify developer life by allowing
developers to design their applications without worrying too much about styling.
This is especially important in the context of single page applications and Progres-
sive Web Apps (PWA) (see “12.8 Deploy & publish Progressive Web Apps”,
starting from page 546). Each is designed to look and feel like typical desktop or
mobile applications, and it's just right when a component library provides all the
components you've come to expect from the platform. This is why many manufac-
turers of component libraries such as Telerik or DevExpress offer optimized
versions of their UI libraries. The fact that they support not only Angular and React
but also Vue.js shows the importance of the framework meanwhile.
If you don't want to resort to commercial libraries, then you can also use the excel-
lent Vuetify at Vue:
https://vuetifyjs.com
609
Ionic 5 • Bonus: Ionic and other frameworks
This implements very extensively the material design specification of Google and
has thereby even one or other component in stock, which doesn't even own
Google material UI:
https://material.angular.io/
But if you can't find everything at Vuetify or you are afraid to use such a large li-
brary, then take a look at Awesome Vue:
https://github.com/vuejs/awesome-vue
This is basically a curated list of resources around Vue. This includes components,
but also websites or Twitter accounts that deal with the framework.
That such things are of course also for Angular, is only briefly mentioned here. This
is also because it is an integral part of almost every web development conference.
At this point Vue.js has something to catch up with. Especially in German-speaking
countries this is even more true. While Vue has enjoyed huge success in Version 1,
it has only caused a stir with Version 2 in the US, and now, shortly before Version
3, it's spilling over to Europe on a much larger scale.
Learnability
Perhaps the fact that there's less visibility at conferences may be related to the fact
that Vue.js doesn't have as much to explain as Angular does. This is partly because
Vue just has less functionality and fewer core components. On the other hand, it's
also learned very quickly. The structure of a component and the different forms of
binding are understood by every person who has ever programmed JavaScript.
The router and even Vuex have very similar structures to those found in the com-
ponents, so these things are soon understood.
610
Ionic 5 • Bonus: Ionic and other frameworks
learn such a dynamic language as JavaScript. But in both cases, it pays off to really
look at both frameworks and their approaches. Because with that you also learn a
lot for your own work, no matter which of the two frameworks you decide on in
the actual project.
The latter point can be answered very quickly in both cases: there is no end in
sight. Although Angular has lost some attention and benevolence since the initial
hype, React and Vue.js have created two strong alternatives (see “3. Ionic and Re-
act”, section “Use”, starting from page 579). However, each of these frameworks
has its intended use, and for each of these frameworks, there is a strong communi-
ty and a strong team that works tirelessly to drive innovation.
The issue of scalability has already been addressed in various places. In a nutshell,
thanks to typing and dependency injection, Angular scores higher, as both are ear-
ly warning of a variety of errors. Further errors are then intercepted by the better
testability, and thus more stable work is possible, especially in larger projects with
many parallel participants. However, there are some requirements to be met,
which can be very annoying, if you just want to build a website or a component.
Therefore, Vue.js recommends itself for rather smaller front ends with fewer project
participants, while Angular is especially suitable for larger projects. Nevertheless,
both are very good and stable overall solutions that you can use at your own risk
anyway, where you want.
611
Ionic 5 • Bonus: Ionic and other frameworks
Summary
Vue.js is a good alternative to Angular and especially suitable for smaller projects. It
comes with all the features one would expect from an MV* and even application
framework. It's easy to learn, and many negative points from the past, such as
poor testability, have been consistently eliminated. The broad use of JavaScript
also makes it very flexible and particularly popular with individual groups of peo-
ple. However, this involves a number of risks in practical use. In order not to ignore
these, Vue.js is already optimized for TypeScript and therefore even offers the op-
tion of static typing in newer versions.
It remains exciting how Vue.js will evolve. In any case, this framework is worth a
look for newly launched projects, even if you are quite satisfied with Angular.
612
Ionic 5 • Bonus: Ionic and other frameworks
When that's done, we'll create a Vue app and go to its root directory:
$ vue create my-vue-ionic-app
$ cd my-vue-ionic-app
613
Ionic 5 • Bonus: Ionic and other frameworks
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
As we can see, there's an import for the Vue framework itself and an import for a
component called App . The underlying file for App is src/App.vue.
The new Vue method renders the app into an element with the id #app .
Tip: To introspect files with the ending .vue in Visual Studio Code I highly recom-
mend the installation of the Vetur extension from Pine Wu:
https://vuejs.github.io/vetur/
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'app',
components: {
HelloWorld
}
}
</script>
614
Ionic 5 • Bonus: Ionic and other frameworks
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
In the script section note the import of another component called HelloWorld ,
which can be found in the file src/components/HelloWorld.vue:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize
this project,
<br />check out the
<a
href="https://cli.vuejs.org"
target="_blank"
rel="noopener"
>vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/
dev/packages/%40vue/cli-plugin-babel"
target="_blank"
615
Ionic 5 • Bonus: Ionic and other frameworks
rel="noopener"
>babel</a>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/
dev/packages/%40vue/cli-plugin-eslint"
target="_blank"
rel="noopener"
>eslint</a>
</li>
</ul>
<h3>Essential Links</h3>
<ul>
<li>
<a href="https://vuejs.org" target="_blank"
rel="noopener">Core Docs</a>
</li>
<li>
<a href="https://forum.vuejs.org" target="_blank"
rel="noopener">Forum</a>
</li>
<li>
<a href="https://chat.vuejs.org" target="_blank"
rel="noopener">Community Chat</a>
</li>
<li>
<a href="https://twitter.com/vuejs" target="_blank"
rel="noopener">Twitter</a>
</li>
<li>
<a href="https://news.vuejs.org" target="_blank"
rel="noopener">News</a>
</li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li>
<a href="https://router.vuejs.org" target="_blank"
rel="noopener">vue-router</a>
</li>
<li>
616
Ionic 5 • Bonus: Ionic and other frameworks
<script>
export default {
name: "HelloWorld",
props: {
msg: String
}
};
</script>
617
Ionic 5 • Bonus: Ionic and other frameworks
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
Again, you can recognize the three sections template , script and style .
How the whole thing looks like, you have already seen in the figure before.
We now want to spice up this pure Vue app with Ionic. For this we install the
@ionic/vue library:
Once the install is finished we have access to the Ionic plugin and can add it to our
main.js :
Vue.use(Ionic);
Vue.config.productionTip = false;
new Vue({
render: h => h(App),
}).$mount('#app')
618
Ionic 5 • Bonus: Ionic and other frameworks
With the beta version of @ionic/vue there was a problem with using the ioni-
cons library. This error occurred:
warning in ./node_modules/@ionic/vue/dist/ionic-vue.esm.js
"export 'ICON_PATHS' was not found in 'ionicons/icons'
But this workaround only prevents the error. In order to display the icons, more
workarounds had to be used. But since we can assume that a working release can-
didate for Ionic/Vue will be released shortly, I'll just drop icons in this example.
Now we have access to (nearly) all of Ionic’s components. So let's redesign the Hel-
loWorld component and put some Ionic UI elements into src/components/
HelloWorld.vue :
<template>
<div class="ion-page">
<ion-header>
<ion-toolbar>
<ion-title>BoB Tours goes Vue</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-list>
<ion-item @click="about()">
<ion-label>About this app</ion-label>
</ion-item>
<ion-list-header>Price Range</ion-list-header>
<ion-range dualKnobs="true" snaps="true" step="20"
min="80" max="400" value="240">
<ion-label slot="start">80</ion-label>
<ion-label slot="end">400</ion-label>
</ion-range>
<ion-radio-group>
<ion-item>
<ion-label>Azure-Style</ion-label>
<ion-radio value="azure" slot="end"
619
Ionic 5 • Bonus: Ionic and other frameworks
checked="true" />
</ion-item>
<ion-item>
<ion-label>Summer-Style</ion-label>
<ion-radio value="summer" slot="end" />
</ion-item>
</ion-radio-group>
<ion-item>
<ion-label>Allow messages</ion-label>
<ion-checkbox slot="end" checked="true" />
</ion-item>
</ion-list>
</ion-content>
</div>
</template>
<script>
export default {
name: "HelloWorld",
//props: {
// msg: String
//},
methods: {
about: () => {
alert('This is my 1st Vue/Ionic App!');
}
}
};
</script>
<style scoped>
...
</style>
620
Ionic 5 • Bonus: Ionic and other frameworks
The item can be clicked, because here we assign @click to an about() function.
We'll soon have a look at this function.
In most cases, the implementation works as described in the Ionic component doc-
umentation.
It shouldn't go unmentioned that in Ionic/Vue all XML tags without content can be
formulated in the condensed XML special notation: <IonIcon ... /> . That should
be better not be done in Ionic/Angular.
Last but not least we use a labeled ion-checkbox and set its slot attribute to
”end” (the right end of the item element) and its checked attribute to ”true” to
preselect it:
<ion-item>
<ion-label>Allow messages</ion-label>
<ion-checkbox slot="end" checked="true" />
</ion-item>
621
Ionic 5 • Bonus: Ionic and other frameworks
Within the script part of our page we create a methods block with a little func-
tion:
methods: {
about: () => {
alert('This is my 1st Vue/Ionic App!');
}
}
622
Ionic 5 • Bonus: Ionic and other frameworks
Let's rebuild our demo app and add some navigation functionality. In order to use
navigation we need to install the Vue router with:
$ vue add router
Vue.use(Ionic);
Vue.config.productionTip = false;
new Vue({
router,
render: h => h(App),
}).$mount('#app')
You should see the import of router and its injection in the Vue constructor.
Vue.use(VueRouter)
const routes = [
{
path: '/',
623
Ionic 5 • Bonus: Ionic and other frameworks
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for
this route
// which is lazy-loaded when the route is visited.
component:
() => import(/* webpackChunkName: "about" */
'../views/About.vue')
}
]
Instead of the original Vue router we want to use IonicVueRouter . So let's replace
the standard router in src/router/index.js as follows:
import Vue from 'vue'
import { IonicVueRouter } from '@ionic/vue'
import Home from './views/Home.vue'
Vue.use(IonicVueRouter)
...
})
624
Ionic 5 • Bonus: Ionic and other frameworks
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>
By the way - a short explanation to Home and About : As you may have seen, there
is now a folder src/views that contains the files Home.vue and About.vue. We'll
rebuild them soon.
We rebuild App.vue as follows:
<template>
<div id="app">
<ion-app>
<ion-vue-router />
</ion-app>
</div>
</template>
What we have done is wrapping our entire app in an ion-app wrapper and using
ion-vue-router now. IonicVueRouter requires the ion-vue-router element in
order to render Ionic transitions.
You can get rid of the style area on this occasion. We don't need it.
625
Ionic 5 • Bonus: Ionic and other frameworks
<script>
export default {
name: "Slideshow"
};
</script>
<style scoped>
h1 {
margin: 48px 0 0;
}
</style>
Vue.use(IonicVueRouter)
626
Ionic 5 • Bonus: Ionic and other frameworks
What's missing, is a way for the user to navigate from the Home view (page) to the
Slideshow view (page) and back.
627
Ionic 5 • Bonus: Ionic and other frameworks
</ion-item>
</ion-list>
</ion-content>
</div>
</template>
<script>
...
</script>
We simply create a router-link , give the to attribute the path to the desired
page and wrap this around an ion-item element labeled Slideshow . Now this
item can be clicked to navigate to the Slideshow page.
<script>
...
</script>
In the ion-header we add an ion-buttons area at the start slot. Within this area
we place another router-link and wrap it around an ion-back-button . Now
with this back button we are able to navigate back to our Home page.
628
Ionic 5 • Bonus: Ionic and other frameworks
629
Ionic 5 • Bonus: Ionic and other frameworks
Conclusion
As mentioned before, Vue.js is a good alternative to Angular and especially suitable
for smaller projects. Ionic/Vue was in an early beta version at the time of writing
this book. Not everything was perfect, like the support of the Ionicons. But still you
should keep this combination of frameworks in view.
Summary
In this bonus chapter you got to know how to use Ionic without any other frame-
works.
You met Ionic's Capacitor, the native bridge for cross-platform apps. With Capaci-
tor you can build (web) apps that run equally well on iOS, Android, Electron, and
as Progressive Web Apps.
You've learned some basic things about the popular React framework and how to
build an app using Ionic and React.
Often considered as “the leaner Angular” Vue was finally our last trip and you've
seen, that the combination of Ionic and Vue is also an interesting way to build
apps.
This ends our journey through the universe of Ionic. I hope you've enjoyed it.
Now there is only one thing left to do for you:
630