Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When ngrx store state contains a Map, why are changes to this Map not recognized?

I migrated to Ngrx Store v4.1.1 (+ Angular5) according to their example-app. Everything runs fine as before but one SubStore. That SubStore's state contains a Map which is changed. But changes to this Map are somehow not recognized.

Working Plunker can be found here: https://plnkr.co/edit/2Z77Cq?p=preview Detailed code below

My NgModule looks like that:

import {reducers} from './reducers';

@NgModule({
   imports: [ 
     BrowserModule,
     StoreModule.forRoot(reducers) 
   ],
   declarations: [ App ],
   bootstrap: [ App ],
   providers: [Service]
})

My reducers look like that:

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

import * as character from './character.reducer';

export interface State {
  character: character.State;
}

export const reducers: ActionReducerMap<State> = {
  character: character.reducer,
};

/** Character **/
export const getCharacterState = createFeatureSelector<character.State>('character');

export const getCharacter = createSelector(
  getCharacterState,
  character.getCharacter
);

The SubStore Reducer contains the following code:

import { Character, Item } from './models';
import * as character from './character';

export interface State {
  character: Character;
}

export const initialState: State = {
  character: null,
};

export function reducer(state = initialState, action:character.Actions): State {
  switch (action.type) {
    case character.INIT_CHARACTER:
      const char: Character = action.payload;
      state.character = char;
      console.log('init char', char);
      return Object.assign({}, state);

    case character.EQUIP_ITEM:
      const eqItem: Item = action.payload;
      state.character.wardrobeItemIds.set(eqItem.part, eqItem.id);
      console.log('eq ITEMMM', eqItem, state.character.wardrobeItemIds);
      return Object.assign({}, state);

    default:
      return state;
  }
}

export const getCharacter = (state: State) => state.character;

With the corresponding Actions being:

import { Action } from '@ngrx/store';
import { Character, Item } from './models';

export const INIT_CHARACTER = '[Character] Initialized Character';
export const EQUIP_ITEM = '[Character] Equipped Item';

export class InitCharacter implements Action {
  readonly type = INIT_CHARACTER;
  constructor(public payload: Character) {}
}

export class EqItem implements Action {
  readonly type = EQUIP_ITEM;
  constructor(public payload: Item) {}
}

export type Actions =
  InitCharacter |
  EqItem;

Now I initialize the SubStore with a new Character in my Service:

import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import {Character, Item} from './models';

import * as fromRoot from './reducers.ts';
import * as CharacterAction from './character.ts';

@Injectable()
export class Service {
  character$: Observable<Character>;

  constructor(
    private store: Store<fromRoot.State>
  ) {
    // listen to the store
    this.character$ = this.store.select(fromRoot.getCharacter);

    this.character$.subscribe(
      (state: any) => console.log('char store triggered. State:', state)
    );

    // init the wardrobeItemIds Map
    const wardrobeItemIds = new Map<string, string>();
    wardrobeItemIds.set('part1', 'anyId');

    // init the character (this is just a dummy)
    let newCharacter: Character = {
      baseType: 'anyString',
      skinItemIds: [
        'string1',
        'string2'
      ],
      wardrobeItemIds: wardrobeItemIds
    }
    this.store.dispatch(new CharacterAction.InitCharacter(newCharacter));
  }

  addItem(part: string): void {
    // add rnd item of given part
    const item: EquipItem = {
      id: Math.random().toString(),
      part: part,
    }
    this.store.dispatch(new CharacterAction.EqItem(item));
  }
} 

This causes my Subscription inside the same Service

this.character$.subscribe(
  (state: any) => console.log('char store triggered. State:', state)
);

to log the character, which is fine, because it changed from null to the character Object.

Now if I call addItem() which calls this.store.dispatch(new CharacterAction.EqItem(item)); which should add an item to the Map state.character.wardrobeItemIds.

This should cause the Store Observable to fire again and the subscription should log the changed character. But somehow nothing happens. Already checked that the reducer receives the Action properly.

Not sure if this just my stupidity or some kind of bug?

Thx in advance Tobi

like image 400
lolgans Avatar asked Mar 07 '23 10:03

lolgans


1 Answers

It seems that @ngrx/entity is the intended solution for more complex data structures in the store. The use of Map will trigger the new runtime checks if you turn them on, as they are neither immutable not serializable.

like image 177
bmargulies Avatar answered May 13 '23 11:05

bmargulies