【NgRx】引数 props を渡してSTOREを更新しよう!【Angular】

ANGULAR

みなさんこんにちは、現役エンジニアのサメハックです

アパレル企業でトップ販売員を経て
未経験からWebエンジニアに転職し、
現在正社員として5年働いています!

Angularの解説シリーズです。

今回はNgRxで引数を渡す方法について学んでいきましょう!

駆け出しエンジニアや未経験の方、
また新入社員を指導する先輩社員にとっても
わかりやすいように解説していきます!

この記事を読むと・・・
  • NgRxで引数を渡してSTOREを更新することができる
  • サンプルコードがダウンロードできる

※PCにnpm、nodeがインストールされている前提で記述します。
 yarn等をお使いの方は読み替えてください。

今回の内容は難易度がかなり高いので、
何度も読み直してね!

作りたいもの

今回つくりたいのは、input内のテキストを引数として渡して
任意の値でSTOREのSTATEを更新するアプリケーションです。

大前提

【Angular】NgRxの概念を世界一わかりやすく解説!【NGRX】

今回作成するのは前回作成したユーザ情報更新アプリケーションに引数を渡せるようにしたものです。
NgRxの基礎について確認したい方は、そちらを先にご確認ください。

前回の記事もサンプルコードがあるので
ハンズオンしたい人はダウンロードしておいてね!

NgRxとは

NgRxとは、Reduxをベースに作られたAngularで状態(STATE)管理をするためのシステムです。

ライフサイクルは実質このようになっています。
データ処理は一方通行ではなく、

  • データ取得処理
  • データ更新処理

の2種類が存在する、と解釈してください。

今回はデータ更新処理をアップデートしよう!

ファイルをアップデートしよう

モジュール

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { UserComponent } from './user/user.component';
import { UserModule } from './user/user.module';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [AppComponent, UserComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    UserModule,
    // ngModelを扱えるようにする
    FormsModule,
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
追加したのはFormsModuleのインポート部分だよ!

ACTION

REDUCERに引数を渡す構文

createAction(
  `[呼び出されるコンポーネント名]関数名`,
  props<{ 引数名: 型}>()
)

user.action.ts

/* ____________________
ACTION
  各アクションがどこで呼ばれて、どのような処理をするのかを記述
  引数が必要であれば、ここで定義する。

  基本的にデバッグツールのログ確認用途で役に立ちます。

____________________ */

import { createAction, props } from '@ngrx/store';

export const countUpAction = createAction(`[User Component] countUpAction`);
export const countDownAction = createAction(`[User Component] countDownAction`);
export const changeNameAction = createAction(
  `[User Component] changeNameAction`,
  props<{ newName: string }>()
);
この引数名はCOMPONENT、REDUCERと統一する必要があるよ!

REDUCER

STOREを更新する構文

createReducer(
  initialState,
  on(アクション名, (state,{ 受け取った引数 }) => ({
    STATEの更新処理,
})),

user.reducer.ts

import { createReducer, on } from '@ngrx/store';
import { __asyncGenerator } from 'tslib';
import {
  countUpAction,
  countDownAction,
  changeNameAction,
} from './user.action';

export const featureName = 'user';

// 型の定義
export interface MyUserState {
  name: string;
  age: number;
}

/**初期値の設定 ※これをSTOREで状態管理する */
export const initialState: MyUserState = {
  name: 'サメハック',
  age: 100,
};

/**実際にSTOREのSTATEを操作する処理 */
const _userReducer = createReducer(
  initialState,
  on(countUpAction, (state) => ({
    ...state,
    age: state.age + 1,
  })),
  on(countDownAction, (state) => ({
    ...state,
    age: state.age - 1,
  })),
  // 今回更新した処理
  on(changeNameAction, (state, { newName }) => ({
    ...state,
    name: newName,
  }))
);

export function userReducer(state: any, action: any): MyUserState {
  return _userReducer(state, action);
}
ここで実際にSTOREを更新するよ!

COMPONENT

ACTIONに引数を渡す構文

this.store.dispatch(アクション名({引数名: 渡したい値}));
オブジェクト形式で渡すのがポイントだよ!

user.component.ts


import { Component, OnInit } from '@angular/core';
import { select, Store } from '@ngrx/store';
import { MyUserState } from './user.reducer';
import { selectUserAllInfo, selectUserAge } from './user.selector';
import {
  changeNameAction,
  countUpAction,
  countDownAction,
} from './user.action';

@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.scss'],
})
export class UserComponent implements OnInit {
  /* ____________________
  select・・・STOREのデータを受け取るための記述

  変数名 = this.store(select(SELECTORで作成した変数));
  ____________________ */

  /**inputで使用 */
  inputName: string = '';
  /**ユーザの全情報を取得 */
  user$ = this.store(select(selectUserAllInfo));
  /**ユーザの年齢を取得 */
  userAge$ = this.store(select(selectUserAge));

  constructor(private store: Store) {}

  // STOREデータの更新
  countUp() {
    this.store.dispatch(countUpAction());
  }

  // STOREデータの更新
  countDown() {
    this.store.dispatch(countDownAction());
  }

  /* ____________________
  dispatch・・・STOREのデータを更新するための記述

  引数をACTIONに引数を渡す
  this.store.dispatch(アクション名({引数名: 渡したい値}));
  ____________________ */
  // STOREデータの更新
  changeName(newValue: string) {
    this.store.dispatch(changeNameAction({ newName: newValue }));
  }

  ngOnInit(): void {}
}

user.component.html

<p>user works!</p>

<h2>ユーザ情報をすべて取得</h2>
<div *ngIf="user$ | async as user">
  name: {{ user.name }}
  <input type="text" [(ngModel)]="inputName" />
  <button (click)="changeName(inputName)">名前を変更</button>
  <br />
  age: {{ user.age }}
  <button (click)="countUp()">+</button>
  <button (click)="countDown()">ー</button>
</div>

<h2>ユーザの年齢のみ取得</h2>

<div *ngIf="userAge$ | async as userAge">
  age: {{ userAge }}
  <button (click)="countUp()">+</button>
  <button (click)="countDown()">ー</button>
</div>

その他ファイル

今回は変更を入れていないファイルも念の為載せておきます。

user.selector.ts

/* ____________________
SELECTOR
  ・STOREのデータを取得(連携)するための処理
    イメージ的には、getter()のようなもの。
  ・STOREデータ → コンポーネント
   の単方向データバインディングが行われる

createSelector(
  State,(全てのstate情報) => 欲しいデータを取り出す)
____________________ */
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { MyUserState, featureName } from './user.reducer';

export const selectUserState = createFeatureSelector(featureName);

/**ユーザの全情報を取得するセレクター */
export const selectUserAllInfo = createSelector(
  selectUserState,
  (state) => state
);

/**ユーザの年齢情報を取得するセレクター */
export const selectUserAge = createSelector(
  selectUserState,
  (state) => state.age
);
STORE→コンポーネントにわたすデータを定義しているよ!

user.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { StoreModule } from '@ngrx/store';
import { featureName, userReducer } from './user.reducer';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    // Storeを使用できるようにする設定
    StoreModule.forRoot({ [featureName]: userReducer }),
    // デバッグツールのインポート
    StoreDevtoolsModule.instrument(),
  ],
})
export class UserModule {}

app.component.html

<app-user></app-user>

サンプルコードダウンロード

今回使用したサンプルコードはGitHubに置いておきました。
NgRxが即使用できるシンプルなサンプルコードはなかなか見つからないので、
これを元に改版していただくと良いかと思います。

https://github.com/same-hack/NGRX-user-props.git

サンプルコードダウンロード&実行コマンド

git clone https://github.com/same-hack/NgRx-user-props.git
npm install 
npm start

実行環境

Angularのバージョンが古いと動かないことがあるので、
うまく動かなければアップデートしてね!

まとめ

今回のSTORE操作の処理をわかりやすく図解しました。

引数を扱うと処理の自由度があがるので、
非常に汎用性の高い技術かと思います

  • NgRx・・・Angularで状態(STATE)管理をするためのシステム
  • STORE・・・STATEを管理している空間。メモリ上の仮想DBのようなイメージ
  • COMPONENT・・・画面描画&操作に関わる部分
  • SELECTOR・・・データ取得(自動連携)の定義。getter()のようなもの
  • ACTION・・・どこでなんの関数を使用しているかを記述
  • REDUCER・・・STOREのSTATEを更新する。setter()のようなもの

満足いただけたら、1クリックなのでSNSフォローしてもらえると嬉しいです🦈

タイトルとURLをコピーしました