Ionic 2: Build a Todo List Application

19 minutes, 11 seconds

14908421_1335704769786807_468766263245223812_n

On this post we will learn how to make app with Ionic 2. Make sure you have setup the Ionic environment first. Setup Ionic Environment.

1. Generate a New Ionic Project

We will create a new project based on “blank” template. Open terminal or command prompt.


ionic start ionic-todo blank --v2

1.1 Project Structure

It will generate project file on ionic-todo folder. Open the folder with text editor like ‘Sublime Text’ which will looked like this

Inside the folder that was created, we have a typical Cordova project structure where we can install native plugins, and create platform-specifict project files.

We will look at src folder.

src folder

./src/index.html is the main entry point for the app, though its purpose is to set up script and CSS includes and bootstrap, or start running, our app. We won’t spend much of our time in this file.

All the components will be in the src folder (including the root component in the app folder, and all of our page components in the pages folder). A component will consist of a template (.html file, class definition (.ts file) and styling (.scss file).

You might also want to create services for data. The services also refered as ‘providers’ and will be placed in providers folder.

Right now, there is just single Home Page component that sets up the default view.

We look at this file src/app/app.component.ts. This is a TypeScript file, higher level scripting before compile down to traditional JavaScript that browser needs.


import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar, Splashscreen } from 'ionic-native';

import { HomePage } from '../pages/home/home';

@Component({
  template: ``
})
export class MyApp {
  rootPage = HomePage;

  constructor(platform: Platform) {
    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 app.component.ts file defines our root component.


rootPage = HomePage;

To change views in an Ionic application you can either change this root page, or push and pop views.

1.2 Run The Project

On the terminal change to the project directory.


sudo ionic serve

2. Setting Up the Home Page

Let’s setup our todo lists template.

Open src/pages/home/home.html and you will find something like this.

 
<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>

It look like this on the browser.

Home Page

First, we change the title to todos. Then, we will add a button ion-buttons at the ion-navbar. We also put end so that the button will be placed in the “end” position.

                 
<ion-buttons end>
    <button ion-button icon-only (click)="addItem()"><ion-icon name="add-circle"></ion-icon></button>
</ion-buttons>

Inside the ion-content we will put ion-list which like a table view.

         
<ion-list>
  <ion-item *ngFor="let item of items" (click)="viewItem(item)">{{item.title}}</ion-item>
</ion-list>

The full code for src/pages/home/home.html is like this.

         
<ion-header>
  <ion-navbar>
    <ion-title>
      Todos
    </ion-title>
    <ion-buttons end>
        <button ion-button icon-only (click)="addItem()"><ion-icon name="add-circle"></ion-icon></button>
    </ion-buttons>
  </ion-navbar>
</ion-header>
<ion-content>
  <ion-list>
    <ion-item *ngFor="let item of items" (click)="viewItem(item)">{{item.title}}</ion-item>
  </ion-list>
</ion-content>

2.1 Creating the Class Definition

Now the view has laid out. We need to setup the class linked to home. Open the src/pages/home/home.ts file, it will looked like this.


import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

  constructor(public navCtrl: NavController) {
    
  }

}

Change to this code.


import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
 
@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
 
  public items;
 
  constructor(public navCtrl: NavController) {
 
  }
 
  ionViewDidLoad(){
 
    this.items = [
      {title: 'hi1', description: 'test1'},
      {title: 'hi2', description: 'test2'},
      {title: 'hi3', description: 'test3'}
    ];
 
  }
 
  addItem(){
 
  }
 
  viewItem(){
 
  }
 
}

By using NavController framework, we will be able to push


this.navCtrl.push(SOME_PAGE);

and pop


this.navCtrl.pop()

Adding New Page : Items

We are going to create a new page called AddItemPage. This will be a simple form where we will input a title and description to create a new todo.

We add page with this command.


ionic g page AddItemPage

Whenever we create a new page, we need to ensure that it is imported into our app.module.ts, and declared in the declarations and entryComponents arrays.

Modify src/app/app.module.ts to reflect the following:


import { NgModule } from '@angular/core';
import { IonicApp, IonicModule } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { AddItemPage } from '../pages/add-item-page/add-item-page';

@NgModule({
  declarations: [
    MyApp,
    HomePage,
    AddItemPage
  ],
  imports: [
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage,
    AddItemPage
  ],
  providers: []
})
export class AppModule {}

Setting up the AddItem View

