中安拓也のブログ

プログラミングについて書くブログ

【JavaScript】Ionicで架空のECアプリを作成する #6 - タブバーのアイコンにバッジをつける

前回の記事はこちら

ハイブリットモバイルアプリフレームワークのIonicを使って、架空のアパレルショップにおける注文アプリを作成する。

第6回目である今回は、カートアイコンにショッピングカート内の商品数を表示するバッジをつけるところまで

  • デモ

こちらのURLで、完成版のアプリを実際に操作することができます

https://l08084.github.io/ionic-sample-shopping-app/www

  • GitHubリポジトリ

github.com

バージョン情報

Angular(JavaScriptのWebフレームワーク)ベースの、ハイブリットモバイルアプリ用フレームワークである「Ionic」を使っている

  • ionic-angular@3.9.2
  • Angular@5.2.10
  • Google Chrome バージョン: 60.0.3112.113

タブバーにバッジを表示する

Ionicのタブには、インプットプロパティtabBadgeがあるため、タブバーのアイコンにバッジを表示するだけなら、簡単にすみます。

src/pages/tabs/tab.html:

<ion-tabs>
  <ion-tab [root]="tab1Root" tabTitle="商品" tabIcon="home"></ion-tab>
  <ion-tab [root]="tab2Root" tabTitle="カート" tabIcon="cart" tabBadge="4" tabBadgeStyle="danger"></ion-tab>
</ion-tabs>

上記のコードでは、カートアイコン上にバッジで「4」を表示しています

f:id:l08084:20180503183243p:plain
動作確認イメージ

バッジ上の数値とカート内の商品数を同期させる

このままではバッチに固定の数値が表示されているだけで意味がないので、ショッピングカート内の商品数とバッジ上で表示されている数値が常に一致するようにしてあげる必要があります。

IonicのEventsを使う

カート内の商品数とバッジ上の数値を同期させる件ですが、IonicのEventsを使って実現します。Eventsはいわゆる出版-購読型モデルを実現するIonicのAPIになります。 今回は、商品をカートに入れるなど、カート内の商品数が更新されるタイミングでPublish(出版)を実施し、タブ側のコンポーネントではSubscribe(購読)を行うことによって、タブ側で常に最新のカート内の商品数を取得することができる...といった風に使います。

タブ側でSubscribeを行う

実際にコードを書いていきます。まずは、タブバーコンポーネントの方から。

src/pages/tabs/tab.ts:

import { Component } from "@angular/core";

import { HomePage } from "../home/home";
import { CartPage } from "../cart/cart";
import { Events } from "ionic-angular";

@Component({
  templateUrl: "tabs.html"
})
export class TabsPage {
  public tab1Root = HomePage;
  public tab2Root = CartPage;
  public itemCount = 0;

  constructor(public events: Events) {}

  ionViewDidLoad() {
    this.events.subscribe(
      "cart:updated",
      count => (this.itemCount = count ? count : 0)
    );
  }
}

上記のコードでは、タブバーコンポーネント初期作成時(ionViewDidLoad())に、トピック"cart:updated"とアロー関数を引数に、Subscribe(購読)を行なっています。こうすることによって、同一トピック(つまり"cart:updated")のPublish(出版)が行われる度に、アロー関数が実行されるため、バッジの値itemCountは常に最新の値になります。

ちなみにタブバーのテンプレートファイル

src/pages/tabs/tab.html:

<ion-tabs>
  <ion-tab [root]="tab1Root" tabTitle="商品" tabIcon="home"></ion-tab>
  <ion-tab [root]="tab2Root" tabTitle="カート" tabIcon="cart" tabBadge="{{itemCount}}" tabBadgeStyle="danger"></ion-tab>
</ion-tabs>

カート内の商品数が変更されるタイミングでPublishを行う

あとは、ショッピングカート内の商品数が更新されるタイミングでPublish(出版)を行いさえすれば、カートアイコンのバッジ上に正しい商品数が表示される。

  • カート内の商品数が更新されるタイミング
    • アプリの初期表示時
    • カートに商品を追加した時
    • 注文を確定して、カートの中身が空になった時
アプリの初期表示時

前回起動時に、商品をカートに入れていた場合は、ストレージの機能により、カート内に商品が残ったままであるため、アプリ起動時には必ず商品数が0になる、というわけではない。

なので、アプリ起動時にも、ストレージの中身をチェックして、商品の数をPublishしてあげる必要がある。

タブ(src/pages/tabs/tab.ts):

import { Component } from "@angular/core";

import { HomePage } from "../home/home";
import { CartPage } from "../cart/cart";
import { Events } from "ionic-angular";
import { Storage } from "@ionic/storage";

