Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Create A Simple Todo List App Using Ionic 2 For Android And iOS

TwitterFacebookRedditLinkedInHacker News

I’ve created a few tutorials around Ionic 2 while it was in its early alpha stage up until now. These tutorials explain how to use the bits and pieces that the framework or Angular offers, but I never demonstrated how to make a functional application. Seeing how to put the pieces together makes a huge difference when learning a new technology.

We’re going to see how to build a simple todo list type Android and iOS application using Ionic 2, Angular, and TypeScript.

Creating a New Ionic 2 Project

To make things as easiest as possible to understand, we’re going to start with a fresh project and work our way up. Using the Command Prompt (Windows) or Terminal (Mac and Linux), execute the following:

ionic start TodoApp blank --v2
cd TodoApp
ionic platform add ios
ionic platform add android

There are a few things to note in the above commands. First you’ll notice the --v2 tag during the project creation. This means we will be creating an Ionic 2 project that uses TypeScript. It is only possible to use this tag if we have the correct Ionic CLI. The second thing to be aware of is around the iOS platform. We cannot build for the iOS platform unless we are using a Mac computer.

Ionic 2 Simple Todo List

Creating the Main List Page

We are going to be creating a two page application, but for now we’re going to focus on the main page. This page will be responsible for showing the list of todo items. We’ll also be able to remove todo items from this page as well.

Create the following directories and files whatever way makes sense for you. I’m on a Mac, so I’m going to use mkdir and touch to do this from the Terminal.

mkdir app/pages/todos
touch app/pages/todos/todos.ts
touch app/pages/todos/todos.html
touch app/pages/todos/todos.scss

Let’s focus on the logic file first. Open the project’s app/pages/todos/todos.ts file and include the following code. Don’t worry, we’ll break it down after.

import {Page, NavController} from 'ionic-angular';
import {AddPage} from "../add/add";

@Page({
    templateUrl: 'build/pages/todos/todos.html'
})
export class TodosPage {

    public todoList: Array<string>;

    constructor(private nav: NavController) { }

    onPageDidEnter() {
        this.todoList = JSON.parse(localStorage.getItem("todos"));
        if(!this.todoList) {
            this.todoList = [];
        }
    }

    delete(index: number) {
        this.todoList.splice(index, 1);
        localStorage.setItem("todos", JSON.stringify(this.todoList));
    }

    add() {
        this.nav.push(AddPage);
    }

}

We haven’t created the page yet, but let’s go ahead and import it anyways. We’re not going to try to run the application right away so we will be fine. We’re also importing the NavController which will allow us to navigate to the AddPage that we’ve yet to create.

In the @Page section we define the HTML file that will be paired with this particular TypeScript file.

This brings us to the actual TodosPage class. We will have one public variable being our list of todo items. By making it public we can access it from the HTML file and render it to the screen.

constructor(private nav: NavController) { }

In the above constructor method we aren’t actually initializing any variables, but we are defining the NavController to be used throughout our application. You might be wondering why we aren’t initializing the todoList variable in the constructor method, but instead the following:

onPageDidEnter() {
    this.todoList = JSON.parse(localStorage.getItem("todos"));
    if(!this.todoList) {
        this.todoList = [];
    }
}

We’re using the framework reserved onPageDidEnter method because we want to hit two birds with one stone. The page constructor method will only fire on navigation push events, or in other words, navigate to events. When we try to navigate back, the constructor won’t fire. The onPageDidEnter event will trigger in both scenarios which is why we are reading the todo items from local storage in it. If there is nothing in local storage we will initialize an empty array.

delete(index: number) {
    this.todoList.splice(index, 1);
    localStorage.setItem("todos", JSON.stringify(this.todoList));
}

The next method we see is the delete method. It will be used for deleting items in the list based on whatever index is provided. Using the splice method we can delete an item at a particular index. Once this is done, we will re-serialize the array and store it in local storage.

add() {
    this.nav.push(AddPage);
}

