2
votes

I am using NGRX and Angular 7.

I have a store that is only used for the user settings (user preferences)

Here is a short version =>

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;

After any setting has changed, I would like to execute an effect, that will store the current settings in the local storage:

At the moment in my effect, I am just using a selector like this that trigger after any state changes (so it works ok) =>

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;
    }
  })
);

But, this will be executed on initialization, so before my INIT Effect, and it will always override the localStorage value with the store initial state. This will make my Init effect to just retrieve the initial state from the localstorage.

I can put the store selection inside the Init Effect (and it works fine)

But I was wondering if there is a way without using a selector/subscription, and just using an effect. So that every time the user trigger an action, it will save.

1
I guess you are searching for an meta effect. Please google this wording and you will likely find what you need.MoxxiManagarm

1 Answers

1
votes

As mentioned in official NgRx docs, you can consider using MetaReducers.

Developers can think of meta-reducers as hooks into the action->reducer pipeline. Meta-reducers allow developers to pre-process actions before normal reducers are invoked.

With meta-reducer, you're able to execute code each time an action is performed. As mentioned just above, this code is executed before normal reducers are invoked. In your case, to store the new state (after current action is performed), you should use the returned state after calling 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;
  };
}

I've prepared a Stackblitz demo to illustration this answer.

However, may I suggest you another option by sharing with you a personal point of view ? In fact, meta-reducer is called for each action. It could result with a lot of unwanted storage calls.

In this situation, I would prefer calling another specific action in each concerned effect to explicitly request for state saving. But it's clear that the drawback is some repetive code, and a risk to miss a call.