Go Offline – add to do list

Users of any data-driven mobile application should be able to read and write data without an Internet connection. When the app reconnects to the Internet, the details should be synced to the server. We will build an offline mobile app that stores data locally and syncs with an actual database when the connectivity is available.

What are we learning?

  • Use Pouch DB, a JavaScript DB, with Ionic which syncs with a hosted DB.
  • Installing ionic pouchdb plugin.
  • Connect our local DB to remote IBM Cloudant DB, a NoSQL hosted DB from IBM.
  • Code a mobile application which will sync with a hosted DB.

What are we learn from the Angular point?

1. FormGroup and Formbuilder from angular forms to validate the form.
  <form [formGroup]="todoForm" (ngSubmit)="addToDo()">
 
 2. Submit button is disable, until all input field in form have inserted.
 <button ion-button block [disabled]="!todoForm.valid" type="submit">Add To Do</button>

What are we building?

We will build a to-do app to which we can add to-dos. These individual to-do items will be stored in the
Cloudant db. If the device is offline or has no connectivity with the Cloudant DB, then the local db is stored
with the updates. Data sync occurs when the Cloudant DB becomes available.

We have to create free account on cloudant.com and create database as 

 

Building our apps:

ionic start OfflineToDo blank

Introduction to IBM Cloudant DB
IBM Cloudant DB is a managed NoSQL JSON database. Following are the features of IBM Cloudant DB:

  • Flexibility–Data is accessible as JSON and provides schema flexibility.
  • Offline apps–Cloudant Sync provides a better, faster user experience.

Installing the PouchDb 
It provides an easy way to replicate the data from a remote db. Following are the features of PouchDB:

  • In-Browser db–Pouch DB can run in various browsers and helps to execute queries faster, as there is no need to execute queries over network.
  • Lightweight–It is a very lightweight db, which can be installed using scripts even in mobile devices.
  • Easy to implement—It is a JavaScript-based db and is easy to implement.
  • Open Source–It is an open source-based db.
  • Document-based database—data is stored in granular data types, with associative mapping of keys to documents.

Step 1:

npm install pouchdb

 

Step 2: Creating data service to connect/retrieve/delete to remote db.

In app folder creates a new folder called provider, add a new file called dataservice.ts. The dataservice provider will contain all code related to the database.

ionic generate provider dataservice

As we need pouchdb and promise for database-related implementation, import the required ones in dataservice.ts file import PouchDB from ‘pouchdb’;

PouchDB from ‘pouchdb‘;
import ‘rxjs/add/operator/toPromise‘;

Add the following line of code in src/providers/dataservice.ts

import { Injectable } from '@angular/core';
import 'rxjs/add/operator/map';
import PouchDB from 'pouchdb';
import 'rxjs/add/operator/toPromise';

@Injectable()

export class Dataservice {
  private db: any ;
  //cloudant username

  private userName = 'n-----n';
  private password= 'blue1---1';
  private dbURL = 'https://n---ten.cloudant.com/tododb';
  //array of todos

  private toDos = [];

  constructor() {
    this.db = new PouchDB('tododb');
      let options = {
        live: true,
        retry: true,
        continuous: true,
        auth: {
        username: this.userName,
        password: this.password
      }
    }
  //Sync the db with Cloudant

  this.db.sync(this.dbURL, options);
  }

  addToDo(doc) {
    return this.db.post(doc);
  }
  deleteToDo(doc) {
    return this.db.remove(doc);
  }

  retrieveToDos(){
    return new Promise<any>(resolve => {
      this.db.allDocs({include_docs: true}).then((result) => {
      if (result.total_rows > 0){
        result.rows.map((row) => {
          this.toDos.push(row.doc);
          resolve(this.toDos);
        });
      }
      else {
        resolve(this.toDos);
      }
      this.db.changes({live: true, since: 'now', include_docs: true}).
        on('change', (change) => {
          this.onChange(change);
        });
      }).catch((error) => {
        console.log(error);
      });
    });
  }

  onChange(change){
    let changedDoc = null;
    let changedIndex = null;
    this.toDos.forEach((doc, index) => {
      if(doc._id === change.id){
        changedDoc = doc;
        changedIndex = index;
      }
    });

    //Handle deleted document

    if(change.deleted){
      this.toDos.splice(changedIndex, 1);
    }
    else {
      //Handle the updates

      if(changedDoc){
        this.toDos[changedIndex] = change.doc;
      }
      //Handle additions

      else {
        this.toDos.push(change.doc);
      }
    }
  }
}

Note:

  1. When the app is initialized, the dataservice constructor is called which will sync the local DB with the remote DB we have tododb database in cloudant.com.
  2. We have specified the authentication for cloudant with login and password.
  3. To add and delete ToDo items add the respective data service methods in the Dataservice class as
    shown in the
      addToDo(doc) {
        return this.db.post(doc);
      }
      deleteToDo(doc) {
        return this.db.remove(doc);
      }
  4. To retrieve Todo items, add the method retrieveToDos(). This method returns a promise which has the “toDos = []” as an array. Also if the data is changed, it will call the method onChange to update the data accordingly.
  5. If the data is updated, we need to update the toDos array accordingly. So the onChange method in onChange(change) accordingly updates by checking the modified doc and the index.

Now that we have the data service layer ready, it’s time to work on the view part.

Step 3: Listing To Do or Creating a view for Todo List

