中安拓也のブログ

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

【Angular】Akita学習(1) - カウンターアプリ作成

はじめに

仕事で状態管理ライブラリのAkitaを使うことになったので勉強のためにAngularとAkitaを使ってカウンターアプリを作ることにしました。

Akitaって?

f:id:l08084:20201005162817p:plain

Angular, React, Vueに対応している状態管理ライブラリになります。

f:id:l08084:20201006221324p:plain

状態を保持するStore、現在の状態を取得するQuery、状態を更新したり、APIを呼び出したりするServiceという風に機能が分かれています。

環境

  • Angular: 8.2.14
  • @datorama/akita: 5.2.4

$ ng versionの実行結果

Angular CLI: 8.3.29
Node: 12.13.1
OS: darwin x64
Angular: 8.2.14
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.803.29
@angular-devkit/build-angular     0.803.29
@angular-devkit/build-optimizer   0.803.29
@angular-devkit/build-webpack     0.803.29
@angular-devkit/core              8.3.29
@angular-devkit/schematics        8.3.29
@angular/cli                      8.3.29
@ngtools/webpack                  8.3.29
@schematics/angular               8.3.29
@schematics/update                0.803.29
rxjs                              6.4.0
typescript                        3.5.3
webpack                           4.39.2

Akitaインストール

まず、Angular CLIを使ってAngularプロジェクト(akita-counter)を作成します。

$ ng new akita-counter

続いて、Akitaライブラリをインストールします。

$ ng add @datorama/akita

Akita CLIもグローバルインストールします。

$ npm install @datorama/akita-cli -g

カウンターアプリ作成

値を一つずつ増やしたり減らしたりできるカウンターアプリを作成していきます。

Store

まず、カウンターの値を保持するStoreを作成します。

Akitaには通常のStoreとデータベースのテーブルのような取り扱いができるEntityStoreがありますが、今回の例では通常のStoreを使用しています。

counter.store.ts

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

export interface CounterState {
  counter: number;
}

export function createInitialState(): CounterState {
  return {
    counter: 0,
  };
}
@Injectable({ providedIn: 'root' })
@StoreConfig({ name: 'counter' })
export class CounterStore extends Store<CounterState> {
  constructor() {
    super(createInitialState());
  }
}

Query

続いて、Storeから現在のカウンターの値を取得するQueryを作成します。

counter.query.ts

import { Injectable } from '@angular/core';
import { Query } from '@datorama/akita';
import { CounterState, CounterStore } from './counter.store';
@Injectable({ providedIn: 'root' })
export class CounterQuery extends Query<CounterState> {
  constructor(protected store: CounterStore) {
    super(store);
  }
}

Service

カウンターの値を更新する機能を持つServiceを作成します。加算を行うincrement()と減算を行うdecrement()を作成しました。

counter.service.ts

import { Injectable } from '@angular/core';
import { CounterStore } from './counter.store';
@Injectable({ providedIn: 'root' })
export class CounterService {
  constructor(private counterStore: CounterStore) {}

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

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

increment()decrement()の内部ではStoreの値を更新するためにStoreのメソッドであるupdate()を呼び出しています。

Component

カウンター画面を作成します。QueryとServiceをDIすることで、カウントの取得と更新を行っています。

counter.component.ts

import { Component, OnInit } from '@angular/core';
import { CounterService } from './state/counter.service';
import { CounterQuery } from './state/counter.query';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-counter',
  templateUrl: './counter.component.html',
  styleUrls: ['./counter.component.scss'],
})
export class CounterComponent implements OnInit {
  readonly counter$: Observable<number>;

  constructor(
    private counterService: CounterService,
    private counterQuery: CounterQuery
  ) {
    this.counter$ = this.counterQuery.select('counter');
  }

  ngOnInit() {}

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

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

Storeの値を取得するメソッドとして、Queryは2種類のメソッドを用意しています。select()getValue()です。select()ではStoreの値をObservable型で返します。getValue()はStoreの生の値を返します。

今回の例では、Storeの値の取得にselect()を使っています。

counter.component.html

<button (click)="increment()">Increment</button>
<button (click)="decrement()">Decrement</button>
{{ counter$ | async }}

上記のコードを実行すると、このようなカウンターアプリが表示されます。

f:id:l08084:20201006222153p:plain
カウンターアプリ

なお、AkitaはChromeのRedux Devtools Extensionで値の変遷を追うことができます。

f:id:l08084:20201007152716p:plain
AkitaにRedux Devtools Extentionを使っているところ

次回の記事

【Angular】Akita学習(2) - TODOアプリ作成 - 中安拓也のブログ

参考サイト

Akita | Reactive State Management

https://engineering.datorama.com/@NetanelBasal

Angular向け状態管理ライブラリAkitaの紹介 - Qiita

Angularのシンプルな状態管理ライブラリ Akita について - Qiita

Akita🐶でがんばる状態管理 - Qiita

素朴な Angular 向けの type safe で immutable な Flux Store - Qiita