Search code examples
angularstate-managementngxs

Avoid image reload on state update


I'm using NGXS as my state manager. I have a "menuItems" object that populates my "sidenav" component:

export const menuItems: Array<MenuItem> = [


{
    icon: 'assets/circle-logo.png',
    label: 'Home',
    route: '/home',
    selected: false
  },
  {
    icon: 'assets/circle-logo.png',
    label: 'Customers',
    route: '/customers',
    selected: false
  },
  {
    icon: 'assets/circle-logo.png',
    label: 'Entries',
    route: '/entries',
    selected: false
  }];

when the application load the first time the image on the "icon" property is loaded normally.

enter image description here

The state is updated when I click on a item from my sidenav:

enter image description here

Then an index is send to the state, to change the property selected to "true" and highlight the item, as seen in the image above.

My problem is when I update the "menuItems" state the "logo-image" is reloaded again. I little "blink" is visible on the screen when the image load again.

enter image description here

This is the code of my sidenav state:

import { State, Action, StateContext } from '@ngxs/store';
import { MenuItem } from '@shared/types';
import { menuItems } from '@components/sidenav/menu-items';

export class SelectMenuItem {
  static readonly type = 'SelectMenuItem';
  constructor(public itemIndex: number) {}
}

@State<Array<MenuItem>>({
  name: 'menuItems',
  defaults: menuItems
})
export class SideNavState {
  @Action(SelectMenuItem)
  selectMenuItem(ctx: StateContext<Array<MenuItem>>, action: SelectMenuItem) {
    const { getState, setState } = ctx;
    const { itemIndex } = action;
    const state = getState();
    const currentItem = state.findIndex(s => s.selected);

    if (currentItem !== itemIndex) {
      const newState = state.map((item, index) => {
        if (currentItem > -1 && currentItem === index) {
          item = { ...item, selected: false };
        }
        if (itemIndex === index) {
          item = { ...item, selected: true };
        }
        return item;
      });
      setState(newState);
    }
  }
}

Am I not updating correctly the state, or there is a workaround for this behavior?


Solution

  • I think the issue here is that your template is reloading the entire list each item you want to select an item - because you are calling setState, which will cause anything subscribed to your state to receive an update.

    I'd suggest modelling your state differently e.g.

    export class SelectMenuItem {
      static readonly type = 'SelectMenuItem';
      constructor(public itemIndex: number) {}
    }
    
    // In the state model, capture the selected item/index
    // separate from the list of menu items.
    interface MenuItemStateModel { 
      selectedItem: number,
      menuItems: MenuItem[]
    }
    
    @State<MenuItemStateMode>({
      name: 'menuItems',
      defaults: {
        selectedItem: null,
        menuItems: []
      }
    })
    export class MenuItemState { 
    
      @Action(SelectMenuItem)
      selectItem({patchState}: StateContext<MenuItemStateModel>, action: SelectMenuItem) { 
        patchState({selectedItem: action.itemIndex});
      }
    
      // .. Load the menu items from API (or hardcoded?)
    
    }
    

    Then remove the 'selected' property from your menuItem type. In the template if you then want to show it highlighted you can bind a style according to whether the itemIndex === state.selectedItem.

    This way you don't modify the list when items are selected/unselected.