Finally we have the add method. This is where we will perform the navigation to the AddPage that we’ll create soon.

Now we can focus on creating the HTML file that goes with the TypeScript file we just created. Open the project’s app/pages/todos/todos.html and include the following markup:

<ion-header>
    <ion-navbar>
        <ion-title>Ionic 2 Example - List</ion-title>
        <ion-buttons end>
            <button (click)="add()"><ion-icon name="add"></ion-icon></button>
        </ion-buttons>
    </ion-navbar>
</ion-header>

<ion-content class="todos">
    <ion-list>
        <ion-item-sliding *ngFor="let todo of todoList; let i = index">
            <ion-item>
                <h2>{{ todo }}</h2>
            </ion-item>
            <ion-item-options>
                <button danger (click)="delete(i)">
                    <ion-icon name="trash"></ion-icon>
                    Delete
                </button>
            </ion-item-options>
        </ion-item-sliding>
    </ion-list>
</ion-content>

Let’s break down the HTML file like we did the TypeScript file. So starting with the page’s navigation bar:

<ion-header>
    <ion-navbar>
        <ion-title>Ionic 2 Example - List</ion-title>
        <ion-buttons end>
            <button (click)="add()"><ion-icon name="add"></ion-icon></button>
        </ion-buttons>
    </ion-navbar>
</ion-header>

We define the page title and add a single button to the right side of the header. This button will only be an icon, but when it is pressed it will trigger the add function that we created. This is made possible from the (click) tag.

Now we’re brought into the core page content.

<ion-content class="todos">
    <ion-list>
        <ion-item-sliding *ngFor="let todo of todoList; let i = index">
            <ion-item>
                <h2>{{ todo }}</h2>
            </ion-item>
            <ion-item-options>
                <button danger (click)="delete(i)">
                    <ion-icon name="trash"></ion-icon>
                    Delete
                </button>
            </ion-item-options>
        </ion-item-sliding>
    </ion-list>
</ion-content>

We have an ion-list in the above, but it is not a standard list. Each element in the list will be a sliding item, meaning we can swipe it. We create each item in the list by looping through the public todoList array. We also define our looping index as it will be relevant when it comes time to delete items.

When a list row is swiped, it will expose a button which a trash icon. When this button is pressed, the delete method will be called and we will pass the index of the list item that was pressed.

When it comes to our stylesheet, open the project’s app/pages/todos/todos.scss and include the following class:

.todos { }

We aren’t using any special styles, but at least we know it is available to us should we decide we want to make some changes.

Creating the Page for Adding Todo Items

Now let’s have a look at creating the second page of our application. This page will be responsible for adding items to the local storage which will then be loaded in our main page. You don’t have to use local storage. You can use SQLite or another technology if you’d like, but for this example we’ll just stick with local storage.

Just like with the first page we need to create some directories and files. Again, I’ll be using mkdir and touch, but you create them however you want.

mkdir app/pages/add
touch app/pages/add/add.ts
touch app/pages/add/add.html
touch app/pages/add/add.scss

With everything created, open the project’s app/pages/add/add.ts file and include the following code:

import {Page, NavController} from 'ionic-angular';

@Page({
    templateUrl: 'build/pages/add/add.html'
})
export class AddPage {

    public todoList: Array<string>;
    public todoItem: string;

    constructor(private nav: NavController) {
        this.todoList = JSON.parse(localStorage.getItem("todos"));
        if(!this.todoList) {
            this.todoList = [];
        }
        this.todoItem = "";
    }

    save() {
        if(this.todoItem != "") {
            this.todoList.push(this.todoItem);
            localStorage.setItem("todos", JSON.stringify(this.todoList));
            this.nav.pop();
        }
    }

}

Just like with the TodosPage class, let’s break down this file as well.

In the @Page section we are defining the HTML file that will be paired with this particular TypeScript file. Most of what we are doing happens in the AddPage class.

In the class we have two variables, one public and one private. The todoList variable is private because we don’t need to render it on the screen, but we do need to use it. The todoItem variable is public because we do need to bind it to a form on the screen.

