[Ionic3] Storageを使ったTODOアプリの作成

[Ionic3] Storageを使ったTODOアプリの作成

TODOアプリと作る

よく見かけるTODOアプリをIonic3で作っていきます。基本的には以下のサイトの写経ですが、データ保存のために、公式ドキュメントに書いてある Storage を利用します。

それから編集もできるようにします。

作ったものはgithubに有ります。

https://github.com/mntm0/ionic3_todo

プロジェクトの作成

まずはプロジェクトを空のテンプレートで作成します。

$ ionic start todo blank

とりあえず起動して確認します。

$ cd todo
$ ionic serve

一覧画面の作成

ホーム画面でTODO一覧が表示されるようにします。home.ts と home.html を次のように編集します。

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: 'title1', description: 'description1' },
      { title: 'title2', description: 'description2' },
      { title: 'title3', description: 'description3' }
    ];
  }

}
<ion-header>
  <ion-navbar color="primary">
    <ion-title>
      TODO
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-list>
    <ion-item *ngFor="let item of items">
      {{item.title}}
    </ion-item>
  </ion-list>
</ion-content>

この時点で画面は次のように表示されるはずです。

ionic3 で作成する TODOアプリ

ionViewDidLoad

NavController – Ionic API Documentation – Ionic Framework

ionViewDidLoad関数は、ページのライフサイクルイベントの一種で、ページがロードされた時に発生するイベントです。

home.ts

ここではページロード時に、items というプロパティを更新しています。items が画面にバインドされているTODOオブジェクトの配列です。この値が更新されると、画面上のTODOリストが更新されます。

home.htmlは見ての通りなので割愛。

入力画面

一覧表示ができたので、次に入力画面を作成します。まずはCLIでページの雛形を作成します。

$ ionic generate page InputItem

おそらく input-item という名前でディレクトリが作成され、その下にファイル群が生成されています。input-item.ts, input-item.html をそれぞれ以下のように編集します。

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

@IonicPage()
@Component({
  selector: 'page-input-item',
  templateUrl: 'input-item.html',
})
export class InputItemPage {

  public title: string;
  public description: string;

  constructor(public navCtrl: NavController,
    public navParams: NavParams,
    public viewCtrl: ViewController) {
  }

  ionViewDidLoad() {
    // 呼び出し時に引数が渡っていればそれを表示
    let item = this.navParams.get('item');
    if (item) {
      this.title = item.title;
      this.description = item.description;
    }
  }

  saveItem() {
    let inputItem = {
      title: this.title,
      description: this.description
    };
    // 入力された値を閉じる際に返す
    this.viewCtrl.dismiss(inputItem);
  }

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

}
<ion-header>
  <ion-navbar color="primary">
    <ion-navbar>
      <ion-title>AddItem</ion-title>
    </ion-navbar>
    <ion-buttons end>
      <button ion-button icon-only (click)="close()">
        <ion-icon name="close"></ion-icon>
      </button>
    </ion-buttons>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <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="primary" (click)="saveItem()">Save</button>
</ion-content>

input-item.ts

入力画面をモーダルで表示させるために、ViewController を利用するので import しておきます。

この入力画面は編集時にも利用するので、表示時に引数で値を受け取ることにします。ionViewDidLoad関数の中で、NavControllerから引数を受け取っています。

ViewController – Ionic API Documentation – Ionic Framework

ViewController の dismiss() で画面を閉じます。閉じる際に引数で入力後の値を返しているのが、saveItem() で、close() は単に画面を閉じます。入力値の保存は、呼び出し元になる home.ts で行うことになります。

input-item.html

ここは特に難しいことはしておらず、単純に入力値がバインドされるように設定しているのと、close(), saveItem() がそれぞれ呼ばれるようにしています。

データの新規登録

一覧画面から入力画面を呼び出す処理を作成します。その前にまずは app.module.ts に追記します。import, declarations, entryComponents の3箇所です。

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';
import { InputItemPage } from '../pages/input-item/input-item';

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

これで入力ページを呼び出すことができるようになります。まずは新規TODOの作成処理を作成してみます。次のとおりに home.ts, home.html を書き換えます。

import { Component } from '@angular/core';
import { NavController, ModalController } from 'ionic-angular';
import { InputItemPage } from '../input-item/input-item';

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

  public items = [];

  constructor(public navCtrl: NavController,
    public modalCtrl: ModalController) {

  }

  ionViewDidLoad() {

  }

  createItem() {
    // モーダルの生成
    let addModal = this.modalCtrl.create(InputItemPage);

    // dismissイベント(閉じられた時)の処理
    addModal.onDidDismiss((item) => {
      if(item) {
        // 入力があればそれを追加する
        this.items.push(item);
      }
    });

    // モーダルの表示
    addModal.present();
  }

}
<ion-header>
  <ion-navbar color="primary">
    <ion-title>
      TODO
    </ion-title>
    <ion-buttons end>
      <button ion-button icon-only (click)="createItem()">
        <ion-icon name="add"></ion-icon>
      </button>
    </ion-buttons>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-list>
    <ion-item *ngFor="let item of items">
      {{item.title}}
    </ion-item>
  </ion-list>
