【NgRx】@ngrx/entityを使ったCRUDについて

前提

件名の通り、@ngrx/entityを使ってCRUDの機能を持った画面を実装しています。
CRUDの機能は画面だけで完結するものではなく、サーバーと通信をしています。
その通信の処理は、@ngrx/effectsを使って実装しています。
(よくあるパターンかと思ってます。)

画面は以下の3画面あります。

  • 一覧画面
  • 登録画面
  • 編集画面

画面の機能は以下です。

  • 一覧画面はentityの一覧表示と、削除ができる
  • 登録画面はentityの追加ができ、登録完了後に一覧画面に遷移する
  • 編集画面はentityの更新ができ、追加完了後に一覧画面に遷移する

問題点

登録画面からentityの追加をして一覧画面が表示されたとき、追加したentityが一覧に表示されていない。

解決したいこと

登録画面からentityの追加をして一覧画面が表示されたとき、追加したentityを一覧に表示させたい。

現象

Redux DevToolsで検証したところ、登録のアクション、登録成功のアクション、一覧のアクション、一覧成功のアクションは適切な順番で呼び出されていることは確認できました。

また、登録成功のアクションで追加したentityがStoreに保存されることは確認できました。

ここまでは想定通りだったのですが、一覧成功のアクションで古いStoreの状態を返していることが分かりました。

古いStoreの状態というのは、登録成功のアクションで追加したentityがStoreに保存される前の状態のStoreです。

該当ソースコード

// hoge-list-page.component.ts

import { Component, OnInit } from '@angular/core';
import { select, Store } from '@ngrx/store';

import * as fromHoge from '@app/hoge/reducers';
import { HogeActions } from '@app/hoge/actions';

@Component({
  selector: 'app-hoge-list-page',
  templateUrl: './hoge-list-page.component.html',
  styleUrls: ['./hoge-list-page.component.scss'],
})
export class HogeListPageComponent implements OnInit {
  hogeList$ = this.store.pipe(select(fromHoge.selectAllHoges));

  constructor(
    private store: Store<fromHoge.State>,
  ) {}

  ngOnInit() {
    this.store.dispatch(HogeActions.loadHogeList());
  }
}
// hoge-reducer.ts

import { createReducer, on } from '@ngrx/store';
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';

import { HogeActions } from '@app/hoge/actions';
import { Hoge } from '@app/hoge/models';

export const hogeFeatureKey = 'hoge';

export interface State extends EntityState<Hoge> {}

export const adapter: EntityAdapter<Hoge> = createEntityAdapter<Hoge>();

export const initialState: State = adapter.getInitialState();

export const reducer = createReducer(
  initialState,
  on(HogeActions.loadHogeListSuccess, (state, { hogeList }) =>
    adapter.setAll(hogeList, state),
  ),
  on(HogeActions.createHogeSuccess, (state, { hoge }) =>
    adapter.addOne(hoge, state),
  ),
  on(HogeActions.updateHogeSuccess, (state, { hoge }) =>
    adapter.updateOne(hoge, state),
  ),
  on(HogeActions.deleteHogeSuccess, (state, { id }) =>
    adapter.removeOne(id, state),
  ),
);

export const {
  selectIds: selectHogeIds,
  selectEntities: selectHogeEntities,
  selectAll: selectAllHoges,
  selectTotal: selectTotalHoges,
} = adapter.getSelectors();
// reducers/index.ts

import {
  ActionReducerMap,
  createFeatureSelector,
  createSelector,
} from '@ngrx/store';

import * as fromHoge from '@app/hoge/reducers/hoge.reducer';
import * as fromRoot from '@app/reducers';

export const hogeFeatureKey = 'hoge';

export interface HogeState {
  [fromHoge.hogeFeatureKey]: fromHoge.State;
}

export interface State extends fromRoot.State {
  [hogeFeatureKey]: HogeState;
}

export const reducers: ActionReducerMap<HogeState> = {
  [fromHoge.hogeFeatureKey]: fromHoge.reducer,
};

export const selectHogeState = createFeatureSelector<State, HogeState>(
  hogeFeatureKey,
);

export const selectHogeEntitiesState = createSelector(
  selectHogeState,
  (state) => state[fromHoge.hogeFeatureKey],
);

export const selectAllHoges = createSelector(
  selectHogeEntitiesState,
  fromHoge.selectAllHoges,
);
// hoge.effects.ts

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { catchError, map, switchMap, tap } from 'rxjs/operators';

import { HogeActions } from '@app/hoge/actions';
import { HogeApiService } from '@app/hoge/services';

  loadHogeList$ = createEffect(() =>
    this.actions$.pipe(
      ofType(HogeActions.loadHogeList),
      switchMap(() =>
        this.hogeApiService
          .getHogeList()
          .pipe(
            map((hogeList) => HogeActions.loadHogeListSuccess({ hogeList })),
          ),
      ),
    ),
  );

  createHoge$ = createEffect(() =>
    this.actions$.pipe(
      ofType(HogeActions.createHoge),
      switchMap(({ hoge }) =>
        this.hogeApiService.createHoge(hoge).pipe(
          map(() => HogeActions.createHogeSuccess({ hoge })),
          catchError(() => of(HogeActions.createHogeFailure())),
        ),
      ),
    ),
  );

  createHogeSuccess$ = createEffect(
    () =>
      this.actions$.pipe(
        ofType(HogeActions.createHogeSuccess),
        tap(() => this.router.navigate(['/hoges'])),
      ),
    { dispatch: false },
  );

  constructor(
    private router: Router,
    private actions$: Actions,
    private hogeApiService: HogeApiService,
  ) {}

Actionの生成部分も見せていただけますか?

// hoge.action.ts

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

import { Hoge, Params } from '@app/hoge/models';

export const loadHogeList = createAction(
  '[Hoge] Load Hoge List',
  props<{ params: Params }>(),
);

export const loadHogeListSuccess = createAction(
  '[Hoge] Load Hoge List Success',
  props<{ hogeList: Hoge[] }>(),
);

export const loadHogeListFailure = createAction(
  '[Hoge] Load Hoge List Failure',
  props<{ error: any }>(),
);

export const createHoge = createAction(
  '[Hoge] Create Hoge',
  props<{ hoge: Hoge }>(),
);

export const createHogeSuccess = createAction(
  '[Hoge] Create Hoge Success',
  props<{ hoge: Hoge }>(),
);

export const createHogeFailure = createAction('[Hoge] Create Hoge Failure');

export const updateHoge = createAction(
  '[Hoge] Update Hoge',
  props<{ hoge: Hoge }>(),
);

export const updateHogeSuccess = createAction(
  '[Hoge] Update Hoge Success',
  props<{ hoge: Update<Hoge> }>(),
);

export const updateHogeFailure = createAction('[Hoge] Update Hoge Failure');

export const deleteHoge = createAction(
  '[Hoge] Delete Hoge',
  props<{ id: string }>(),
);

export const deleteHogeSuccess = createAction(
  '[Hoge] Delete Hoge Success',
  props<{ id: string }>(),
);

export const deleteHogeFailure = createAction('[Hoge] Delete Hoge Failure');
1 Like

アクションのソースコードです。

こちらのトラブルなんですが、キャッシュが効いていたせいか、時間がたったらできるようになりました。

ご迷惑おかけしました。

2 Likes

こちらの問題、キャッシュの問題ではありませんでした。

サーバーがリソースを登録する前に
GETリクエストしてしまい、一覧には表示されていないということでした。

この問題はどのように対処したらよいかアドバイスいただきたいです。

リクエストdelayさせるインターセプター実装して解決