constructor(private nav: NavController) {
    this.todoList = JSON.parse(localStorage.getItem("todos"));
    if(!this.todoList) {
        this.todoList = [];
    }
    this.todoItem = "";
}

Unlike the previous page, all of our initialization logic will happen in the constructor method. This is because pop events will never happen on this page. You could use onPageDidEnter if you really wanted to.

save() {
    if(this.todoItem != "") {
        this.todoList.push(this.todoItem);
        localStorage.setItem("todos", JSON.stringify(this.todoList));
        this.nav.pop();
    }
}

In the save method we first check to make sure the todoItem variable has data in it. If it does, push it into the list and save it to local storage. When saving is complete we will pop back in the navigation stack to the previous page.

Now we can focus on the HTML file that is paired with this TypeScript file. Open the project’s app/pages/add/add.html file and include the following markup:

<ion-header>
    <ion-navbar>
        <ion-title>Ionic 2 Example - Create</ion-title>
        <ion-buttons end>
            <button (click)="save()"><ion-icon name="checkmark"></ion-icon></button>
        </ion-buttons>
    </ion-navbar>
</ion-header>

<ion-content class="add">
    <ion-list>
        <ion-item>
            <ion-label floating>Todo Item</ion-label>
            <ion-input type="text" [(ngModel)]="todoItem"></ion-input>
        </ion-item>
    </ion-list>
</ion-content>

Just like last time, we’ll break down the HTML to understand what is happening.

<ion-header>
    <ion-navbar>
        <ion-title>Ionic 2 Example - Create</ion-title>
        <ion-buttons end>
            <button (click)="save()"><ion-icon name="checkmark"></ion-icon></button>
        </ion-buttons>
    </ion-navbar>
</ion-header>

In the navigation header we have a title and a single button to the right of the title. This button will be a checkmark to represent it is used for saving the data. When the (click) event is triggered, the save method will be called in the TypeScript file.

<ion-content class="add">
    <ion-list>
        <ion-item>
            <ion-label floating>Todo Item</ion-label>
            <ion-input type="text" [(ngModel)]="todoItem"></ion-input>
        </ion-item>
    </ion-list>
</ion-content>

In the core content we have a list of input elements, or in our case a single input element. It is bound to the todoItem variable in our TypeScript file via the [(ngModel)] tag.

When it comes to our stylesheet, open the project’s app/pages/add/add.scss and include the following class:

.add { }

We aren’t using any special styling, but at least we know it is available to us.

Updating the Application Theme

We didn’t specifically add any styles, but we layed down the foundation for the future. This means we should add them to the theme collection.

Open the project’s app/theme/app.core.scss and include the following lines:

@import '../pages/todos/todos';
@import '../pages/add/add';

Now we can make our application more beautiful than it already is if we wanted to.

Updating the Parent Application Component

When we created our project we started with a HomePage page. We aren’t using it so we want to replace it with the TodosPage that we had created.

Open the project’s app/app.ts file and include the following code:

import {App, Platform} from 'ionic-angular';
import {StatusBar} from 'ionic-native';
import {TodosPage} from './pages/todos/todos';

@App({
    template: '<ion-nav [root]="rootPage"></ion-nav>',
    config: {}
})
export class MyApp {

    rootPage: any = TodosPage;

    constructor(platform: Platform) {
        platform.ready().then(() => {
            StatusBar.styleDefault();
        });
    }

}

Essentially all that changed was the references to HomePage. After updating them, the application should be good to go.

Conclusion

You just saw how to create a simple Ionic 2 TypeScript application. It accomplished quite a bit. We saw how to navigate between pages, persist data, as well as delete data. As mentioned in the tutorial, if you wanted to use something beyond local storage, like SQLite, you can without any issues.

With this foundation, you should be able to take these skills and build a more complex application that is more useful to people in one of the app stores.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in C#, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Unity. Nic writes about his development experiences related to making web and mobile development easier to understand.