Search code examples
stateangular7ngrx

Ngrx: How to prevent data loss after state update in array of elements?


My Angular application shows a list of Item where every Item (Component) has its own state composed by the following properties:

{
    name: 'item name',
    status: 'OK'
}

I used a ngrx store to mantain an updated state for every component (the OK state is coming from a backend-service via Effect, so I retrieve data asynchronously to be put into the store).

Angular polls a backend-service and if the status field for the n-th item is not OK, the UI has to be colored differently.

The back-end service returns an array of items of the type shown above and it is placed into the list below.

So, here's the State representation:

export interface State {
    list: Item[]
}

Now I would like to store one more information in every Item of the list upon an event: the open property which is not depending by the backend. It is set via UI upon a button click.

So I would have a state like this at a certain point:

[{
    name: 'item 1',
    status: 'OK',
    open: true
},
{
    name: 'item 2',
    status: 'OK',
    open: false
},
{
    name: 'item 3',
    status: 'OK'
}
...
{
    name: 'item n',
    status: null
}]

The open property is set by a UI event. Other properties come from the backend (via array).

My problem is that every time I dispatch an action that pulls data from the back-end service, all the elements are overwritten (I clearly understand why) and I have this:

[{
    name: 'item 1',
    status: 'OK'
},
{
    name: 'item 2',
    status: 'OK'
},
{
    name: 'item 3',
    status: 'OK'
}
...
{
    name: 'item n',
    status: null
}]

What's the best approach to solve this? I think it could be easily solved into the reducer, but I still can't figure out how.

I tried to:

  • use a different store
  • use another array in the same store

but I think I will have synch issues in both cases

  • I tried to change the reducer from this:
case ItemsActions.STORE_ITEM_LIST:
    return {
    ...state,
    list: action.payload
}

to this (update each item specifying each field):

case ItemsActions.STORE_ITEM_LIST:
return {
    ...state,
    list: action.payload.map((item, index) => {
        action.payload[index].name === item.name ? {
            ...item,
            name: item.name,
            status: item.status
        } : item
    })
}

Solution

  • In theory you have to map over the payload and "join" the current state value with the incoming value.

    This translates into the following implementation:

    case ItemsActions.STORE_ITEM_LIST:
    return {
        ...state,
        list: action.payload.map((item) => ({
                ...state.list.find(item2 => item2.name === item.name) || {}, // if the item isn't found just create an empty object
                name: item.name,
                status: item.status
    
        }))
    }
    

    To make these updates easier you can take a look at: