2
votes

I am using *ngFor to display values from an array:

[
  {
    id: 1,
    name: 'item1'
  },   
  {
    id: 2,
    name: 'item2'
  }
]

html:

<div *ngFor="let item of (items$ | async); trackBy: trackById;">
   // more html to display data
</div

ts:

items$: Observable<any>;
trackById = trackByProperty('id');

ngOnInit() {
  this.items$ = this.store.pipe(select(selectors.itemsSelector));
}

trackByProperty<T = any>(property: keyof T) {
  return (index: number, object: T) => object[property];
}

This works as intended, ngFor gets the correct and current values in the items$ array

My issue is that when I update the items$ array using ngrx it doesn't seem to catch the new array and doesn't update the DOM

Heres the flow of data using ngrx

  1. Dispatch action to the reducer, sending a new object to add to the array.

      this.store.dispatch(new actions.UpdateArray(
          { id: 3, name: 'item3' }
        )
      );
    
  2. Reducer picks up this action and updates the store state with the updated array (receives the new item and pushes it to the array).

    case actions.UPDATE_ARRAY: {
      const newItem = action.payload;
      const items = state.items;
      items.push(newItem);
      return {
        ...state,
        items
      };
    }
    
  3. Selector updates.

I can confirm the state gets updated correctly when logging out the action.payload in the reducer.

Does anyone know why I'm not getting an updated array in the *ngFor?

Forgot to mention, but I'm using changeDetection: ChangeDetectionStrategy.OnPush as my change detection in my component

Update

I found out that the DOM actually updates when you click on the component. I'd like it to update without having to do that.

1
Pkt. 2, you update array with push method? If yes you should change it to spread operator which create new instance of array: return [... oldArray, newElement]pioro90
Could you post the code relevant to your store ? Actions, states, reducers, effects ...user4676340
@cup_of I "kind of" mocked the behavior here and it seems to work. Would you be able to reproduce your issue with that ?user4676340
@cup_of seems strange indeed. It would mean your view isn't updated, which is in contradiction with the reactive programming that triggers a change detection. That's a really strange issue ! Try reproducing on stackblitz, and either post your minimal reproducible example here or on the Angular/NgRx github repos, see what they will answer you !user4676340
@trichetriche I actually think the same as you, but I think using memoized selectors could cause weird behavior. It's a simple change (and also a good practice) so I think the OP should try it.Tzach Ovadia

1 Answers

1
votes

This might happen because NgRx selectors use Memoization which sometimes causes undesirable behavior.

Changing

case actions.UPDATE_ARRAY: {
  const newItem = action.payload;
  const items = state.items;
  items.push(newItem);
  return {
    ...state,
    items
  };
}

to

case actions.UPDATE_ARRAY: {
  const newItem = action.payload;
  const items = [...state.items, newItem]; // <-- creates new instance
  return {
    ...state,
    items
  };
}

should solve the problem.