Ngrxをこれから始める人への入門ガイド

# はじめに
この記事は[Angular AdventCalendar2018](https://qiita.com/advent-calendar/2018/angular) 14日目の記事です。

今までReact、Vueを使うことが多かったんですが、半年前からプロダクトでAngular + Ngrxを扱うことになりました。
Angularを使い始めて思ったのは、ググっても他のFWと比べると記事が少ないなと思うのと、Ngrxについてはさらに少ないな、と思ったのでこれからNgrxを使おうと思っている人が増えるようにNgrx初心向けの導入記事です。

# Ngrxとは
Ngrx公式ページ。Angular.ioにあわせたドキュメントページが最近作られました。
https://ngrx.io/

> NgRx Store provides reactive state management for Angular apps inspired by Redux. Unify the events in your application and derive state using Rxjs

Ngexは、Reduxを参考にAngularに状態管理を提供するライブラリです。Fluxの思想に従って、Componentで扱うStateをすべて一箇所のストアで一元管理し、Component間でのstateのやりとりを扱いしやすくします。
そもそもReduxって何?って方は[Reduxの入門記事](https://qiita.com/kiita312/items/49a1f03445b19cf407b7)を以前書いたのでご参照ください。

# Ngrxの要素
前述の通り、NgRxはReduxと構成はほぼ同じです。データフローの簡単な遷移図がこちらです。

<img width="794" alt="スクリーンショット 2018-12-04 18.20.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/36813/3e4377dd-6a66-ee24-23d3-443cec7d8d98.png">



- Viewからイベントが発火され、Actionを作成
- ActionをStoreへdispatchする
- ReducerがActionを受けて新しいStateを作成&更新
- Selectorを通りViewへ新しいStateが渡る

それぞれの構成要素を[チュートリアル](https://ngrx.io/guide/store)のサンプルコードと共に解説していきます。

#### Action
Storeのstateを変更したい場合に直接変更は行えず、必ずActinonを作成してStoreへdispatchすることで変更を指示します。

``` src/app/counter.actions.ts
import { Action } from '@ngrx/store';
 
export enum ActionTypes {
  Increment = '[Counter Component] Increment',
}

export class Increment implements Action {
  readonly type = '[Counter Component] Increment';
}
```
Actionはプロパティにユニーク値であるtypeを持ちます。

コンポーネントではActionをStoreへdispatchするために、コンストラクタからstoreを注入しておきます。ユーザーイベントに応じて毎回新規のActionを作成し、storeのdispatchメソッドを呼び出します。

``` src/app/my-counter/my-counter.component.ts
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { Increment } from '../counter.actions';
export class MyCounterComponent {
 
  constructor(private store: Store<{ count: number }>) {
  }

  increment() {
    this.store.dispatch(new Increment());
  }
}
```
```src/app/my-counter/my-counter.component.html
<button (click)="increment()">Increment</button>
```

### Store
アプリケーションのstateを保持する場所です。アプリケーション内に一つのみ存在します。
AppModuleにStoreModule.forRootを使いReducerと共に登録します。

```src/app/app.module.ts 
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter';

@NgModule({
  imports: [StoreModule.forRoot({ count: counterReducer })],
})
export class AppModule {}
```
### Reducer
Reducerは、Actionと現在のstateに応じて新しいstateを作成するピュアなメソッドです。
また、stateの初期値の設定も行います。

```src/app/counter.reducer.ts
import { Action } from '@ngrx/store';
import { ActionTypes } from './counter.actions';
 
export const initialState = 0;
 
export function counterReducer(state = initialState, action: Action) {
  switch (action.type) {
    case ActionTypes.Increment:
      return state + 1; 
    default:
      return state;
  }
}
```

### selector 
selectorは、Storeのstateの必要な部分のみを取得する為のものです。createSelectorのメソッドを使い、各stateを取得するselectorを登録します。

``` reducers.ts
import { createSelector } from '@ngrx/store';
 
export interface FeatureState {
  counter: number;
}
 
export interface AppState {
  feature: FeatureState;
}
 
export const selectFeature = (state: AppState) => state.feature;
export const selectFeatureCount = createSelector(
  selectFeature,
  (state: FeatureState) => state.counter
);
```

### Effect
Effectは本来のReduxの機能には含まれておらず、redux-thunkやredux-sagaに該当するものです。外部APIとのHTTP通信など非同期処理を行う部分を担う箇所です。
Effectは、StoreへdispatchしたActionをキャッチして処理を行い、新しいActionをdispatchします。

Ngrxでもeffectはstoreのモジュールとは別になっています。

```bash
yarn add @ngrx/effects
```

``` ./effects/auth.effects.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Action } from '@ngrx/store';
import { Actions, Effect, ofType } from '@ngrx/effects';
import { Observable, of } from 'rxjs';
import { catchError, map, mergeMap } from 'rxjs/operators';
 
@Injectable()
export class AuthEffects {
  // Listen for the 'LOGIN' action
  @Effect()
  login$: Observable<Action> = this.actions$.pipe(
    ofType('LOGIN'),
    mergeMap(action =>
      this.http.post('/auth', action.payload).pipe(
        // If successful, dispatch success action with result
        map(data => ({ type: 'LOGIN_SUCCESS', payload: data })),
        // If request fails, dispatch failed action
        catchError(() => of({ type: 'LOGIN_FAILED' }))
      )
    )
  );
 
  constructor(private http: HttpClient, private actions$: Actions) {}
}
```


# Ngrxの内部実装
さて、Ngrxの概要について言及してきましたが、これをrxでどのように実現しているのか、内部実装を見て理解を深めましょう。

<img width="914" alt="スクリーンショット 2018-12-07 14.11.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/36813/a084eaa6-cdd7-7e28-e6ea-0cb169e014c0.png">

### storeのdispatchメソッド
dispatchメソッドではactionObseverに対してactionをnextしています。
actionObserverはBehaviorSubjectで、disptchされるactionのストリームです。

``` store/src/store.ts
dispatch<V extends Action = Action>(action: V) {
 this.actionsObserver.next(action);
}
```
### stateとreducer
state自身はBehaviorSubjectで、actionOvserverをsubscribeしています。pipeしてreducerのストリームから登録されているredeucerをwithLatestFromで取得し、scanでreducerを実行して作られた新しいstateを自身のストリームに流しています。
この時同時にscannedActionsストリームにも新しいstateを流しています。


```store/src/state.ts
const actionsOnQueue$: Observable<Action> = actions$.pipe(
    observeOn(queueScheduler)
);
const withLatestReducer$: Observable<
    [Action, ActionReducer<any, Action>]
> = actionsOnQueue$.pipe(withLatestFrom(reducer$));

const seed: StateActionPair<T> = { state: initialState };
const stateAndAction$: Observable<{
    state: any;
    action?: Action;
}> = withLatestReducer$.pipe(
    scan<[Action, ActionReducer<T, Action>], StateActionPair<T>>(
    reduceState,
    seed
    )
);

this.stateSubscription = stateAndAction$.subscribe(({ state, action }) => {
    this.next(state);
    scannedActions.next(action);
});
```

### effect
```
import { Actions, Effect, ofType } from '@ngrx/effects';
```
effectモジュールで使用するActionsは、上記でのscannedActionsにあたります。つまり、effectの実行タイミングは、storeがreducerの処理を行って新しいstateに変わった後のタイミングということがわかります。

```effects/src/actions.ts
constructor(@Inject(ScannedActionsSubject) source?: Observable<V>) {
    super();

    if (source) {
      this.source = source;
    }
  }
```

## さいごに

Ngrxを導入するかどうかは、アプリの規模や開発人数に依ると思います。
とくにAngularsではserviceがStoreのようにsingletonな存在なので、service内に関連するstateを保持すれば状態管理はそんなに困らないです。
開発人数が増えてstateの更新が煩雑になってきている、テストしづらい、などで状態管理をしっかりしたいと感じた時にNgrxを入れることを考えてみてはどうでしょうか。