</ion-content>

home.ts

コンストラクタの初期データはもう不要なので削除します。代わりに createItem() を作成し、そこで入力画面をモーダルで表示する処理を作成します。onDidDismissで入力値が帰ってきていた場合、それを表示用の items に追加しています。

home.html

ナビゲーションバーに createItem() を呼び出すボタンを追加しています。

以上の処理が書けたら表示して、画面上からデータをできるようになっています。

データの編集

続けてデータの編集です。home.ts に editItem() を追加します。home.html で、これをクリックイベントで呼び出すように追加します。

editItem(item) {
  const index = this.items.indexOf(item);

  // モーダルの生成(引数にitemを渡す)
  let editItem = this.modalCtrl.create(InputItemPage, {
    item: item
  });

  // dismissイベント(閉じられた時)の処理
  editItem.onDidDismiss((item) => {
    if(item) {
      this.items[index] = item;
    }
  });

  // モーダルの表示
  editItem.present();
}
<ion-header>
  <ion-navbar color="primary">
    <ion-title>
      TODO
    </ion-title>
    <ion-buttons end>
      <button ion-button icon-only (click)="createItem()">
        <ion-icon name="add"></ion-icon>
      </button>
    </ion-buttons>
  </ion-navbar>
</ion-header>

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

データの作成と同じような感じですが、インデックスを保持して入力内容の item で置き換えてます。

データの削除

データの削除を行うためのボタンを追加して処理を追加します。home.ts に deleteItem() を追加します。home.html にボタンを追加して、これを呼び出します。

deleteItem(item) {
  const index = this.items.indexOf(item);
  this.items.splice(index, 1);
}
<ion-header>
  <ion-navbar color="primary">
    <ion-title>
      TODO
    </ion-title>
    <ion-buttons end>
      <button ion-button icon-only (click)="createItem()">
        <ion-icon name="add"></ion-icon>
      </button>
    </ion-buttons>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-list>
    <ion-item *ngFor="let item of items" (click)="editItem(item)">
      {{item.title}}
      <button ion-button color="danger" item-end (click)="deleteItem(item)">
        <ion-icon name="trash">Delete</ion-icon>
      </button>
    </ion-item>
  </ion-list>
</ion-content>

TODOのようなものが完成

ionic3 で作成する TODOアプリ

たぶんここまでで単純なTODOのようなものができているはずです。

ストレージにデータを保存したい

データをストレージに保存するようにしたいと思います。アプリを終了しても、データが保持されるようにします。基本的には公式ドキュメントに沿います。

Storage – Ionic Framework

まずは必要なものをインストールします。

$ ionic cordova plugin add cordova-sqlite-storage
$ npm install --save @ionic/storage

app.module.ts に追記します。

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 { IonicStorageModule } from '@ionic/storage';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { InputItemPage } from '../pages/input-item/input-item';

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

あとは home.ts にストレージでデータの処理を書くようにします。ページロード時にストレージからTODOのデータを読み出して表示し、データ更新後にストレージのデータを更新しています。

import { Component } from '@angular/core';
import { NavController, ModalController } from 'ionic-angular';
import { Storage } from '@ionic/storage';
import { InputItemPage } from '../input-item/input-item';

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

  public items = [];

  constructor(public navCtrl: NavController,
    public modalCtrl: ModalController,
    private storage: Storage) {

  }

  ionViewDidLoad() {
    // 初期データをストレージから読み込んで表示
    this.storage.get('items').then((items) => {
      this.items = items ? items : [];
    });
  }

  createItem() {
    // モーダルの生成
    let addModal = this.modalCtrl.create(InputItemPage);

    // dismissイベント(閉じられた時)の処理
    addModal.onDidDismiss((item) => {
      if(item) {
        // 入力があればそれを追加する
        this.items.push(item);
        this.updateStorageData();
      }
    });

    // モーダルの表示
    addModal.present();
  }

  editItem(item) {
    const index = this.items.indexOf(item);

    // モーダルの生成(引数にitemを渡す)
    let editItem = this.modalCtrl.create(InputItemPage, {
      item: item
    });

    // dismissイベント(閉じられた時)の処理
    editItem.onDidDismiss((item) => {
      if(item) {
        this.items[index] = item;
        this.updateStorageData();
      }
    });

    // モーダルの表示
    editItem.present();
  }

  deleteItem(item) {
    const index = this.items.indexOf(item);
    this.items.splice(index, 1);
    this.updateStorageData();
  }

  updateStorageData() {
    // ストレージのデータを更新
    this.storage.set('items', this.items).then(() => {
    });
  }

}

後付の無理矢理感はありますが、やりたいことは実現できています。ストレージは key/value のペアを保存する方式なので、RDBのような使い方はできません。内部でデータがどのように保存されるかは、プラグイン側がうまくやってくれるようですが、基本的にはSQLiteに保存されているみたいです。driver というプロパティ値で確認できます。

かなりお手軽に実装できるのでちょっとしたデータ保存には最適だと思われます。

ネイティブの機能として SQLite をプラグインで使用することもできるみたいです。

以上。

Ionicカテゴリの最新記事