中安拓也のブログ

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

Angular + Reduxを学ぶ #3 - @Select デコレーターでネストされたStateを参照する

前回記事はこちら

angular-redux/store@selectデコレーターの使い方について解説していきます。

バージョン情報

Angularをベースに、Reduxのアーキテクチャを採用しています。

  • Angular: 5.2.9

  • angular-redux/store: 7.1.1

  • Node: 8.1.4

  • Angular CLI: 1.7.3

サンプルアプリケーションについて

仮想通貨取引所のビットフライヤーとコインチェックのビットコイン価格を、WebAPIから取得して画面に表示するアプリケーションを例に説明します。

Stateの構造

本サンプルアプリケーションのStateは、2層にネストされていて、👇画像の構造になっています。

f:id:l08084:20180322201336p:plain

Stateのソースコード

1層目

Stateの一層目は、BitflyerTickerModel型のbitflyerTickerプロパティとCoincheckTickerModel型のcoincheckTickerプロパティで構成されています。

src/state/root/store.ts:

import { BitflyerTickerModel } from '../bitflyer-ticker/bitflyer-ticker.model';
import { CoincheckTickerModel } from '../coincheck-ticker/coincheck-ticker.model';

export interface IAppState {
  bitflyerTicker: BitflyerTickerModel;
  coincheckTicker: CoincheckTickerModel;
}

export const INITIAL_STATE: IAppState = {
  bitflyerTicker: null,
  coincheckTicker: null,
};
2層目

BitflyerTickerModel型は、ビットフライヤーが取り扱っている通貨の価格を表していて、12個のプロパティを持っています。

src/state/bitflyer-ticker/bitflyer-ticker.model.ts:

export interface BitflyerTickerModel {
    best_ask: number;
    best_ask_size: number;
    best_bid: number;
    best_bid_size: number;
    ltp: number;
    product_code: string;
    tick_id: number;
    timestamp: string;
    total_ask_depth: number;
    total_bid_depth: number;
    volume: number;
    volume_by_product: number;
}

CoincheckTickerModel型は、コインチェックが取り扱っている通貨の価格を表していて、7個のプロパティを持っています。

src/state/coincheck-ticker/coincheck-ticker.model.ts:

export interface CoincheckTickerModel {
    last: number;
    bid: number;
    ask: number;
    high: number;
    low: number;
    volume: string;
    timestamp: number;
}

ネストされたStateの参照方法

@selectを使って、Stateの最新の値を取得していきます。 @selectのオプションには、文字列データ、もしくは文字配列データを指定することができます。

src/app/app.component.ts:

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { BitflyerService } from './services/bitflyer.service';
import { CoincheckService } from './services/coincheck.service';
import { select } from '@angular-redux/store';
import { BitflyerTickerModel } from '../state/bitflyer-ticker/bitflyer-ticker.model';
import { CoincheckTickerModel } from '../state/coincheck-ticker/coincheck-ticker.model';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  // 2層目のltpプロパティを参照
  @select(['bitflyerTicker', 'ltp']) readonly bitflyerLtp$: Observable<number>;

  // 1層目のcoincheckTickerプロパティを参照
  // プロパティ名と変数名が同じため、selectorオプションは省略されている
  @select() readonly coincheckTicker$: Observable<CoincheckTickerModel>;

  // 1層目のbitflyerTickerプロパティを参照
  @select('bitflyerTicker') readonly ticker$: Observable<BitflyerTickerModel>;

  constructor(
    private bitflyerService: BitflyerService,
    private coincheckService: CoincheckService
  )  {}

  ngOnInit() {
    this.bitflyerService.getTicker();
    this.coincheckService.getTicker();
  }
}

各プロパティの初期値はundefinedのため、値が更新されるまでの間にエラーが発生するのを防ぐ目的で、Angularのsafe navigation operator(?.)を使用しています。

src/app/app.component.html:

<div>{{ bitflyerLtp$ | async | number }}円</div>
<div>{{ (coincheckTicker$ | async)?.timestamp | date  }}</div>
<div>{{ (ticker$ | async)?.product_code }}</div>

動作確認

👆のソースコードを実行すると、@selectデコレーターによって、最新のStateの値が取得されていることがわかります。

f:id:l08084:20180323163234p:plain

参考サイト

@angular-redux/store | @angular-redux/store