Immer: Does not work with Angular+NGRX+AOT

Created on 15 Nov 2018  路  10Comments  路  Source: immerjs/immer

Please specify:

(Not filling out _all fields_ of this template will probably result in your issue being closed without further notice. If you are not willing fill it in, why would someone else spent his time fixing it?)

  • [x ] Issue: When used with Angular 7 with AOT turned on, this loads with no states; regular

    • SIMPLE Reproduction:

https://github.com/billdwhite/ngrx-immer/tree/ngrx-immer

Check out, run npm install, then run using
'ng serve'
it loads fine; now use AOT compilation:
'ng serve --aot'
or use the angular.json run config:
'ng run ngrx-immer:serve:production'
no states are loaded

Most helpful comment

With ngrx/store 8+ they have a new way to create reducers with createReducer and on.

I've made a small helper function for using immer with createReducer.

import produce, { Draft } from 'immer';
import { ActionCreator, createReducer, on } from '@ngrx/store';
import { ActionType, FunctionWithParametersType } from '@ngrx/store/src/models';

function produceOn<Type extends string, C extends FunctionWithParametersType<any, object>, State>(
  actionType: ActionCreator<Type, C>,
  callback: (draft: Draft<State>, action: ActionType<ActionCreator<Type, C>>) => any,
) {
  return on(actionType, (state: State, action): State => produce(state, (draft) => callback(draft, action)));
}

// Usage:

const featureReducer = createReducer(
  initialState,
  produceOn(action, (draft, action) => {
     // TODO STUFF
  }
);

export function reducer(state = initialState, action) {
  return featureReducer(state, action);
}

All 10 comments

https://github.com/timdeschryver/ngrx-immer/issues/1

Turns out that you have to set up the reducers to return a function, not a const.

I add some code in case somebody else have same issue and come to this thread

Old code

  export const reducer = produce<AppraisalState, AppraisalActions>((draftState, appraisalAction) => {
    switch (appraisalAction.type) {

      case AppraisalActionTypes.LoadAppraisals:
        {
            draftState.xx = appraisalAction.payload;
            return;
             ...
        }
    }
  }, initialState);
}

New Code

export function reducer(state: AppraisalState = initialState, action: Action) {

  return produce<AppraisalState, AppraisalActions>((draftState, appraisalAction) => {
    switch (appraisalAction.type) {

      case AppraisalActionTypes.LoadAppraisals:
        {
            draftState.xx = appraisalAction.payload;
            return;
             ...
        }
    }
  })(state, action as AppraisalActions);
}

@RezaRahmati You can simplify that further:

export function reducer(state: AppraisalState = initialState, action: AppraisalActions) {
  return produce(state, draftState => {
    switch (appraisalAction.type) {
      case AppraisalActionTypes.LoadAppraisals:
        draftState.xx = appraisalAction.payload;
        return;
    }
  });
}

With ngrx/store 8+ they have a new way to create reducers with createReducer and on.

I've made a small helper function for using immer with createReducer.

import produce, { Draft } from 'immer';
import { ActionCreator, createReducer, on } from '@ngrx/store';
import { ActionType, FunctionWithParametersType } from '@ngrx/store/src/models';

function produceOn<Type extends string, C extends FunctionWithParametersType<any, object>, State>(
  actionType: ActionCreator<Type, C>,
  callback: (draft: Draft<State>, action: ActionType<ActionCreator<Type, C>>) => any,
) {
  return on(actionType, (state: State, action): State => produce(state, (draft) => callback(draft, action)));
}

// Usage:

const featureReducer = createReducer(
  initialState,
  produceOn(action, (draft, action) => {
     // TODO STUFF
  }
);

export function reducer(state = initialState, action) {
  return featureReducer(state, action);
}

to add onto the example:

import {createReducer} from '@ngrx/store';
import {on} from "@ngrx/store";
import produce, {Draft} from "immer";

export const initialUserState: IUserState = {
    knownUsers: [user1, user2],
    selectedUser: null,
    scenes: null
};

export function produceOn<Type extends string, C extends FunctionWithParametersType<any, object>, State>(
    actionType: ActionCreator<Type, C>,
    callback: (draft: Draft<State>, action: ActionType<ActionCreator<Type, C>>) => any,
) {return on(actionType, (state: State, action): State => produce(state, (draft) => callback(draft, action)));}

export const loadRequest = createAction('[Scenes API] Scene Load Request', props<{ businessId: BusinessId }>());
export const loadSuccess = createAction('[Scenes API] Scene Load Success', props<{ scenes: List<SceneModel> }>());

// ngrx 8+ with immer and support for on() within reducer

const featureReducer = createReducer(
    initialUserState,
    produceOn(loadSuccess, (draft, action) => {
        draft.scenes = {myList: [1,2,3]};
    }),
    produceOn(loadFailure, (draft, action) => {
        draft.scenes = {myList: []};
        console.log('error loading...');
    })
);

Thank you for solution! However, I see two potential problems:

  • You acces a couple private NGRX models / types
  • Afaics you still lose type safety for state / actions?

@TekSiDoT if you want to use Immer with NgRx 8 in a type-safe way there's mutableOn from ngrx-etc.

const entityReducer = createReducer(
  {
    entities: {},
  },
  mutableOn(create, (state, { type, ...entity }) => {
    state.entities[entity.id] = entity
  }),
  mutableOn(update, (state, { id, newName }) => {
    const entity = state.entities[id]
    if (entity) {
      entity.name = newName
    }
  }),
  mutableOn(remove, (state, { id }) => {
    delete state.entities[id]
  }),
)

Hi @TekSiDoT and @timdeschryver
question, What do you say produceOn accesses privates and is not type safe?
I wonder why as I do have type help and having a hard time understanding which private is being accessed. Thanks again for the link,
Sean.

@born2net : Sorry for the confusion, my bad, no privates accessed
@timdeschryver : thanks for the hint, we've since introduced mutableOn in our code base and are very happy with the results.

@born2net sorry, maybe type safe is badly worded.
What I meant is that the on function supports more than one action type is parameter, whereas produceOn does not - yes you could add more typings to support it.

In fact, what ngrx-etc does, is more or less the same code as in your snippet.

Was this page helpful?
0 / 5 - 0 ratings