@Component({
  templateUrl: "tabs.html"
})
export class TabsPage {
  public tab1Root = HomePage;
  public tab2Root = CartPage;
  // カート内の商品の数
  public itemCount = 0;

  constructor(private events: Events, private storage: Storage) {}

  /**
   * Ionicのライフサイクルメソッド
   * タブ画面の作成時に呼び出される
   *
   * @memberof TabsPage
   */
  ionViewDidLoad() {
    // トピック`cart:updated`を購読している
    // 最新のカート内の商品数を取得して、バッジの数値を更新する
    this.events.subscribe(
      "cart:updated",
      count => (this.itemCount = count ? count : 0)
    );

    // add this!
    // アプリの初期表示時のタイミング
    // 前回起動時にカート内に商品を入れた場合を考慮して、
    // ストレージの読み込みを実施している
    this.storage
      .length()
      .then(result => {
        if (result !== 0) {
          this.storage
            .get("items")
            .then(items => {
              // 現在のカート内の商品の数を取得
              const count = items.length;
              // トピック`cart:updated`で出版
              // eventDataとして、カート内の商品の数を渡している
              this.events.publish("cart:updated", count);
            })
            .catch(err => console.log(`storage error: ${err}`));
        }
      })
      .catch(err => console.log(`storage error: ${err}`));
  }
}
カートに商品を追加した時

ショッピングカートに商品を追加したタイミングでも、当然カート内の商品の数が変わるため、Publishしてあげる必要がある。

商品詳細画面(src/pages/detail/detail.ts):

import { Component } from "@angular/core";
import { NavController, NavParams, Events } from "ionic-angular";
import { Storage } from "@ionic/storage";
import { Product } from "../../model/product.model";

@Component({
  selector: "page-detail",
  templateUrl: "detail.html"
})
export class DetailPage {
  product: Product;

  constructor(
    private navCtrl: NavController,
    private navParams: NavParams,
    private storage: Storage,
    private events: Events
  ) {}

  ngOnInit() {
    // 商品リスト画面でタップした商品情報を受け取る
    this.product = this.navParams.get("product");
  }

  /**
   * ショッピングカートに商品を追加する
   *
   * @param {Product} product
   * @memberof DetailPage
   */
  addItem(product: Product) {
    let itemList = [];
    this.storage
      .length()
      .then(result => {
        // カート内の商品が0個だった場合
        if (result === 0) {
          itemList = [];
          itemList.push(product);
          // ストレージにボタンを押下した商品を追加する
          this.storage.set("items", itemList);
          // add this!
          // トピック`cart:updated`で出版
          // eventDataとして、カート内の商品の数を渡している
          this.events.publish("cart:updated", 1);
        } else {
          // カート内にすでに商品があった場合
          this.storage
            .get("items")
            .then(items => {
              items.push(product);
              const count = items.length;
              // ストレージにボタンを押下した商品を追加する
              this.storage.set("items", items);
              // add this!
              // トピック`cart:updated`で出版
              // eventDataとして、カート内の商品の数を渡している
              this.events.publish("cart:updated", count);
            })
            .catch(err => console.log(`storage error: ${err}`));
        }
      })
      .catch(err => console.log(`storage error: ${err}`));
    // 商品リスト画面に戻る
    this.navCtrl.pop();
  }
}
注文を確定して、カートの中身が空になった時

注文を確定して、未決済の商品(つまりカート内の商品)が0件になったときも、Publishして購読者に最新の商品数を送ってあげる必要がある。

カート画面(src/pages/cart/cart.ts):

// ...長いので省略

  /**
   * 「注文する」ボタン押下時に呼び出し
   *  Confirm Alertを表示し、OKを選択すると、
   *  注文を確定して、ストレージをクリアする
   *
   * @memberof CartPage
   */
  order() {
    const confirm = this.alertCtrl.create({
      title: "注文を確定しますか?",
      buttons: [
        {
          text: "キャンセル",
          handler: () => {}
        },
        {
          text: "OK",
          handler: () => {
            let alert = this.alertCtrl.create({
              title: "ご注文を受け付けました!",
              subTitle: "ご指定の住所までのお届け時間は、20-30分程です",
              buttons: ["OK"]
            });
            alert.present();
            this.storage.clear();
            this.productList = [];
            this.isEmpty = true;
            // add this!
            // トピック`cart:updated`で出版
            // eventDataとして、カート内の商品の数を渡している
            this.events.publish("cart:updated", 0);
          }
        }
      ]
    });
    confirm.present();
  }

// ...長いので省略

ここまで書くと、ショッピングカート内の商品数と、バッジの表示が同期されます。


次回の記事