Open src/pages/add-item-page/add-item-page.html it will looked like this.

         
<ion-header>
  <ion-navbar>
    <ion-title>AddItemPage</ion-title>
  </ion-navbar>
</ion-header>
<ion-content padding>
</ion-content>

Then edit to this.

         
<ion-header>
  <ion-toolbar>
    <ion-title>
        Add Item
    </ion-title>
        <ion-buttons end>
            <button ion-button icon-only (click)="close()"><ion-icon name="close"></ion-icon></button>
        </ion-buttons>
    </ion-toolbar>
</ion-header>

<ion-content>
    <ion-list>

      <ion-item>
        <ion-label floating>Title</ion-label>
        <ion-input type="text" [(ngModel)]="title"></ion-input>
      </ion-item>

      <ion-item>
        <ion-label floating>Description</ion-label>
        <ion-input type="text" [(ngModel)]="description"></ion-input>
      </ion-item>

    </ion-list>

    <button full ion-button color="secondary" (click)="saveItem()">Save</button> 

</ion-content>

We have another button defined, this time calling a saveItem function that we will define in app-item-page.ts shortly. We also have a button that references a close function – since we will eventually launch this page as a Modal we want the ability to dismiss the page, so we will also be defining this function in app-item-page.ts.

We also have 2 inputs, on them we have [(ngModel)], which sets up two-way data binding for us.

Setting up the Add Item Class


import { Component } from '@angular/core';
import { NavController, ViewController } from 'ionic-angular';

@Component({
  selector: 'page-add-item-page',
  templateUrl: 'add-item-page.html'
})
export class AddItemPage {
    
  title;
  description;

  constructor(public navCtrl: NavController, public vc: ViewController) {}

  ionViewDidLoad() {
    console.log('Hello AddItemPage Page');
  }

  saveItem(){

    let newItem = {

      title: this.title,
      description: this.description

    };

    this.vc.dismiss(newItem);

  }

  close(){
    this.vc.dismiss();
  }

}

Saving Items in the Home Page

Let’s open back the home.ts file. We will sending data back to Home page to save the data, so we have to setup the connection.

Lets update Home page class home.ts.


import { Component } from '@angular/core';
import { ModalController, NavController } from 'ionic-angular';
import { AddItemPage } from '../add-item-page/add-item-page'

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
 
  public items = [];
 
  constructor(public navCtrl: NavController, public modalCtrl: ModalController ) {
    
  }
 
  ionViewDidLoad(){
  
  }
 
  addItem(){
 
    let addModal = this.modalCtrl.create(AddItemPage);
    // call back when modal dismissed
    addModal.onDidDismiss((item) => {
      if(item){
        this.saveItem(item);
      }
    });
    addModal.present();
    
  }
 
  viewItem(item){
  }

  saveItem(item){
    this.items.push(item);
  }
 
}

2016-10-30 01_00_50.gif

Viewing Items

We also want the ability to click on specific item, then view the details of that item. To do this, we have to generate a new page.


ionic g page ItemDetailPage

Modify the app.module.ts to this following


import { NgModule } from '@angular/core';
import { IonicApp, IonicModule } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { AddItemPage } from '../pages/add-item-page/add-item-page';
import { ItemDetailPage } from '../pages/item-detail-page/item-detail-page';
 
@NgModule({
  declarations: [
    MyApp,
    HomePage,
    AddItemPage,
    ItemDetailPage
  ],
  imports: [
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage,
    AddItemPage,
    ItemDetailPage
  ],
  providers: []
})
export class AppModule {}

Then let’s setup the view of item-detail-page.html to this code

     
<ion-header>
  <ion-navbar>
    <ion-title>
      {{title}}
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content>
  <ion-card>
    <ion-card-content>
      {{description}}
    </ion-card-content>
  </ion-card>
</ion-content>

We’re just using the directive to fancy it up a bit, and outputting the values of title and description which will be defined in item-detail-page.ts.

Modify the item-detail-page.ts to this following:


import { Component } from '@angular/core';
import { NavParams } from 'ionic-angular';
 
@Component({
  selector: 'page-item-detail-page',
  templateUrl: 'item-detail-page.html'
})
export class ItemDetailPage {
 
  title;
  description;
 
  constructor(public navParams: NavParams){
 
  }
 
  ionViewDidLoad() {
    this.title = this.navParams.get('item').title;
    this.description = this.navParams.get('item').description;
  }
 
}

