首页 > 解决方案 > 在 NGRX 中任何动作调度后执行效果

问题描述

我正在使用 NGRX 和 Angular 7。

我有一个仅用于用户设置(用户首选项)的商店

这是一个简短的版本=>

import { Action } from '@ngrx/store';
import * as featureModels from './models';

export enum ActionTypes {
  SetSettings = '[SETTINGS] Set Settings',
  SetNavigationSettings = '[SETTINGS] Set Navigation Settings',
}

export class SetNavigationSettings implements Action {
  public readonly type = ActionTypes.SetNavigationSettings;
  constructor(public payload: {settings: Partial<featureModels.INavigationSettings>}) {}
}

export class SetSettings implements Action {
  public readonly type = ActionTypes.SetSettings;
  constructor(public payload: {settings: featureModels.ISettings}) {}
}

export type Actions = SetNavigationSettings |
                      SetSettings;

更改任何设置后,我想执行一个效果,它将当前设置存储在本地存储中:

目前,在我的效果中,我只是使用这样的选择器,它会在任何状态更改后触发(所以它工作正常)=>

export class SettingsEffects {

  constructor(
    private actions$: Actions,
    private dataService: SettingsDataService,
    private localStorageService: LocalStorageService,
    private store$: Store<IAppState>
  ) {
    this.store$.pipe(select(featureSelector.selectSettingsState)).subscribe((settings) => {
          //save
    });
  }

  @Effect()
  init$ = this.actions$.pipe(
  ofType(ROOT_EFFECTS_INIT),
  switchMap(action => {
    const settings = this.localStorageService.retrieve('settings');
    console.log(settings)
    if (settings) {
      return of(new featureActions.SetSettings({settings}));
    } else {
      return EMPTY;
    }
  })
);

但是,这将在初始化时执行,因此在我的 INIT Effect 之前,它将始终用存储初始状态覆盖 localStorage 值。这将使我的 Init 效果仅从本地存储中检索初始状态。

我可以将商店选择放在初始化效果中(并且效果很好)

但我想知道是否有一种方法不使用选择器/订阅,而只使用效果。这样每次用户触发一个动作时,它都会保存。

标签: angularngrxngrx-store

解决方案


正如官方 NgRx 文档中提到的,您可以考虑使用 MetaReducers。

开发人员可以将 meta-reducer 视为 action->reducer 管道的挂钩。元减速器允许开发人员在调用普通减速器之前预处理操作。

使用meta-reducer,您可以在每次执行操作时执行代码。如上所述,此代码在调用普通 reducer 之前执行。在您的情况下,要存储新状态(在执行当前操作之后),您应该在调用reducer(state, action).

export function debug(reducer: ActionReducer<any>): ActionReducer<any> {
  return function (state, action) {
    const nextState = reducer(state, action);

    console.log('log action', action);
    console.log('log state', state);
    console.log('log next state', nextState);

    // store nextState in local storage
    localStorage.setItem('state', JSON.stringify(nextState));

    return nextState;
  };
}

我准备了一个Stackblitz 演示来说明这个答案。

但是,我可以通过与您分享个人观点来建议您另一种选择吗?事实上,meta-reducer每个动作都被调用。这可能会导致大量不需要的存储调用。

在这种情况下,我宁愿在每个相关效果中调用另一个特定操作来明确请求状态保存。但很明显,缺点是一些重复的代码,并且有漏接电话的风险。


推荐阅读