【Angular】AkitaのStoreを使った状態管理

今回は状態管理ライブラリ「Akita」を使っていこうと思います。
これは何かというと、画面操作で入力されたデータを保持したり、逆に保持しているデータを取り出したりすることを容易にしてくれるライブラリだと思っていただければ大丈夫です。
Akitaのデータ保持には2種類あり、データをkey-valueで保持してくれる「Store」と、データベース的に保持してくれる「EntityStore」があります。
今回はStoreを使った状態管理を紹介していきます。
(EntityStoreを使う方法についてはこちらを参照してみてください。)

スポンサーリンク

Storeを使う利点

では何故わざわざStoreを使って状態管理をしていくのかについて説明していきます。
最大の利点は複数のコンポーネントで1つのStoreを使ったときにあります。
Webアプリ開発においてたいていの場合、結構な数のコンポーネントを作っていくことになると思いますが、基本的にコンポーネントは他のコンポーネントでどのような操作が行われているか一切見ていません
例えば店の商品を管理するアプリを作りたいとして、コンポーネントAである商品の値段を1000円から1500円に修正したとしても、コンポーネントBはそのような操作が行われたことを知りません。コンポーネントBでは商品の値段を1000円で表示し続けることになります。
ですがStoreを使っていくと、各コンポーネントで行った編集内容が1つのStoreで管理されていくことになるので、編集した内容がStoreを通して全コンポーネントに波及します
商品を管理するアプリの例でいえば、コンポーネントAが商品の値段を1500円に修正したことでStoreで保持している商品の値段も1500円に修正され、コンポーネントBはStoreのデータを読み込んで表示しているので、商品の値段を1500円として表示してくれます。
こんな感じでアプリ全体で修正したり表示したりするデータの管理においてStoreは非常に有効に働きます。

Akitaのインストール

Akita、Akita Cliをインストールしていきます。

Akitaのインストール

以下のコマンドを実行してください。

npm install @datorama/akita

Akita Cliのインストール

以下のコマンドを実行してください。

npm install @datorama/akita-cli -g

Storeを作っていく

Storeを使うにあたって、以下の4つのファイルが必要になります。

  • index.ts
  • ○○○○.query.ts:Storeのデータを読み込むのに使います。
  • ○○○○.service.ts:Storeのデータを書き込むのに使います。
  • ○○○○.store.ts:Storeを定義します。

Akitaのコマンドを使うと、これら4つのファイルを一度に作れます。
コマンドプロンプトで「Akita」と入力してください。

> akita
Give me a name, please Ringo
Which store do you need? Store
Choose a directory.. (Use arrow keys)

いくつか入力を求められます。
Give me a name, please :Storeの名前を入力してください。今回はRingoにしました。
Which store do you need? :選択肢として「EntityStore」と「Store」が用意されています。今回はStoreにします。
Choose a directory.. (Use arrow keys)  :どこのディレクトリに作るかを選択してください。こちらも選択肢が表れますので、そこから選んでください。

Storeの編集

作成したStoreファイルを編集していきましょう。

import { Store, StoreConfig } from '@datorama/akita';
import { Injectable } from '@angular/core';

export interface RingoState {
   name: string;
   counter: number;
}

export function createInitialState(): RingoState {
  return {
    name: '初期名前',
    counter: 0
  };
}

@Injectable({ providedIn: 'root' })
@StoreConfig({ name: 'ringo-store' })
export class RingoStore extends Store<RingoState> {
  constructor() {
    super(createInitialState());
  }
}

string型のname(初期値:初期名前)とnumber型のcounter(初期値:0)を持つStoreにしています。

Queryの編集

import { Injectable } from '@angular/core';
import { Query } from '@datorama/akita';
import { RingoState,RingoStore } from './ringo-store.store';

@Injectable({ providedIn: 'root' })
export class RingoQuery extends Query<RingoState> {
  constructor(store: RingoStore) {
    super(store);
  }
}

これを使うことでRingoStoreの内容を読み込んでいくことになります。

Serviceの編集

import { RingoStore } from './ringo-store.store';
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class RingoService {
  constructor(private ringoStore: RingoStore) {}

  increment() {
    this.ringoStore.update((state) => ({
      counter: state.counter + 1,
    }));
  }

  decrement() {
    this.ringoStore.update((state) => ({
      counter: state.counter - 1,
    }));
  }

  setName(value: string){
    this.ringoStore.update(() => ({
      name: value,
    }));
  }
}

RingoStoreに保持しているデータを修正するためのメソッドを用意します。
counterを1増やしたり、減らしたりするためのincrement()、decrement()と、
nameを上書きするためのsetName(value: string)を用意しました。

コンポーネントからStoreを使っていく

では早速、上で作っていったStoreを使っていきましょう。
今回はHTMLとTSファイルをこのようにしてみました。

import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { RingoService } from './state/ringo-store.service';
import { RingoQuery } from './state/ringo-store.query';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'angular-study';
  inputText: string = "";
  readonly counter$: Observable<number>;
  readonly name$: Observable<string>;

  constructor(
    private ringoService: RingoService,
    private ringoQuery: RingoQuery
  ){ 
    this.counter$ = this.ringoQuery.select('counter');
    this.name$ = this.ringoQuery.select('name');
  }

  increment() {
    this.ringoService.increment();
  }

  decrement() {
    this.ringoService.decrement();
  }

  setName(){
    this.ringoService.setName(this.inputText);
  }
}
<div>
    <p>名前を設定する</p>
    <input type="text" [(ngModel)]="inputText">
    <input type="submit" (click)="setName()">
    <p>数値を変更する</p>
    <button (click)="increment()">増加</button>
    <button (click)="decrement()">減少</button>
    <p>設定内容</p>
    <p>名前 {{ name$ | async }}</p>
    <p>数値 {{ counter$ | async }}</p>
</div>

名前を設定するのところに入力フィールドを用意しており、そこに適当な文字を入力してボタンを押すと、Storeのnameを編集できます。
増加、減少のボタンを用意しており、これらを押すことでStoreのcounterを1増やしたり減らしたりできます。
画面の一番下にStoreに設定されているデータを読み込んで出力する部分を設けています。
コンストラクタでQueryのselect()を呼び出していますが、これはQueryに初めから用意してくれているメソッドでObservable型で読み込むことが出来ます。

実際に動かしてみる

では動かしてみましょう。

終わりに

今回はStoreを使った状態保持について紹介しました。
Akitaには他にもEntityStoreという形式もありますので、次はそちらの記事も書いていきます。

スポンサーリンク

Angular開発

Posted by 社畜林檎