AngularのNGRXで状態管理にふれる

# はじめに
本記事はNGRXの紹介と、簡単なチュートリアルを通してNGRXをざっくりと理解することを目的にしています。また、Effectは別ライブラリとなり本記事には登場しません。そちらはまた別記事にてご紹介したいと思います。

# NGRXの特徴
NGRXはRxJS(Reactive Extensions for JavaScript)を利用したAngularアプリケーションのためのグローバルな状態管理ライブラリで、Reduxに影響を受けて作られています。ReduxはFluxという設計思想のもと作られたライブラリで、単一方向のデータフローを特徴としており、主にReactでの利用を想定されています。

![state-management-lifecycle.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2178270/5b0d7a87-4ec4-6cb9-8c69-d4cc061000e7.png)
>引用元：NGRX
https://ngrx.io/guide/store

上記の図はNgRxにおけるアプリケーション状態の全体的な一般フローを表しており、こちらの図を見ると単一方向のデータフローがわかるかと思います。

Storeというのはアプリケーションの状態(state)を保持する場所です。Storeのstateは直接変更するのではなく、必ずActionでDispatchを発行しReducerを経由して変更します。stateはSelectorを介して取得します。

Action -> Reducer -> Store -> Selector

※コンポーネントが破棄されるとストアデータも破棄されるNgRxComponentStoreもあります。

# 環境構築

1. Node.jsのインストール
2. Angular CLIのインストール
3. プロジェクト作成
4. NGRXインストール

### 1. Node.jsのインストール
Node.jsは下記の記事などを参考にインストールしてください。
[Node.jsをインストールする](https://qiita.com/sefoo0104/items/0653c935ea4a4db9dc2b
)

また、この後のチュートリアルでは開発環境としてVisual Studio Codeを使用しています。こちらは必須ではありませんが、必要に応じてインストールしてください。

### 2. Angular CLIのインストール
Angular CLIはnpmからインストールします。
下記コマンドをターミナル(コマンドライン)で実行してください。

```
npm install -g @angular/cli
```

:::note warn
npm は基本的にNode.jsインストール時に合わせてインストールされます。上記コマンドが実行できない場合はnpmがインストールできてない可能性があります。
:::

### 3. プロジェクト作成
CLIコマンド `ng new` をターミナル(コマンドライン)で実行し、`my-app` という名前を付けます。
```
ng new my-app
```

routing、stylesheetについて聞かれますが、今回はroutingは「No」、stylesheetは「css」で進めてください。
```
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? CSS
```

:::note warn
Visual Studio Codeでngコマンドを実行する際、エラーが出る場合があります。その場合は下記を実行し、PowerShellの実行ポリシーを変更してください。

`Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process`

