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

## TL;DR

タイトルまんまですが、TypeScript と RxJS だけで、type safe で immutable な Flux パターンが実現できるよ、って話です。Flux Store の実装はコメント入れても 40 行未満です。

PoC 的に作ってみましたが、Angular が適合するユースケースでは、あまり必要な場面はありません。

* 2018-10-06 この古い記事に「いいね」もらったので、現時点のオススメフレームワークを追記
* 2017-06-15 末尾にクライアント状態管理が必要になる場面についての考察を追記


## 動機

 * flux の考え方はいいけど、type safe で immutable, rxjs ベースの素朴な Store が欲しかった
   * [redux の switch 文は何が嬉しいの？](https://bits.ristic.io/dont-overcomplicate-javascript-0) 
   * immutable.js も無駄に大きいし...


## Akita (2018-10-06 追記)

本記事と問題意識を同じくする [Akita](https://netbasal.gitbook.io/akita/) という、高機能かつ使いやすい API を持つ状態管理フレームワークをオススメします。

簡単に特徴を紹介すると：

* Store, Query, Service の役割が明確に分離されている
* State を表現する Model は [Form と相互変換可能](https://netbasal.gitbook.io/akita/plugins/persist-form)
* [Redux Devtools](https://netbasal.gitbook.io/akita/enhancers/devtools) 対応
* [CLI](https://netbasal.gitbook.io/akita/entity-store/akita-cli) が用意されている
* plugable な [State の永続化層](https://netbasal.gitbook.io/akita/enhancers/persist-state) 

Akita の考え方や様々な利用例は [blog](https://netbasal.gitbook.io/akita/entity-store/blog-posts) でわかりやすく紹介されているので、リファレンスを主とする[公式ドキュメント](https://netbasal.gitbook.io/akita/) よりも [blog](https://netbasal.gitbook.io/akita/entity-store/blog-posts) から読む方が理解しやすいと思います。


## アイデアの元

 * [ANGULAR MODEL PATTERN](https://tomastrajan.github.io/angular-model-pattern-example#/getting-started) が考え方のベースにあります。
 * Action 自体を関数にしちゃうのは [Walts](http://qiita.com/armorik83/items/191d50cbf093778198fe) を参考にさせてもらいました。
 * immutable にする方法は [Immutable.js Records in TypeScript](https://spin.atomicobject.com/2016/11/30/immutable-js-records-in-typescript/) のコメント「[spread でいいやん](https://spin.atomicobject.com/2016/11/30/immutable-js-records-in-typescript/#comment-583868)」がきっかけで、[TypeScript 2.1.4](http://qiita.com/vvakame/items/fc7e37d0296c63f41f4f) 以降の spread と Readonly, Partial をそのまま使うだけでいいと気付きました。

## Flux Store の実装

これだけです。

 * `BehaviorSubject` で state を管理しています。
 * 公開する `Observable` からは `Readonly<T>` が流れてくるので immutable が保証されます
 * `Action` は 現 state (`Readonly<T>`) から次の state (`Partial<T>`) を返す関数です。返り値は `Partial` なので、変更対象のプロパティだけを返せばいいようにしています。
 * `dispatch` メソッドが `Action` を適用して `Subject` に新しい state を流します。`dispatch` 内では、`update` が `spread` を利用して、現 state と `Action` の結果の `Partial` から新しい state を合成しています。

```typescript
 import {BehaviorSubject} from 'rxjs/BehaviorSubject';
 import {Observable} from 'rxjs/Observable';
 
 export type Action<T> = (current: Readonly<T>) => Partial<T>;
 
 export class Store<T> {
 
   private _data: BehaviorSubject<Readonly<T>>;
   public data$: Observable<Readonly<T>>;
 
   // サブクラスでは injectable にするため
   // override して引数なしのコンストラクタにしましょう
   constructor(initialData: T) {
     this._data = new BehaviorSubject(initialData as Readonly<T>);
     this.data$ = this._data.asObservable();
   }
 
   protected current(): Readonly<T> {
     return this._data.getValue();
   }
 
   protected next(data: Readonly<T>): void {
     this._data.next(data);
   }
 
   // state を更新する
   //
   // current をそのまま渡すと怒られる
   // "spread types may only be created from object types"
   // TypeScript 2.3 の不具合っぽい
   // https://github.com/Microsoft/TypeScript/pull/13288
   protected update(current: Readonly<T>, diff: Partial<T>): Readonly<T> {
     return {...current as object, ...diff as object} as Readonly<T>;
   }
 
   public dispatch(action: Action<T>): void {
     const current = this.current();
     const diff = action(current);
     const result = this.update(current, diff);
     this.next(result);
   }
 
 }
```

## Store の実装例

 * テストしやすいよう Action は pure function として分離しました。
 * ただ、Action ごとに dispatch 呼びだす component 公開用のメソッドを作るのがちょっと手間です。コンポーネントで毎回
  `this.store.dispatch(this.store.action(params...)` とするよりもマシですが…
 * 非同期処理は `Observable` でも `Promise` でも最終的に `dispatch` を呼べばいいだけなので、特に仕組みは用意していません。同じようなコードが増えそうなら、`dispatchAsync` みたいなのをあらためて考えます。

```typescript
 import {Injectable} from '@angular/core';
 import {Store} from '../../lib/store';
 
 export interface Counter {
   name: string;
   value: number;
 }
 
 @Injectable()
 export class CounterStore extends Store<Counter> {
 
   constructor() {
     super({name: 'hello counter', value: 0});
   }
 
   // pure function for test
   _increment(current: Readonly<Counter>): Partial<Counter> {
     return {value: current.value + 1};
   }
 
   // pure function for test
   _decrement(current: Readonly<Counter>): Partial<Counter> {
     return {value: current.value - 1};
   }
 
   // public interface for component with side effect
   increment(): void {
     this.dispatch(this._increment);
   }
 
   // public interface for component with side effect
   decrement(): void {
     this.dispatch(this._decrement);
   }
 }
```

## コンポーネントの実装例

 [Angular 4.0 で便利になった ngIf](https://blog.lacolaco.net/post/ng4-feature-ngif/) のおかげで `Observable` も `async` パイプだけで、テンプレートを簡潔に書けるようになりました。なので、store の `Observable` をそのままテンプレートに渡しています。

コンポーネントは状態を持たないので、`ChangeDetectionStorategy` には `OnPush` を安心して利用できます。わざわざ、[`ChangeDetectorRef#markForCheck` で変更を通知](http://qiita.com/laco0416/items/523d96ddbfe55c4e6949#observables)しなくていいわけです。

また、状態を持たず、`Observable` をそのままテンプレートに渡すのは、コンポーネント内で `subscribe` しない、ということでもあります。結果 `Subscription` の管理も不要になります。[ライフサイクルコールバックで頑張って `unsubscribe` する](https://netbasal.com/automagically-unsubscribe-in-angular-4487e9853a88) 必要もありません。

### app.component.ts

```typescript
 import {ChangeDetectionStrategy, Component} from '@angular/core';
 import {Observable} from "rxjs/Observable";
 import {Counter, CounterStore} from "./services/CounterStore";
 
 @Component({
   selector: '[app-root]',
   templateUrl: './app.component.html',
   changeDetection: ChangeDetectionStrategy.OnPush,
 })
 export class AppComponent {
 
   constructor(private store: CounterStore) {}
 
   get counter$(): Observable<Readonly<Counter>> {
     return this.store.data$;
   }
 
   increment(): void {
     this.store.increment();
   }
 
   decrement(): void {
     this.store.decrement();
   }
 
 }
```

### app.component.html

```html

 <h3>store example</h3>
 
 <div *ngIf="counter$ | async as counter">
   name: "{{ counter.name}}", value: {{counter.value}}
   <br>
   <button (click)="increment()">increment</button>
   <button (click)="decrement()">decrement</button>
 </div>
```


## そもそもクライアントサイドの状態管理って必要？ (2017-06-15 追記)

[ng-kyoto #6](https://mobile.twitter.com/search?q=%23ng_kyoto) で、この記事について飛び込み発表させていただいたのですが、そこでもらった質問です。

### 画面遷移型アプリでは不要

Angular の強みの一つに Form があり、他のフレームワークと比較して業務アプリに向いている、と言えるでしょう。SPA ながら、旧来の Struts や Rails アプリのような画面遷移を行うようなウェブアプリです。

URL で明確に分割された画面があり、かつ、扱うデータが Todo サンプルのような個人的でシンプルなものでなく多数の項目から構成されるデータで、データベースで最新状況が共有されるようなユースケースです。まさに一般的な業務アプリが適合すると思いませんか？

このようなユースケースでは、画面ごとに必要な情報は閉じていて、画面をまたがったグローバルな情報はほぼ存在しないし、DB で共有された常に更新され続けている最新のデータが見れなければ、何の役にも立ちません。

言い替えれば、**クライアントサイドでの状態管理は不要**、ということになります。

実装自体は以下のような流れになります。

1. Router の resolve 等で画面遷移時に最新データをサーバーから取得
2. 入力情報は Form が管理
3. 編集後に「保存」等の明確なアクションでサーバーに反映

クライアントサイドで管理すべきデータは Angular の Form がよしなにやってくれるので、データを取得して送信する薄い Service があれば十分です。


### 画面遷移しないアプリでは重要

逆の観点から言えば、上記の前提があてはまらない場合に、クライアントサイドでの状態管理が必要になります。

具体的には Facebook や Slack のように画面遷移がないアプリで、GUI MVC (!= Web MVC2) が想定していた Model が様々な View に反映されるようなユースケースです。Facebook を例にすれば、ヘッダ未読数やウォール、チャットウィンドウなど、様々な箇所で今の「状態」を適切に管理しないといけないわけです。

その必要性から Flux アーキテクチャが Facebook から生まれたわけですね。


### 結論

ということで、結論としては、ユースケース依存という当たり障りのない回答になってしまいます。

しかし、Angular の提供する Form や Router の強みが発揮される画面遷移型アプリでは、http 通信を wrap する薄い Service があれば十分で、状態管理は不要と言っていいと思います。