水無瀬のプログラミング日記

プログラムの勉強した時のメモ

NgRx使ってみる

TL;DR.

ソース

はじめに

Angularでアプリを作っていると状態管理というかデータの取り回しについて困ることが多々あったりする。
そこでいろいろ調べた結果やっぱりRedux最強説がっぽい。
自力でReduxチックに実装したこともあるけど、
既にAngularに最適化されたライブラリがあったからそれを使おうというのが今回のテーマ。

ngrxとは

端的に言うとAngular向けRedux。
Angularに向けてRxJS使って設計されているって公式サイトに書いてあった。

インストール

# 状態管理ライブラリメインとなるモジュール
npm install @ngrx/store

サンプル作成

公式サイトのCounterを参考にTODOアプリを作ってみる。
ng new ngrx-sampleからスタート。
必要なファイルを先に作っておく。

# action
$ mkdir src/app/action && touch src/app/action/todo.action.ts
# reducer
$ mkdir src/app/reducer && touch src/app/reducer/todo.reducer.ts

action

基本的にはチュートリアルのまま。
ただ、今回は任意の値が欲しかったから、追加時にはconstructorで値を受け取っておく。

import { Action } from '@ngrx/store';

export enum ActionTypes {
    Add = 'TODO_ADD',
    Delete = 'TODO_DELETE',
    Reset = 'TODO_RESET'
}

export class Add implements Action {
    readonly type = ActionTypes.Add;
    // 追加したい文字列を受け取る
    constructor(public payload: { text: string }) { }
}
export class Delete implements Action {
    readonly type = ActionTypes.Delete;
}
export class Reset implements Action {
    readonly type = ActionTypes.Reset;
}

reducer

これも基本的にはチュートr(ry
一応副作用が無いように追加の時にはスプレッド演算子使って、削除の時にはフィルター使った。
それ以外は基本そのまま。

import { Action } from '@ngrx/store';
import { ActionTypes } from '../action/todo.action';

export const initialList: Array<string> = [];

export function todoReducer(state = initialList, action: Action): Array<string> {
    switch (action.type) {
        case ActionTypes.Add:
            // 末尾に新規のものを追加
            // state.push(action['payload']['text'])だとstateが変わってしまうのでNG
            return [...state, action['payload']['text']];
        case ActionTypes.Delete:
            // 末尾を削除
            // state.pop()だとstateが変わってしまうのでNG
            return state.filter((value, index) => index !== (state.length - 1));
        case ActionTypes.Reset:
            return [];
        default:
            return state;
    }
}

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 { todoReducer } from './reducer/todo.reducer';

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

UI

TODO登録するUI。
テキストボックスと「追加」、「削除」、「リセット」の3ボタンを置いただけ。
追加したTODOはリスト表示する。

app.component.html

<input type="text" #text><br>
<button type="button" (click)="add(text.value)">Add</button>
<button type="button" (click)="delete()">Delete</button>
<button type="button" (click)="reset()">Reset</button>
<ul>
  <li *ngFor="let todo of todolist">{{todo}}</li>
</ul>

app.component.ts

import { Component } from '@angular/core';

import { Store, select } from '@ngrx/store';
import { Observable } from 'rxjs';
import { Add, Delete, Reset } from './action/todo.action';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  public todo$: Observable<Array<string>>;
  public todolist: Array<string> = [];

  constructor(private store: Store<{ todo: Array<string> }>) {
    this.todo$ = store.pipe(select('todo'));
    this.todo$.subscribe(res => this.todolist = res);
  }

  public add(value: string) {
    this.store.dispatch(new Add({ text: value }));
  }
  public delete() {
    this.store.dispatch(new Delete());
  }
  public reset() {
    this.store.dispatch(new Reset());
  }
}

まとめ

今回使ったのはNgRxが提供してるライブラリの中でも一番とっつきやすいところをやってみた。
APIコールとか副作用があるものに向けた@ngrx/effectsとかもあるからそのうち触ってみる。

参考サイト