LoginSignup
0
0

[Angular][NgRX]effectでStateの値を参照する方法

Posted at

社長のみなさん! 4月ですよ~!

うん、4月が始まり既に3日も過ぎてるけど、これだけ言いたかったの!!

社会人2年目のみなさん!【新人】という肩書が消えましたよ~!

これからは上からだけではなく、下からも圧が凄いかもしれないけど、しっかり働いてくださいね!!
新人の中にエースとかいると、マジで追い抜かれるんじゃないかとか、自分の役割がうばわれていくんじゃないかという不安で眠れないかもしれないけど、頑張ってね!
!!!!震えて眠れ!!!!

そんなこんなでタイトルのヤツ

以下のような、すごくまどろっこしい実装をしてる箇所がスッキリするねって話。

  • Stateの値をComponent内でSelector経由してsubscribeしてComponentの内部変数に格納する
  • Action呼び出す時にその値を引数として渡す
  • Effectでそのパラメータを使って処理をする

なんのこっちゃって感じなので、データのフローを簡単に書いてみるとこんな感じ
State→Selector→Component→Action→Effect

実際に対象の値を使いたいのはEffect内なのに、Selector~Actionという余計なフローがあって邪魔じゃね?ってなり、
createEffectでStoreの値が直接参照できたら素敵やん ということで、
ソレのサンプルをどうぞ。

サンプル

AngularはStandaloneで作る感じで、
とりあえず前フリ的なState、Selector、Action、Reduserと
ソレをアプリ起動時に登録するmain.tsの一部

state.ts
// 名前付きのステートを作るため
export const featureName = 'appState';

export interface State {
  hoge: string;
  fuga: number;
}
export const initialState: State = {
  hoge: "初期値",
  fuga: 1,
}
selector.ts
import { createFeatureSelector, createSelector } from '@ngrx/store';
import {featureName, State} from './state';

const featureSelector = createFeatureSelector<State>(featureName);

// hogeだけ取ってくるヤツ(今回の話には関係ないけど、普通こんなの書くでしょ?)
export const hoge = createSelector(
  featureSelector,
  (state) => state.hoge
);
action.ts
import { createAction, props } from '@ngrx/store';

export const DoSomething = createAction(
  '一意になる名前([機能名]Action名(DoSomethingみたいな))',
  props<{
    dispValue: string; // 純粋な画面からのパラメータ
  }>() // propsを使うときはここの()を忘れずに!
);
// DoSomethingが成功した時に呼ばれるStoreを更新するためのAction
export const DoSomethingSuccess = createAction(
  '一意になる名前([機能名]Action名(DoSomethingSuccessみたいな))',
  props<{
    hoge: string;
    fuga: number;
  }>() // propsを使うときはここの()を忘れずに!
);
reduser.ts
import { createReducer, on } from '@ngrx/store';
import {DoSomethingSuccess} from './action';
import {initialState, State} from './state';

export const reducer = createReducer(
  initialState,
  on(DoSomethingSuccess, (state, value): State => {
    console.debug('DoSomethingSuccess', state, value);
    return {
      ...state,
      ...value,
    };
  }),
);
main.ts(一部抜粋)
// importとか省略
import { bootstrapApplication } from '@angular/platform-browser';
import { featureName, State } from './state';
import { reduser } from './reduser';
import { Effect } from './effect';
import { provideEffects } from '@ngrx/effects';
import { provideState, provideStore } from '@ngrx/store';

bootstrapApplication(AppComponent, {
  providers: [
    // 省略
    // Store使うよ宣言
    provideStore(),
    provideState({ name: featureName, reducer: reduser }),
    provideEffects(Effect),
    // ちなみにStoreやEffectを機能別でいくつも作りたい場合は上記を何行も書く
    // provideState({ name: featureName1, reducer: reduser1 }),
    // provideState({ name: featureName2, reducer: reduser2 }),
    // provideEffects(Effect1),
    // provideEffects(Effect2),
  ],
});

今回のメインのEffect

effect.ts
import { Store } from '@ngrx/store';
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import {
  catchError,
  concatMap,
  switchMap,
  tap,
  withLatestFrom,
} from 'rxjs/operators';
import { DoSomething, DoSomethingSuccess} from './action';
import { featureName, State } from './state';

@Injectable()
export class Effect {
  constructor(
    private actions$: Actions,
    // constructorでStoreを定義しておく!
    private store$: Store,
  ) {}

  doSomething$ = createEffect(() =>
    this.actions$.pipe(
      ofType(DoSomething),
      tap(() => console.debug('Effect DoSomething')),
      // 以下を書くことで、その後のconcatMapとかの引数にStateを設定できる
      withLatestFrom(this.store$),
      concatMap(([action, store]) => {
        // 引数のstateは定義したStateの全部が入ってしまっているため、
        // 以下のように名前を指定して取得を行う。
        const state: State = store[featureName];
        console.debug(state.hoge);
        console.debug(state.fuga);
        
        // Actionのパラメータはこんな感じで取得
        console.debug(action.dispValue);
        return [DoSomethingSuccess({
          hoge: "値を変える",
          fuga: 100000
        })]; 
      })
    )
  );
}

withLatestFrom でサンプルを書いてみましたが、
concatLatestFrom っていうのでも取得はできるようです。

更に言うとStoreなので、普通にselectとかdispatchとかも書けるため、
以下のような感じでSelectorの結果をそのまま使うことも出来るってよ。

effect抜粋
import { hoge } from './selector';
@Injectable()
export class Effect {
  constructor(
    private actions$: Actions,
    // constructorでStoreを定義しておく!
    private store$: Store,
  ) {}

  doSomething$ = createEffect(() =>
    this.actions$.pipe(
      ofType(DoSomething),
      tap(() => console.debug('Effect DoSomething')),
      // 以下を書くことで、その後のconcatMapとかの引数にStateを設定できる
      concatLatestFrom(action => this.store.select(hoge)),
      concatMap(([action, hoge]) => {
        console.debug(state.hoge);
        return [DoSomethingSuccess({
          hoge: "値を変える",
          fuga: 100000
        })]; 
      })
    )
  );
}

ね、簡単でしょ?

公式もちゃんと読もうね!

公式な説明はこちら

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0