When we push this page we will pass in the data of the item that was clicked, and then we just set the title and description to that of the item using NavParams.

Now we have to update at home.ts, update the viewItem function.


viewItem(item){
  this.navCtrl.push(ItemDetailPage, {
    item: item
  });
}

2016-10-30 01_59_09.gif

Saving Data Permenantly with Storage

The todo application will basically work now, but the data isn’t being stored anywhere so as soon as you refresh the application you will lose all of your data (not ideal).

What we’re going to do now is create a service called Data that will handle storing and retrieving data for us. We will use the Storage service Ionic 2 provides to help us do this. Storage is Ionic’s generic storage service, and it handles storing data in the best way possible whilst providing a consistent API for us to use.

This means that if you are running on a device and have the SQLite plugin installed, then it will use a native SQLite database for storage, otherwise it will fall back to using browser based storage (which can be wiped by the operating system).

Run this command to generate Data service


ionic g provider Data

Open ./provider/data.ts and modify like the following:


import { Storage } from '@ionic/storage';
import {Injectable} from '@angular/core';
 
@Injectable()
export class Data {
 
  constructor(public storage: Storage){
 
  }
 
  getData() {
    return this.storage.get('todos');  
  }
 
  save(data){
    let newData = JSON.stringify(data);
    this.storage.set('todos', newData);
  }

}

Instead of using the @Component decorator, we are instead declaring this class as an @Injectable. In our constructor, we set up a reference to the Storage service.

Save function simply takes in the array of all of the items and saves it to storage, whenever the items change we will call this function.

We will also need to set up the Storage service, as well as the Data provider, in our app.module.ts file.


import { NgModule } from '@angular/core';
import { IonicApp, IonicModule } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { AddItemPage } from '../pages/add-item-page/add-item-page';
import { ItemDetailPage } from '../pages/item-detail-page/item-detail-page';
import { Storage } from '@ionic/storage';
import { Data } from '../providers/data';
 
@NgModule({
  declarations: [
    MyApp,
    HomePage,
    AddItemPage,
    ItemDetailPage
  ],
  imports: [
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage,
    AddItemPage,
    ItemDetailPage
  ],
  providers: [Storage, Data]
})
export class AppModule {}

Notice that we have declared these services in the providers array, rather than in the declarations or entryComponents arrays.

Now we need to update home.ts to make use of this new service.


import { Component } from '@angular/core';
import { ModalController, NavController } from 'ionic-angular';
import { AddItemPage } from '../add-item-page/add-item-page'
import { ItemDetailPage } from '../item-detail-page/item-detail-page';
import { Data } from '../../providers/data';
 
@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
 
  public items = [];
 
  constructor(public navCtrl: NavController, public modalCtrl: ModalController, public dataService: Data) {
 
    this.dataService.getData().then((todos) => {
       if(todos){
        this.items = JSON.parse(todos); 
      }
     });
 
  }
 
  ionViewDidLoad(){
 
  }
 
  addItem(){
     let addModal = this.modalCtrl.create(AddItemPage);
     addModal.onDidDismiss((item) => {
           if(item){
            this.saveItem(item);
          }
     });
    addModal.present();
  }
 
  saveItem(item){
    this.items.push(item);
    this.dataService.save(this.items);
  }
 
  viewItem(item){
    this.navCtrl.push(ItemDetailPage, {
      item: item
    });
  }
}

Let’s discuss what we changed here for Storage, first we import Data service class and passing through constructor. We are still setting the items to be empty to start off with, and fetching the data using the data service. If there are any items returned it will set items to that, but if there is not it will just set it to an empty array again.

It’s important to note here that getData returns a promise not the data itself. Fetching data from storage is asynchronous which means our application will continue to run while the data loads. A promise allows us to perform some action whenever that data has finished loading, without having to pause the whole application.

Finally, we also add a call to the save function in the data service when a new item is being added. So now this function will update our items array with the new data straight away, but it will also save a copy to the data service so that it is available next time we come back to the application.

Conclusion

It’s feel different from develop mobile apps natively using wonderful specific tools like Xcode and Android Studio. With Ionic development we can use regular IDE like Sublime Text, which has some drawback such no code compleetion etc.

But it’s a good solution for lower cost entry for mobile apps that doesn’t need so much power on the smartphone rather just to be an website inside the app with serveral advantages.

Resources:

Build Todo App From Scratch with Ionic 2

Ionic 2 Tutorial Series

Ionic Documentation V2