Now we will create a view for our todo apps. In src/pages/home.ts add the following code.

import { Component } from '@angular/core';
import { NavController, Platform } from 'ionic-angular';
import { Dataservice} from '../../providers/dataservice'; 
import {AddtodoPage} from '../addtodo/addtodo';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html',
  providers: [Dataservice]
})
export class HomePage {
  public toDos : any[];
  public noToDo : boolean;

  constructor(private todoService: Dataservice, private navController: NavController, private platform: Platform) {
    this.navController = navController;
    this.platform.ready().then(() => {
      this.todoService.retrieveToDos().then(data => {
      this.toDos = data;
      if(this.toDos.length > 0 ) {
        this.noToDo = false;
      }
      else {
        this.noToDo = true;
      }
    })
    .catch(console.error.bind(console));
    });
  }

  showToDoPage() {
	  this.navController.push(AddtodoPage);
  }

  delete(item) {
    this.todoService.deleteToDo(item);
  }
}

Note :

  1. Add our service provider DataService in providers: [Dataservice] in home.ts
  2. Import platform and Dataservice at head of home.ts
  3. In the constructor, we have code to call retrieve the todo list data from DataService.
  4. We can delete the todo list by dragging the list from left to right
  5. We have showToDoPage() method to navigate to the new page to add new to do list on AddtodoPage.

Adding the user identification (UI) to display the list of To Do. Add the following the code in in src/pages/home/home.html.

<ion-header>
  <ion-navbar>
    <ion-title>To Do</ion-title>
    <ion-buttons end>
      <button (click)="showToDoPage()">
        <ion-icon name="add"></ion-icon>
      </button>
    </ion-buttons>
  </ion-navbar>
</ion-header>

<ion-content class="home">
  <div *ngIf="noToDo">
    <br/>Click on + to add to do items.
  </div>
  <ion-list inset>
    <ion-item-sliding *ngFor="let toDo of toDos" >
      <ion-item>
        <h2>{{ toDo.name }} - {{ toDo.createdTime | date:'yMMMMd' }}</h2>
        <p class="item-description">{{toDo.description}}</p>
      </ion-item>
      <ion-item-options side="right">
        <button ion-button (click)="delete(toDo)">
          <ion-icon name="ion-trash-a"></ion-icon>Delete
        </button>
      </ion-item-options>
    </ion-item-sliding>
  </ion-list>
</ion-content>
  1. Here we listing all the todo list added to the remote database.
  2. showToDoPage(), To add new todo list by navigate to new page.
  3. We have the delete button to delete the todo list. This sliding option provides a way to delete the To Do

 

 

Step 4: Add a new page for adding new to do list

Add a new page for adding To Do using ionic generate

$ ionic generate page addtodo

We have to import import { Dataservice} from ‘../../providers/dataservice’; import { FormBuilder, FormGroup, Validators } from ‘@angular/forms’;

Add the following code in src/pages/addtodo/addtodo.ts

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { Dataservice} from '../../providers/dataservice'; 
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'page-addtodo',
  templateUrl: 'addtodo.html',
  providers: [Dataservice]
})
export class AddtodoPage {
  todoForm: FormGroup;

  constructor(private navController: NavController, private fb: FormBuilder, private todoService: Dataservice) {
    this.todoForm = fb.group({
      'name': ['', Validators.compose([Validators.required,Validators.pattern
      ('[a-zA-Z, ]*'),Validators.minLength(3),Validators.maxLength(100)])],
      'description':['']
    });
  }

  addToDo() {
    let date = new Date();
    let newDoc = {
      'name': this.todoForm.value.name,
      'description':this.todoForm.value.description,
      'createdTime': date.getTime()
    };
    //Add the to do using the data service

    this.todoService.addToDo(newDoc);

    //After the addition navigate to the list view

    this.navController.popToRoot();
  }
  
  ionViewDidLoad() {
    console.log('ionViewDidLoad Addtodo');
  }

}

Add the following code in src/pages/addtodo/addtodo.html

<ion-header>
  <ion-navbar>
    <ion-title>Add To Do</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <form [formGroup]="todoForm" (ngSubmit)="addToDo()">
    <ion-list>
      <ion-item>
        <ion-label color="primary">Name</ion-label>
        <ion-input placeholder="Name" formControlName="name"></ion-input>
      </ion-item>
      <ion-item>
        <ion-label color="primary">Description</ion-label>
        <ion-input placeholder="Description" formControlName="description" ></ion-input>
      </ion-item>
      <ion-item>
        <button ion-button block [disabled]="!todoForm.valid" type="submit">Add To Do</button>
      </ion-item>
    </ion-list>
  </form>
</ion-content>

We can see that ADD TO DO button is disable first, this can be achieved as

<button ion-button block [disabled]="!todoForm.valid" type="submit">Add To Do</button>
  1. When the todForm is empty or when both name and description input have no data inserted then the Add to do button is disabled. When both inputs in the form are entered then the button is enabled.
  2. In form (ngSubmit)=”addToDo() ” we have addToDo to add new to do list to the database.

At last, we have to add reference of AddtodoPage in app.module.ts file and no need to add DataService as a provider, here we can provide DataService as provider directly in the home.ts and addtodo.ts file directly.

import { BrowserModule } from '@angular/platform-browser';

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

@NgModule({
  declarations: [
    MyApp,
    HomePage,
    AddtodoPage
  ],
  imports: [
...
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage,
    AddtodoPage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}