みなさんこんにちは、現役エンジニアのサメハックです
アパレル企業でトップ販売員を経て
未経験から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フォローしてもらえると嬉しいです🦈