Reduxパターンは、特にアプリケーションがより複雑になった場合に、Webアプリの状態を管理するための非常に強力な方法です。 ReduxライブラリはReactで最もよく使用されますが、 ngrx / store ライブラリとRxJSの機能のおかげで、AngularアプリでReduxのような方法でアプリの状態を管理できます。

ここでは、Reduxの3つの基本原則について簡単に復習します。

  • アプリの状態全体が単一の状態ツリーに保存されます。
  • 状態は読み取り専用です。
  • 状態の変更は、純粋関数(オブジェクトを変更せず、代わりに完全に新しいオブジェクトを返す関数)のみを使用するレデューサーを介して行われます。

Reduxの詳細については、公式ドキュメントを参照してください。

この投稿は、 ngrx4+と互換性があるように更新されました。 TypeScript 2.4+およびRxJS5.4+が必要です。


この投稿では、todoを追加、削除、更新したり、todoに完了のマークを付けたりできる、非常にシンプルなtodoアプリを作成します。

入門

まず、 ngrx / store が必要です。これは、npmまたはYarnを使用してプロジェクトにインストールできます。

# npm
npm install @ngrx/store --save

# Yarn
yarn add @ngrx/store

私たちのTodoレデューサー

それでは、先に進んで、todoアプリ用の単純なレデューサーを作成しましょう。 これがおなじみになる前にレデューサーを書いたことがある場合:

reducers / todo.reducer.ts
import { Action } from '@ngrx/store';

export const ADD_TODO = 'ADD_TODO';
export const DELETE_TODO = 'DELETE_TODO';
export const UPDATE_TODO = 'UPDATE_TODO';
export const TOGGLE_DONE = 'TOGGLE_DONE';
export interface ActionWithPayload<T> extends Action {
  payload: T;
}
export interface TodoPayload {
  index?: number;
  done?: boolean;
  value?: string;
  newValue?: string;
}

アクションには、タイプとオプションのペイロードがあります。 タイプは文字列である必要があるため、ここでは、さまざまなタイプを保持する定数を定義してエクスポートします。 レデューサー関数自体が状態とアクションを取得し、switchステートメントを使用してアクションタイプに応じて正しい状態を返します。

switchステートメントは、提供されたアクションが事前定義されたアクションのいずれとも一致しない場合に状態を返すdefault句を定義します。

switchステートメントで、操作が現在の状態を変更するのではなく、常に新しい状態を返す方法に注意してください。

Appモジュールの構成

レデューサーが配置されたので、 ngrx /storeモジュールとレデューサーを使用してアプリモジュールを構成できます。

app.module.ts
// ...

import { AppComponent } from './app.component';
import { StoreModule } from '@ngrx/store';
import { todoReducer } from './reducers/todo.reducer';

StoreModule をインポートし、provideStoreメソッドとレデューサーの名前を使用してNgModuleのインポートに追加します。

コンポーネントでの選択とディスパッチ

レデューサーが配置され、アプリモジュールが適切に構成されたので、ngrxのStoreサービスをコンポーネントに注入できます。 次に、Storeサービスを使用してデータを選択し、アクションをディスパッチするのと同じくらい簡単です。

Store.selectでデータを選択すると、戻り値は監視可能になります。これにより、テンプレートで非同期パイプを使用して、データのサブスクリプションを管理できます。

これがコンポーネントクラスの実装です。いくつかの重要な項目が強調表示されています。

app.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';

import { Store } from '@ngrx/store';
import { ADD_TODO, DELETE_TODO, UPDATE_TODO, TOGGLE_DONE }
  from './reducers/todo.reducer';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styles: [
    .done { text-decoration: line-through; color: salmon; }
    ]
})
export class AppComponent implements OnInit {
  todos$: Observable<any>;
  todo: string;
  editing = false;
  indexToEdit: number | null;
  constructor(private store: Store<any>) {}
  ngOnInit() {
    this.todos$ = this.store.select('todoReducer');
  }
  addTodo(value) {
    this.store.dispatch({ type: ADD_TODO, payload: { value, done: false } });
    this.todo = '';
  }
  deleteTodo(index) {
    this.store.dispatch({ type: DELETE_TODO, payload: { index } });
  }
  editTodo(todo, index) {
    this.editing = true;
    this.todo = todo.value;
    this.indexToEdit = index;
  }
  cancelEdit() {
    this.editing = false;
    this.todo = '';
    this.indexToEdit = null;
  }
  updateTodo(updatedTodo) {
    this.store.dispatch({ type: UPDATE_TODO, payload: { index: this.indexToEdit, newValue: updatedTodo } });
    this.todo = '';
    this.indexToEdit = null;
    this.editing = false;
  }

コンポーネントクラスは非常に単純であり、そのほとんどがストアへのディスパッチアクションであることがわかります。

コンポーネントテンプレート

コンポーネントテンプレートは、次のように単純です。

<input placeholder="your todo" [(ngModel)]="todo">

<button
  (click)="addTodo(todo)"
  [disabled]="!todo"
  *ngIf="!editing">
    Add todo
</button>

<button
  (click)="updateTodo(todo)"
  *ngIf="editing">
    Update
</button>
<button
  (click)="cancelEdit()"
  *ngIf="editing">
    Cancel
</button>


<ul>
  <li *ngFor="let todo of todos$ | async; let i = index;">
    <span [class.done]="todo.done">{{ todo.value }}</span>
    <button (click)="editTodo(todo, i)">Edit</button>
    <button (click)="toggleDone(todo, i)">Toggle Done</button>
    <button (click)="deleteTodo(i)">X</button>
  </li>
</ul>

🍰そして、あなたはそれを持っています! ngrx / storeのおかげで、非常にシンプルですが、Reduxスタイルの状態管理を備えた機能的なtodoアプリです。