ng : このシステムではスクリプトの実行が無効になっているため、ファイル C:\Users\xxxxx\AppData\Roaming\npm\ng.ps1 を読み込むこと
ができません。詳細については、「about_Execution_Policies」(https://go.microsoft.com/fwlink/?LinkID=135170) を参照してください。
:::

### 4. NGRXインストール
ターミナルで以下コマンドを打ち込み、NGRXをインストールします。
なお、ngrx/storeにはEffectは含まれていません。本チュートリアルではEffectは利用しませんが、必要に応じてEffectは別途インストールしてください。

```
npm install @ngrx/store --save
```

yarnやngコマンドを利用してインストールすることも可能です。
```
//yarn
yarn add @ngrx/store
//ng
ng add @ngrx/store@latest
```

# チュートリアル

簡単なカウンターアプリを作成するチュートリアルを通してNGRXに触れたいと思います。

:::note warn
今回の記法はNGRX v7.4以降のcreateAction、createReducer、createEffectの関数を利用しています。これらを利用することにより、従来より簡潔に記載できるようになります。
:::

### Actionを作成する
カウンターアプリに必要なインクリメント、デクリメント、リセットのActionを作成します。
```src/app/counter.actions.ts
import { createAction } from '@ngrx/store';

export const increment = createAction('[Counter Component] Increment');
export const decrement = createAction('[Counter Component] Decrement');
export const reset = createAction('[Counter Component] Reset');
```

### Reducerを作成する
作成した各Actionに紐づくReducerを作成します。ReducerにStoreの値を書き換える処理を記載します。なおinitialStateは(その名の通り)初期値になります。
```src/app/counter.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset } from './counter.actions';

export const initialState = 0;

export const counterReducer = createReducer(
  initialState,
  on(increment, (state) => state + 1),
  on(decrement, (state) => state - 1),
  on(reset, (state) => 0)
);
```

### Moduleに登録する
app.module.tsに`StoreModule`と作成した`counterReducer`を登録します。
また、@NgModuleのimportsに`StoreModule.forRoot`を登録します。`StoreModule.forRoot`に登録することにより、アプリケーション全体でカウンターアプリが利用できるようになります。
```src/app/app.module.ts (StoreModule)
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter.reducer';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, StoreModule.forRoot({ count: counterReducer })],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
```

### コンポーネントを作成する
Actionを呼び出すコンポーネントを作成します。
ここでは`ng g component`コマンドを利用してコンポーネントを作成しています。ngコマンドを利用するとコンポーネントに必要なファイルの生成に加えて、app.module.tsへの登録も行ってくれます。
(//TODOはこの後の工程で修正します)

```
ng g component my-counter
```

```src/app/my-counter/my-counter.component.ts
import { Component } from '@angular/core';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-my-counter',
  templateUrl: './my-counter.component.html',
})
export class MyCounterComponent {
  count$: Observable<number>

  constructor() {
    // TODO: Connect `this.count$` stream to the current store `count` state
  }

  increment() {
    // TODO: Dispatch an increment action
  }

  decrement() {
    // TODO: Dispatch a decrement action
  }

  reset() {
    // TODO: Dispatch a reset action
  }
}
```

```src/app/my-counter/my-counter.component.html
<button (click)="increment()">Increment</button>

<div>Current Count: {{ count$ | async }}</div>

<button (click)="decrement()">Decrement</button>

<button (click)="reset()">Reset Counter</button>
```

### AppComponentにコンポーネントを宣言する
app.component.html(AppComponent)に先ほど作成したコンポーネントを宣言します。
```src/app/app.component.html
<app-my-counter></app-my-counter>
```

### Moduleに登録する
`MyCounterComponent`をAppModuleの`@NgModule`の`declarations`プロパティに登録します。
`declarations`プロパティに登録することでモジュール内でコンポーネント、ディレクティブやパイプを識別し、利用できるようになります。また、この中のいくつかを`exports`プロパティを通して公開し、外部コンポーネントから使用できるようにすることもできます。
```src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter.reducer';
import { MyCounterComponent } from './my-counter/my-counter.component';

@NgModule({
  declarations: [AppComponent, MyCounterComponent],
  imports: [BrowserModule, StoreModule.forRoot({ count: counterReducer })],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
```

### イベントとAction、Selectorを紐づける
コンポーネントでTODOとしていた処理にActionを設定します。
StoreにActionをディスパッチすることで、インクリメント、デクリメント、リセットの各メソッドを実装する。

```src/app/my-counter/my-counter.component.ts
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { increment, decrement, reset } from '../counter.actions';

@Component({
  selector: 'app-my-counter',
  templateUrl: './my-counter.component.html',
})
export class MyCounterComponent {
  count$: Observable<number>;

  constructor(private store: Store<{ count: number }>) {
    this.count$ = store.select('count');
  }

  increment() {
    this.store.dispatch(increment());
  }

  decrement() {
    this.store.dispatch(decrement());
  }

  reset() {
    this.store.dispatch(reset());
  }
}
```

### アプリケーションを起動する
下記コマンドを実行するとアプリケーションが起動されます。
`ng serve --open`

![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2178270/1f00f3ec-8a64-c6e2-ad90-e058f29dfa05.gif)

### おわりに
以上、NGRXのご紹介と簡単なチュートリアルを終わりたいと思います。次回はEffectに関する記事をご紹介します。