はじめに
仕事で状態管理ライブラリのAkitaを使うことになったので勉強のためにAngularとAkitaを使ってカウンターアプリを作ることにしました。
Akitaって?
Angular, React, Vueに対応している状態管理ライブラリになります。
状態を保持する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 }}
上記のコードを実行すると、このようなカウンターアプリが表示されます。
なお、AkitaはChromeのRedux Devtools Extensionで値の変遷を追うことができます。
次回の記事
【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