Chapter 7.Building Our Ionic2Do App
With our development environment configured, some initial exposure to Angular 2, and a foundation in Apache Cordova, we are finally ready to start creating our first Ionic application. As not to break with tradition, we are going to be building the classic to-do list management application. You might wonder why we would build something that has been built so many times before. Part of the reason is that for many of you, building something familiar will let you begin to map how Ionic works to whatever language or framework you might be more familiar with. Another reason is that a to-do app has more complexity that simply printing out “Hello World” on a screen.
To get started we need to create a new Ionic project. We will use the blank template as our basis:
$ ionic start Ionic2Do blank
The CLI will begin downloading the various elements for the project: the TypeScript components, the node modules, and finally the required Cordova components. This process may take a few minutes, depending on your internet connection. If you have done Ionic v1 development, you will notice that this process is a bit longer than before.
Once all the packages have been downloaded, the CLI will then ask you if you want to link this application to your Ionic dashboard. For now, we don’t need to link our application, so entern
. We can always link our application at a later date.
Next, you need to make sure you change the working directory to your newly created Ionic project’s directory:
$ cd Ionic2Do
More than once I have forgotten this simple step and wondered why my next Ionic command would fail. The CLI itself provides a gentle reminder when it finishes, as well.
Adding Our Build Platforms
Since the build platforms are now longer automatically added, we need to include them manually. Let’s add the Android platform first:
$ ionic cordova platform add android
This will add aplatforms_directory to our project, as well as the_platforms.json_file. The_platforms_directory will have subdirectories of each of the platforms that we include. If you look at the_platforms > android_directory, you will see all the platform-specific elements needed to build our application for the Android OS: the Gradle files, the_AndroidManifest.xml, and so on.
To add the iOS platform, we just need to change the platform name in the CLI command:
$ ionic platform add ios
And now the_platforms_directory will now contain an iOS directory that houses the iOS-specific elements, like the Xcode project file.
Previewing Our Ionic2Do App
Let’s go ahead and take a quick look at what the Ionic2 blank template looks like in our browser. Although there is an_index.html_file located inside the_www_directory, we actually cannot open it directly in our browser. Instead, this file needs to be served from an actual web server. Luckily, we do not need to upload this to an actual server; the Ionic CLI has a command that will spin up a local web server and allow us to preview our app:
$ ionic serve
The Ionic CLI will begin the process of compiling and bundling the dependencies. Then it will compile the Sass files into CSS, insert the IonIcon font library, and then copy the HTML files. A web server is started, Google Chrome is launched (or if already running, a new tab will be created), and our Ionic app is displayed (Figure 7-1).
Figure 7-1.Ionic’s blank template
The server will now watch the project files for changes and reload the app once you have saved a file.
In addition, you can start the server with the console logging enabled by adding a--c
flag, as well as enabling the server logs with the--s
flag
But the flag I usually enable is the--lab
flag. This will tell the server to create three copies of our Ionic app in the browser: one will have the platform flag set to iOS, another set to Android, and the third set to Windows. This will allow you to quickly preview the differences in the UI between the three platforms and provide a simpler way to test any CSS changes you might be applying to your project (Figure 7-2).
Figure 7-2.Ionic serve using the --lab flag
To quit the server, just typecontrol-c
into the terminal window.
PREVIEWING A SPECIFIC PLATFORM
When using just$ ionic serve
, your app will use Android as its platform of choice. To see your application as it would be rendered on a different platform, just append--platform=
_platformname
_to end of the command. The values areios
,android
, andwindows
.
Let’s return to the project directory and look at some additional files and how they are related.
Understanding the index.html File
The first file I want to look at is thewww/index.html. With Ionic 1, there was a lot of content that was placed in this file. Here is a portion of one of my Ionic 1 app’s script tags:
<
!-- ionic/angularjs js --
>
<
script src="lib/ionic/js/ionic.bundle.js"
>
<
/script
>
<
script src="lib/ionic-service-core/ionic-core.js"
>
<
/script
>
<
script src="lib/ionic-service-analytics/ionic-analytics.js"
>
<
/script
>
<
!-- ngCordova --
>
<
script src="lib
gCordova/dist
g-cordova.min.js"
>
<
/script
>
<
!-- cordova script (this will be a 404 during development) --
>
<
script src="cordova.js"
>
<
/script
>
<
!-- App js --
>
<
script src="js/app.js"
>
<
/script
>
<
!--Factories/Services--
>
<
script src="js/hikedata.js"
>
<
/script
>
<
script src="js/mapdata.js"
>
<
/script
>
<
script src="js/appStatus.js"
>
<
/script
>
<
script src="js/geoLocService.js"
>
<
/script
>
<
!--Controllers--
>
<
script src="views/home/home.js"
>
<
/script
>
<
script src="views/hikes/hike.js"
>
<
/script
>
<
script src="views/hikelist/hikelist.js"
>
<
/script
>
<
script src="views/hikedetails/hikedetails.js"
>
<
/script
>
<
script src="views/map/map.js"
>
<
/script
>
<
script src="views/about/about.js"
>
<
/script
>
As you can see, there are a whole lot of script tags that were added into the_index.html_file during development. Now Ionic has all this JavaScript loading management is baked into the CLI and the build process. We now just have four script tags—certainly a much more manageable list. In fact, we no longer have to remember to add script tags for every Angular element; the build process now generates a single_main.js_file with all our code merged together:
<
!-- cordova.js required for cordova apps --
>
<
script src="cordova.js"
>
<
/script
>
<
!-- The polyfills js is generated during the build process --
>
<
script src="build/polyfills.js"
>
<
/script
>
<
!-- all code from node_modules directory is here --
>
<
script src="build/vendor.js"
>
<
/script
>
<
!-- The bundle js is generated during the build process --
>
<
script src="build/main.js"
>
<
/script
>
If we look at the content within the<body>
tag, we will just find one tag,<ion-app>
. Through the use of this one component, our entire app will be bootstrapped.
Another change from Ionic 1 is that the CSS is generated from the Sass files by default. With Ionic 1, you had to enable this process. Now, everything is built from the Sass files into a single CSS file:
<
link href="build/main.css" rel="stylesheet"
>
With the shift to a component-focused development paradigm, almost all the real code is now within the app component. Let’s explore the new_src_directory in more detail.
Within thesrc_directory, we find four directories:_app,assets,pages, andtheme. We also find four files: a_declarations.d.ts_file, an_index.html_file, a_manifest.json_file, and a_service-worker.js_file.
Exploring the app Directory
During the beta releases of Ionic 2, there was an_app.ts_file that contained the general bootstrapping Angular code for the application. With the shift to NgModule, and Angular itself stabilizing its recommended directory structures, the Ionic directory structure followed suit. Now, the initial app files are stored within the_app_directory, while the rest of the app is located in the_pages_directory.
Inside the_app_directory, we will find five files.Table 7-1is a brief summary of them.
app.component.ts | This file contains the base component that our app will initially use. |
app.html | This file declares the initial app HTML tag. |
app.module.ts | This file declares the initial modules, providers, and entry components. |
app.scss | This file defines any global CSS styles. |
main.ts | This TypeScript file is used during development to load our app. |
Let’s explore each of these files in more depth to understand their role. First, open_app.module.ts_in your editor.
VISUAL STUDIO CODE
The relationship between a developer and his code editor is a special one. In working with Ionic, I personally have found using Microsoft’s Visual Studio Code as my editor of choice. It is a free download fromhttps://code.visualstudio.com/. It obviously has support for TypeScript, as well as Cordova and Ionic.
This file declares what theNgModule
function will do when called as Angular/Ionic runs:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
@NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
There are several Ionic-specific things to note here. First, importing IonicApp and IonicModule from the ionic-angular library. The IonicModule’sforRoot
method is called to define the root app component. We can pass aconfig
object intoIonicModule
. This object allows us to define such items like the back button text, the icon mode, or even the tab placement. Since we are not changing any of these settings, we will leave them as their platform defaults. Check out the list of all theconfiguration settings.
We also define NgModule’sbootstrap
parameter to useIonicApp
for the actual bootstrap process.
For theentryComponents
parameter, we define an array that contains all the components used by our application. In this case, theMyApp
andHomePage
components are included. By doing this, the Ahead of Time (AoT) compiler can use this and improve our application’s performance.
Now, let’s explore the_app.component.ts_file. At the start of the file are theimport
statements. If you recall, this is how Angular now handles its dependency injection:
import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { HomePage } from '../pages/home/home';
The firstimport
statement loads theComponent
module from Angular core.
WHAT ABOUT THE @APP COMPONENT?
In earlier versions of Ionic 2, the application was bootstrapped using theApp
module. With the release of beta 8, the Ionic team shifted away from this custom module to use the genericComponent
module instead.
The nextimport
statement loads thePlatform
module from the Ionic Framework libraries. The third and fourth import
statements load the StatusBar and Splashscreen plug-ins from the Ionic Native library. The finalimport
statement loads ourHomePage
component that is defined in the_home_directory inside the_pages_directory. We will touch on this module in a bit.
Next comes the@Component
decorator:
@Component({
templateUrl: 'app.html'
})
WHAT IS A DECORATOR?
_Decorators_are simply functions that modify a class, property, method, or method parameter.
Within ourComponent
decorator, we will specify the template to render. The template can be declared in one of two forms: inline, or as an external file and referenced usingtemplateUrl
instead.
If the template is short, we could just include it inline. For example, the_app.component.ts_file had the template declared inline. Here is what it used to look like:
template: `
<
ion-nav [root]="rootPage"
>
<
/ion-nav
>
`
For more complex HTML templates, you will want to keep them in a separate file.
Let’s look at the_app.html_file next. This template is just the<ion-nav>
component:
<
ion-nav [root]="rootPage"
>
<
/ion-nav
>
Just as in Ionic 1, this is a basic navigation container for our content. We will look at the Navigation components in a bit. Also included in our template is set the root page for the component. We use Angular’s new one-way data-binding syntax of[ ]
to set the root property to therootPage
variable. Returning to the_app.component.ts_file, we can see that variable is set within the class definition:
export class MyApp {
rootPage = HomePage;
constructor(platform: Platform, statusBar: StatusBar, splashScreen:
SplashScreen) {
platform.ready().then(() =
>
{
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
statusBar.styleDefault();
splashScreen.hide();
});
}
}
The Ionic CLI automatically names this classMyApp
. Next, it defines therootPage
variable to be theHomePage
component. Since this is TypeScript, we also define the variable type. In this case, we are setting it to be of typeany
.
The component’s constructor has the IonicPlatform
component passed as a parameter. Within the constructor, a JavaScript Promise is made from theplatform.ready
function. Once Ionic and Cordova are done bootstrapping, this Promise will be returned, and any device-specific code will be safe to execute.
DEVICEREADY EVENT
If you have done any Cordova or PhoneGap development in the past, then you are probably familiar with thedeviceReady
event that Cordova will fire once the web-to-native bridge has finished initializing. It is similar to thedocumentReady
event that traditional web development listens for.
In earlier versions of Ionic, we would have made a call toionicBootstrap
, but this is now handled within theNgModule
function.
The next file within our_app_directory is the_app.scss_file. This file serves as a global Sass file for our app. If you are not familiar with Sass, it is a stylesheet language that compiles to CSS. We will explore styling and theming later. For now, we can leave this file as is.
The final file,main.ts, is the entry point to our application. By default our app is running in development mode. Here is what is in the_main.ts_looks like:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
TheplatformBrowserDynamic
module is loaded, as is ourAppModule
. Then thebootstrapModule
function is called and ourAppModule
passed in. For the production version ofmain.ts, theenableProdMode
module is loaded and called before we make the bootstrap call.
Remember, these files are about the initial start of the application. You should not need to interact with these files much except to override a configuration or a global styling change. When we are ready to test or release our application, we will build our application with an additional flag that will enable the build to switch to production mode.
THE PAGES DIRECTORY
The Ionic CLI will also generate apages_directory. With Ionic 1, it was common to keep the HTML templates in one directory and the related_controller.js_file in another directory. Now, it is considered best practice to keep all the associated files together. So within the_pages_directory, we will find a_home_directory. Inside this folder, we will find_home.html,home.scss, andhome.ts. As you build out your Ionic 2 application, you will create new directories for each page and place them within the_pages_directory.
THE HOME.HTML FILE
Looking at the HTML code of this file, we will see that all the tags begin with<ion-
. One of the original reasons the Ionic Framework was built atop the Angular Framework was the ability to extend the HTML language to include markup that represented the mobile components that were needed. This is still true with the current version of Ionic:
<
ion-header
>
<
ion-navbar
>
<
ion-title
>
Ionic Blank
<
/ion-title
>
<
/ion-navbar
>
<
/ion-header
>
<
ion-content padding
>
The world is your oyster.
<
p
>
If you get lost, the
<
a href="http://ionicframework.com/docs/v2"
>
docs
<
/a
>
will be your guide.
<
/p
>
<
/ion-content
>
In order for our<ion-navbar>
to be above the content and remain fixed during page transitions, it must be placed within a<ion-header>
tag. Now within the<ion-navbar>
, we find the<ion-title>
tag that sets the title of the Navbar. This component isplatform-aware
. On iOS, the text is centered within the Navbar, as with the Apple iOS Human Interface guidelines; but on Android or Windows Phone, the text is aligned left. This is a great example of defining the base component and Ionic doing the heavy lifting of applying the platform-specific styling.
Beneath the navbar is the<ion-content>
. This defines the main container that we will place most of our app’s interface. It has one Angular directive,padding
, to provide some CSS padding to the container. The blank template has some text, a<p>
tag, as well as, an<a>
tag. This is a great example of just using basic HTML to define your content.
THE HOME.TS FILE
This TypeScript file contains very little code. In fact, this is the bare minimum to create an Ionic page:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
constructor(public navCtrl: NavController) {
}
}
We first import theComponent
module from the Angular core library. Just as theApp
component was standardized toComponent
, so was thePage
component standardized toComponent
. Next we import theNavController
component from the Ionic-Angular library. One of the biggest things you have to remember when you are developing with Angular 2 is you have to import the components and features from Angular and Ionic in order to use them. The entire library is no longer available for use by default.
OurComponent
decorator defines itstemplateUrl
as'home.html
' and the selector to bepage-home
.
THE BUILD DIRECTORY
Before the first release candidate of Ionic 2, the Ionic CLI generated a running version of our app by building the finished app in a slightly different manner. It used to take all the HTML files and copy them into a build directory within thewww_directory. Any changes to Sass files—the.scssfiles—that we have generated will be compiled and appended to each platform’s CSS file. Our TypeScript is transpiled to ES5 and packed with the Angular and Ionic code and saved as_app.bundle.js.
Although there is still a build directory within thewww, you will not find your HTML files within it anymore. Now, when the Ionic CLI generates a running version of our app, the HTML templates are pre-compiled and included within themain.js_file. The build directory now stores four files:_main.css,main.js,main.js.map, andpolyfills.js. All of the.scss_files are compiled and merged into_main.css. Our TypeScript is still transpiled into ES5 and packed with the Angular and Ionic code and saved asmain.js. The_main.js.map_files assist with any debugging we may need to do, while the_polyfill.js_address any cross-browser compatibility issues that need to be addressed.
THE THEME DIRECTORY
The next top-level element within the_src_directory is the_theme_directory. Here we find a Sass file that we can modify to override any default CSS used by Ionic. We won’t spend much time here now, other than to say that with Ionic2 the ability to customize our app’s visual styling is much easier.
THE ASSETS DIRECTORY
The goal of this directory is to house the various assets your app can need, such as fonts and images.
THE DECLARATIONS.D.TS FILE
Because TypeScript utilizes static types, we need to be able to “describe” code we want to use and import. This is handled through type definitions, and there is a fairly large collection of them managed by the TypeScript team. If for some reason you are unable to find the types for the third-party library, you can create a shorthand type definition in thedeclarations.d.ts_file. The.d.ts_denotes that the file is a definition file and not actual code. Within the file, we can add a line to declare our module:
declare module 'theLibraryName';
This line tells the TypeScript compiler that the module is found, and it is an object of any type. This will allow the library to be used freely without the TypeScript compiler giving errors.
THE MANIFEST.JSON FILE
Since Ionic can be used to create progressive web apps (PWAs), a default_manifest.json_file is included. We will discuss this file and PWAs in more detail inChapter 13.
THE SERVICE-WORKER.JS FILE
The final item in the_src_directory is the_service-worker.js_file, another component of creating PWAs. We will ignore it for now and return to it in earnest inChapter 13.
Now that we have had a brief survey of the basic files created by the Ionic CLI for this template, let’s get to work modifying them for our Ionic2Do app.
Updating the Page Structure
While “Home,” as an initial page name might be acceptable for some projects, I want to update our structure to be more reflective of what this page is: a list of our tasks. Let’s go through our files and directories and update the reference totasklist
. We will start with the_app.component.ts_file:
import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { TaskListPage } from '../pages/tasklist/tasklist';
@Component({
templateUrl: 'app.html'
})
export class MyApp {
rootPage:any = TaskListPage;
constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {
platform.ready().then(() =
>
{
// OK, so the platform is ready, and our plugins are available.
// Here you can do any higher-level native things you might need.
statusBar.styleDefault();
splashScreen.hide();
});
}
}
VISUAL STUDIO CODE
If you are using Visual Studio Code as your editor, you will see portions of your code get a red squiggly line underneath them. This is editor informing you of an issue, such as a missing file or an unreferenced variable.
We also need to update the_app.module.ts_file. Since we are adjusting the starting component, we need to adjust it to reflect the new component name and directory:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { MyApp } from './app.component';
import { TaskListPage } from '../pages/tasklist/tasklist';
@NgModule({
declarations: [
MyApp,
TaskListPage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
TaskListPage
],
providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
Save this file, and we will move on to adjusting the directory structure and filenames. Within the_page_directory, change the references from'home'
to'tasklist'
:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
@Component({
selector: 'page-tasklist',
templateUrl: '
tasklist.html
'
})
export class
TaskListPage
{
constructor(public navCtrl: NavController) {
}
}
Switch to_tasklist.scss_file and change the selector frompage-home {}
topage-tasklist {}
. Then save this file.
If you are still running$ ionic serve
, stop it by pressing Ctrl-C while in the terminal. Then run$ ionic serve
again, and we should see no visible changes from before.
Let’s first update our HTML template by openingtasklist.html.
In the<ion-title>
tag, changeIonic Blank
toTasks
. Next, we add a button to the header that will allow us to add tasks to our list. Adding buttons in either a navbar or its parent component, the toolbar, is a bit different from adding standard buttons.
After the closing</ion-title>
tag, add a<ion-buttons>
tag. This component acts as a container for a collection of buttons. This component did not exist in Ionic 1, causing some interesting solutions when you wanted a row of buttons within your navbar.
Within this tag we need to included an attribute that controls the placement of the buttons it contains. You can either give it the value ofstart
orend
. I want our add item button to be placed at the far left of the header, so set this toend
:
<
ion-buttons end
>
<
/ion-buttons
>
Next we can add a standard button element<button>
and use the Angular 2 syntax to define the click handler to a function namedaddItem()
. We will also add theion-button
directive to give the button the proper platform styling. Another change in Ionic 2 is theicon-left
andicon-right
directives. These directives will add a small amount of padding to either the left or right of the icon. Otherwise, the icon will appear directly next to the text. If you have an icon-only button, then addicon-only
. This will add padding to all the sides of the icon to give it proper spacing:
<
ion-header
>
<
ion-navbar
>
<
ion-title
>
Tasks
<
/ion-title
>
<
ion-buttons end
>
<
button ion-button icon-left (click)="addItem()"
>
Add Item
<
/button
>
<
/ion-buttons
>
<
/ion-navbar
>
<
/ion-header
>
Go ahead and save this file. If$ ionic serve
is running, we should see our changes applied in the browser (seeFigure 7-3).
Figure 7-3.Ionic2Do with the updated header
Ionic ships with a nice collection of icons. They are actually hosted as an independent GitHub project, so you can use them outside of Ionic if you want to.
Ionic 2 made several changes on how we work with the Ionicon library. First, we now have the<ion-icon>
tag, instead of using<i>
. Next, instead of referencing the icon we want to use by defining it with a CSS class, we set just thename
attribute to the icon we want. For the Add Item icon, a standard + icon will work just fine. The markup is:
<
button ion-button icon-left (click)="addItem()"
>
<
ion-icon name="add"
>
<
/ion-icon
>
Add Item
<
/button
>
Many icons have both Material Design and iOS versions (in both solid and outline formats). Ionic will automatically use the correct version based on the platform.
However, if you want more control, you can explicitly set the icon to use for each platform. Use themd
(material design) andios
attributes to specify a platform-specific icon:
<
ion-icon ios="logo-apple" md="logo-android"
>
<
/ion-icon
>
If you want to use a specific icon, then simply use the icon’s full name. For example, if we wanted to use the iOS outline map icon across all platforms, the markup would be:
<
ion-icon name="ios-map-outline"
>
<
/ion-icon
>
Check theIonic Framework Ionicons pageto find the full icon name, then select the icon. It will display the icon’s information (Figure 7-4).
Figure 7-4.The full icon information for the map Ionicon
Now let’s turn our attention to building our list of tasks.
Replace the placeholder content in the<ion-content>
with this bit of HTML:
<
ion-content
>
<
ion-list
>
<
ion-item *ngFor="let task of tasks"
>
{{task.title}}
<
/ion-item
>
<
/ion-list
>
<
/ion-content
>
Let’s walk through this code fragment. The <ion-content>
tag serves as our container for content. It will automatically scroll if the content exceeds the viewport. Next, we have the<ion-list>
, which as you might guess is used to display rows of information. In our case, this will be rows of tasks. Within our<ion-list>
we define what our list item’s template will be, and how to map and bind our data.
With Angular 1, we would use theng-repeat
directive to define the array to repeat through to generate our tasks. Angular changed this directive to*ngFor
. It does essentially the same function. For our sample, we are going to loop through an array namedtasks
and set each item into a locally scoped variable named task. Using the data-binding syntax,{{task.title}}
, we will render out each task’s title string. Save this file, and open the_tasklist.ts_file.
Within the class, we will define thetasks
variable. Since we are using TypeScript, we need to set the type. For now, let’s just define the tasks array as an array ofany
:
export class TaskListPage {
tasks: Array
<
any
>
= [];
constructor(public navCtrl: NavController) {
}
Now we need to include an actual constructor in our class definition.
Within the constructor, let’s set ourtasks
array to some placeholder content to verify that our template code is working:
constructor(public navCtrl: NavController) {
this.tasks = [
{title:'Milk', status: 'open'},
{title:'Eggs', status: 'open'},
{title:'Syrup', status: 'open'},
{title:'Pancake Mix', status: 'open'}
];
}
Save this file. Again, using$ ionic serve
, we can preview this running app in our browser (Figure 7-5).
Let’s turn our attention to adding an item to our task list.
We already included a click handler,addItem
in the HTML file, so we now need to write it.
In the_tasklist.ts_file, after the end of the constructor, we will add ouraddItem
function. For now, we will use the standardprompt
method to display a dialog to allow the user to enter a new task title. This will be included in a generic object that is pushed onto ourtasks
array:
addItem() {
let theNewTask: string = prompt("New Task");
if (theNewTask !== '') {
this.tasks.push({ title: theNewTask, status: 'open' });
}
}
Figure 7-5.Ionic2Do app
Save the file and wait for the app to be recompiled and reloaded. Switching back to our browser, click the Add Item button in the header, and the browser will open a prompt dialog. Next, enter a new task and close the dialog. Angular will automatically update our list (Figure 7-6).
Figure 7-6.Ionic2Do’s Add Item dialog
If you launched your preview using$ ionic serve --lab
, you noticed that only one list was updated with your new tasks. That is becauseionic serve
is actually running two instances of the app.
Now that we can add items to our task list, let’s add a method to mark them as done. A common interface pattern for this is to swipe from right to left on a row to reveal a set of buttons. Ionic provides an<ion-item-sliding>
component that recreates this user experience.
Replace this code block:
<
ion-item *ngFor="let task of tasks"
>
{{task.title}}
<
/ion-item
>
with the following:
<
ion-item-sliding #slidingItem *ngFor="let task of tasks"
>
<
ion-item
>
{{task.title}}
<
/ion-item
>
<
/ion-item-sliding
>
ThengFor
is now placed on the<ion-item-sliding>
component instead of the<ion-item>
. The elements within the<ion-item>
tag will be our visible row items. We also need to include the reference to theslidingItems
variable.
Next we need to use the<ion-item-options>
component to contain our buttons that will be shown when we swipe the row. This component supports having these option buttons be on the right, left, or even both sides. Simply addside='right'
to theion-item-options
that you want revealed when the user swipes from the right to left. For items that you want to show when the user swipes from left to right, define it asside='left'
. If you do not include a side, it will default to the right side.
For this app, we will have a button to mark a task as done, and another button to remove it from the list completely. The markup is just the standard<button>
tag. Each button will have a click function and use an icon from the Ionicon library. Here is the snippet:
<
ion-list
>
<
ion-item-sliding *ngFor="let task of tasks"
>
<
ion-item
>
{{task.title}}
<
/ion-item
>
<
ion-item-options side="right"
>
<
button ion-button icon-only (click)="markAsDone(task)" color="secondary"
>
<
ion-icon name="checkmark"
>
<
/ion-icon
>
<
/button
>
<
button ion-button icon-only (click)="removeTask(task)" color="danger"
>
<
ion-icon name="trash"
>
<
/ion-icon
>
<
/button
>
<
/ion-item-options
>
<
/ion-item-sliding
>
<
/ion-list
>
To mark a task as done, our click handler will call a function namedmarkAsDone
and pass it the reference to that row’s task. If you have used Angular before, you know this is a great example of the power of the framework. You can let Angular keep track of each row and let it resolve the management of what task we are interacting with, rather having to do all the bookkeeping ourselves.
For the button content, we will just use the<ion-icon>
component. Now, since there is nothing inside the<ion-icon>
, you might be tempted to self-close this tag. But Angular requires the tags within a template not be self-closed (Figure 7-7).
Figure 7-7.Ionic2Do’s sliding list buttons
Let’s switch to the_tasklist.ts_file and add our two functions,markAsDone
andremoveTask
, after ouraddItem
function:
markAsDone(task: any) {
task.status = "done";
}
removeTask(task: any) {
task.status = "removed";
let index = this.tasks.indexOf(task);
if (index
>
-1) {
this.tasks.splice(index, 1);
}
}
Before we test our app, let’s make it so that when a user marks a task as done, we draw a line through the task. CSS makes this very easy with thetext-decoration
property. In the_tasklist.scss_file, add the following CSS:
.taskDone {text-decoration: line-through;}
Angular provides a method of conditionally applying CSS classes to an element. We will have Angular apply the CSS classtaskdone
if the task’s status property is done to our<ion-item>
:
<
ion-item [ngClass]="{taskDone: task.status == 'done'}"
>
ANGULAR 1 TO ANGULAR 2
This directive is a good example of some of the subtle changes between Angular 1 and Angular 2. In Angular 1, the syntax wasng-class="expression"
. In Angular 2, the syntax is now[ngClass] ="expression"
.
Make sure all the files are saved, and then test the app in your browser. You should be able to swipe a row and reveal the two option buttons. Click the checkmark to mark the task as done. You should see the text change to have a line drawn through it. But the row did not slide back over the option buttons after we clicked. Let’s fix this interface issue.
In the_tasklist.html_file, we first need to modify ourimport
statement to include theItemSliding
component:
import {NavController, ItemSliding} from 'ionic-angular';
Then we need to modify the<ion-item-sliding>
component. Add a local template variable#slidingItem
. The<ion-item-sliding>
component will set this variable with a reference to each row. Next, pass this variable as the first parameter to both our option button functions. The new code will look like this:
<
ion-item-sliding *ngFor="let task of tasks" #slidingItem
>
<
ion-item [ngClass]="{taskDone: task.status == 'done'}"
>
{{task.title}}
<
/ion-item
>
<
ion-item-options side="right"
>
<
button ion-button icon-only (click)="markAsDone(slidingItem, task)"↵
color="secondary"
>
<
ion-icon name="checkmark"
>
<
/ion-icon
>
<
/button
>
<
button ion-button icon-only (click)="removeTask(slidingItem,task)"↵
color="danger"
>
<
ion-icon name="trash"
>
<
/ion-icon
>
<
/button
>
<
/ion-item-options
>
<
/ion-item-sliding
>
Save the file, and open the_tasklist.ts_file again. We need to include our new parameter to both themarkAsDone
andremoveTask
functions. This parameter will be typed tolist
:
markAsDone(slidingItem: ItemSliding, task: any) {
task.status = "done";
slidingItem.close();
}
removeTask(slidingItem: ItemSliding, task: any) {
task.status = "removed";
let index = this.tasks.indexOf(task);
if (index
>
-1) {
this.tasks.splice(index, 1);
}
slidingItem.close();
}
We can call theclose
method on the list reference, and it will slide our list item back for us.
Try our new version out, and you will see that our rows will close after we click either option button.
IONIC 1 TO IONIC 2
Ionic 1 also had a similar list component. However, triggering the close action required injecting a reference to$ionicListDelegate
and using it. Ionic 2 cleans up this code and just requires that the reference to the list’s row be available.
Adding Full-Swipe Gesture
You have probably used the full-swipe gesture to perform an action on a list item. A great example of this is in the iOS Mail app. If you do a short swipe on the list, the option buttons will reveal themselves. This is what we have working in our application now. But in the iOS Mail app, if you keep swiping, the last option element is automatically triggered. Let’s add this functionality to our application.
First we need to add an event listener for theionSwipe
event. This event will be triggered when the user has performed the full-swipe gesture. We also need to tell it what function to call. Typically, this would be the same function that the last option button would call. So our<ion-item>
will now become:
<
ion-item-options side="right" (ionSwipe)="removeTask(slidingItem, task)"
>
Second we need to add an additional property on the button that we want to visually expand as the gesture is performed. Again, since this is typically done on the last item, we will add to our button that deletes the task. The new button is now:
<
button ion-button icon-only expandable color="danger" ↵
(click)="removeTask(slidingItem, task)"
>
And with those two additions, we have added support for a full-swipe gesture in our application.
Simple Theming
Besides adapting the components’ look to the platform, we can also quickly affect their color. Ionic has five predefined color themes: primary (blue), secondary (green), danger (red), light (light gray), and dark (dark gray). To apply a theme color, we can just add to most Ionic components. Let’s add a touch of color to the header by setting its theme color toprimary
:
<
ion-navbar color="primary"
>
We had already set the theme color to each of the option buttons:
<
button color="secondary" (click)="markAsDone(slidingItem, task)"
>
…
<
button color="danger" (click)="removeTask(slidingItem, task)"
>
We will explore styling our Ionic apps in further detail in a later chapter. But for now, we have a little color in our app.
Proper Typing
The build scripts for Ionic are rather strict in what they allow in terms of variable typing. That is why thetasks
variable is typed asany
, instead ofObject
. Now it is possible to provide some inline typing for anObject
. To do this for our task, it would be:
tasks: Array
<
{ title: string, status: string }
>
= [];
But if we change the typing on the task parameter inmaskAsDone
andremoveTask
functions toObject
, the build process will report an error:
Typescript Error
Property 'status' does not exist on type 'Object'.
This is the TypeScript informing us about the fact that we added thestatus
property on to theTask
object. In earlier versions of the Ionic build scripts, our app would have still functioned; with the existing build scripts, we will need to address it.
The fix is quite simple: we need to create a customTask
class and use it instead of the genericObject
. We could just define theTask
class directly within the_tasklist.ts_file; but instead we will create a new file named_task.ts_in the same directory as our_tasklist.ts_file. In this file, we will export a class namedTask
. It will have two properties: title and status, both typed as strings:
export class Task {
title: string;
status: string;
}
In the_tasklist.ts_file, we need to inject this class definition:
import { Component } from '@angular/core';
import { NavController, ItemSliding } from 'ionic-angular';
import { Task } from './task';
Then change the typing of our task array from:
tasks: Array
<
Object
>
= [];
to:
tasks: Array
<
Task
>
= [];
We also need to adjust the input parameters for both themarkAsDone
andremoveTask
functions. The task variables need to now be typed asTask
instead ofany
.
Saving Data
You might have noticed a major flaw in our app. Any tasks that we add are not being saved. If you reload the app, only the four initial tasks are shown. There are options to solve this problem. We could use localStorage to save our data, but there are cases where this data can be cleared out by either the user or the system. WebSQL is an option, but the specification is no longer maintained, so there might be long-term support issues to consider. If we want to use a Cordova plug-in, there are several other options to consider. Since our data structure is fairly straightforward, it could be written to the file system as a simple text file and read back in. Not a very elegant solution (nor secure), but it would work. A more advanced option might be to use the SQLite plug-in, maybe in conjunction with PouchDB. We could also look at using the newStorage
module from Ionic itself.
But these are one-to-one solutions. In our connected, multidevice, multiplatform world, I want to be able to share and interact with my task list across all my mobile devices.
Creating a Firebase account
One option you can use to quickly have a cloud-based database is Firebase from Google. One of Firebase’s services is to provide a real-time JSON database for storing and syncing your app’s data. If you do not have an account, you can sign up for a free developer account withFirebase.
There you will be prompted to create a new project. Go ahead give your project the name of “Ionic2Do.” Next, select your country or region from the list and click Create Project. This will create a basic Firebase system for us to use. From this dashboard screen, click the Database choice, which will display our default database.
We will need several configuration elements to connect our application up to Firebase. Firebase makes this fairly simple: locate the “Add Firebase to your web app” button and click it. This will display a complete code sample for connecting our app; however, we are only interested in the config values:
var config = {
apiKey: "your-api-key",
authDomain: "your-authdomain",
databaseURL: "https://someurl.firebaseio.com",
projectId: "ionic2do-somecode",
storageBucket: "someurl.appspot.com",
messagingSenderId: "your-sender-id"
};
Save this information to a temporary file, as we will need it later.
But before we can use this database, we need to adjust its security setting. Click the Rules tab to bring up the Rules editor. By default, the database is set to require authorization for both read and write access. Since we are just working on a simple tutorial, we can relax these settings. Change the rules from:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
to:
{
"rules": {
".read": "auth == null",
".write": "auth == null"
}
}
Then click Publish. Our database can now be read and written to without needing authentication.
Installing Firebase and AngularFire2
In order to use Firebase, we need to install some additional node modules that our app will use.
EVERYTHING IS IN BETA
Both the AngularFire and Firebase libraries are still under active development. The code samples listed here might change as the libraries are updated. Please refer to either theO’Reilly siteorthis book’s websitefor updates.
The first node module we need to install is the@types
module. In the early development of TypeScript, there were several different type declaration managers for the_.d.ts_files. With the release of TypeScript 2.0, the preferred method is to use the@types
module to manage any additional library declarations:
$ npm install @types/request --save-dev --save-exact
Now, let’s install Firebase into our project. From the command line:
$ npm install firebase --save
Although we could work with Firebase directly, the AngularFire library was built to make this process even easier. Now with the addition of Observables in Angular 2, the method to interface with Firebase has become even simpler. If you have worked with Angular 1 and Firebase, then you probably used the AngularFire library to provide AngularJS bindings for Firebase. AngularFire 2 is the TypeScript version of this library. This version is still in beta at the time of this writing. To install the AngularFire library, execute this from the command line:
$ npm install angularfire2 --save
Ionic Build System
When we use the Ionic CLI to build our application, it executes a series of scripts collectively known as theIonic App Scripts. The Ionic App Scripts were recently broken out from the main framework as a standalone GitHub repo, so they can be updated and improved independently of the framework itself. These scripts take our various files, third-party libraries, assets, and whatever else our apps need and produce our runnable application. Throughout the development of Ionic 2, the team changed the build system four times, from Browserify to WebPack to Rollup.js and back to WebPack 2. We recommend following theIonic blogfor the latest updates on the Ionic build options.
To install the latest version of the Ionic App Scripts, run the following command from the Ionic_project_directory:
$ npm install @ionic/app-scripts@latest
By default, the Ionic App Scripts will do the following steps for us:
Transpile source code to ES5 JavaScript
Compile Ahead of Time (AoT) template
Compile Just in Time (JiT) template
Inline template for JiT builds
Bundle modules for faster runtime execution
Treeshake unused components and dead-code removal
Generate CSS from bundled component Sass files
Autoprefix vendor CSS prefixes
Minify JavaScript files
Compress CSS files
Copysrc_static assets to_www
Lint source files
Watch source files for live-reloading
Rather than relying on an external task runner like Grunt or Gulp, these scripts are executed from npm scripts. These scripts perform a wide range of tasks for us, including those inTable 7-2.
Task | Description |
build | Creates a complete build of the application; usesdevelopment settings by default; uses--prod to create an optimized build |
clean | Empties the_www/build_directory |
cleancss | Compresses the output CSS withCleanCss |
copy | Runs the copy tasks, which by default copies thesrc/assets/_and_src/index.html_files to_www |
lint | Runs the linter against the source_.ts_files, using the_tslint.json_config file at the root |
minify | Minifies the output JS bundle and compresses the compiled CSS |
sass | Creates a Sass compilation of used modules; bundling must have run at least once before Sass compilation |
watch | Runs watch for dev builds |
In addition to these tasks, the app scripts support the ability for extenstive customization to fit within your larger build system. This customization ranges from various config files, config values, or Ionic environmental variables that can be set. You can learn more about the Ionic App Scripts onGitHub.
Adding AngularFire to Our app.module.ts File
There are some changes we need to make to our_app.module.ts_file to use our AngularFire/Firebase solution. First we need to import the components from the angularfire2 package:
import { AngularFireModule } from 'angularfire2';
import { AngularFireDatabaseModule } from 'angularfire2/database';
The second change is to define our default Firebase configurations. Before the_@NgModule_declaration, add this code (replacing the values that were assigned to you when you created your Firebase account):
The second change is to define our default Firebase configurations. Before the_@NgModule_declaration, add this code (replacing the values that were assigned to you when you created your Firebase account):
export const firebaseConfig = {
apiKey: "your-api-key",
authDomain: "your-authdomain",
databaseURL: "https://someurl.firebaseio.com",
storageBucket: "someurl.appspot.com",
messagingSenderId: "your-messageSenderID"
};
The final change is to theimports
array within the@NgModule
declaration. Here we need to add the call to theAngularFireModule
and initialize it with our config:
imports: [
BrowserModule,
HttpModule,
IonicModule.forRoot(MyApp),
AngularFireModule.initializeApp(firebaseConfig),
AngularFireDatabaseModule
],
With that, our initial setup to use AngularFire/Firebase is done. Let’s turn our attention to where the real work lies in the_tasklist.ts_file.
Using Firebase Data
Like with the changes toapp.module.ts, the first thing we need to update is ourimport
statements. We need to import theAngularFireDatabase
andFirebaseListObservable
modules from the AngularFire 2 library:
import { AngularFireDatabase, FirebaseListObservable } from 'angularfire2';
Typically when working with dynamic data, a common solution is to use theObservable
module from the RxJS library. If you have not heard of the RxJS library before, it is a set of libraries to compose asynchronous and event-based programs using observable collections and Array#extras style composition in JavaScript. The library is actively maintained by Microsoft. You can learn more about this library atGitHub.
AngularFire takes the use ofObservable
a bit further and extends to a custom version,FirebaseListObservable
. This is what we will use for our application.
Next, we need to replace using a local array to store our tasks, to one that will be bound to the Firebase data.
So ourtasks: Array<Task> = [];
will now becometasks: FirebaseListObservable<any[]>;
. This will enable any updates to be applied to ourtasks
variable.
Our component is going to be using the AngularFire library to communicate with ourFirebase
database. In order to do this, we will need to pass a reference to that library as a parameter in our constructor:
constructor(public navCtrl: NavController, public af: AngularFire) { … }
Since our list of tasks is going to be stored remotely, we can replace our initialtasks
array with the AngularFire request to return the data in the_tasks_subdirectory:
this.tasks = af.database.list('/tasks');
At the moment, this array should be empty since our database is empty. Before we can go ahead and perform a spot check on our app by running$ ionic serve
, we need to add a pipe to ourngFor
directive in thetasklist.html. In order to use theFirebaseListObservable
array, we need to tell Angular that this data will be asynchronously fetched. To do so, we just need to include theasync
pipe. Otherwise, when Ionic/Angular tries to render this template, there won’t be any data defined yet and it will throw an error. By adding theasync
pipe, Angular knows how to properly handle this information delay:
<
ion-item-sliding *ngFor="let task of tasks | async" #slidingItem
(ionSwipe)="removeTask(slidingItem, task)"
>
WHAT IS A PIPE?
_Pipes_are functions that will transform data within a template. Angular has several built-in pipes to perform common tasks like changing text case or formatting numbers.
You should be able to add new items to your task list. After you have added a few items, go ahead and quit the server, then relaunch it. You should see your items repopulate your list.