はじめに
仕事で状態管理ライブラリの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