Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Components re-render when state changes even if the actual values are the same

Tags:

angular

rxjs

ngrx

My ngrx state contains an array of items:

export interface MyState {
    myItems: ItemType[];
}

I have a component that contains ngFor with an async pipe.

markup:

<app-my-item-details-card *ngFor="let myItem of myItems$ | async"> </app-my-item-details-card>

selector:

this.myItems$ = createSelector(getMyState, state => state.myItems);

Inside of the AppMyItemDetailsCardComponent I have a boolean flag displayDetails:boolean and button button (click)="displayDetails = !displayDetails" that toggles boolean flag. On the details panel I have some inputs that displays some data from MyItem instance that is passed to the component. User can modify those html elements and click Save button, that fires of an action MyItemUpdateAction. In the store reducer I do something like this:

const myItemToModifyIndex = state.myItems.findIndex(...predicated based on the passed a ction...)
// cloning the item that I need to modify.
const myItemClone = [...state.myItems[myItemToModifyIndex]];
// modifying properties on the cloned item using data from the action
myItemClone.interestingProperty = action.newValueFromUser;
// cloning the array of items from the state.
const myItemsArrayClone = [...state.myItems];
// setting new item in the cloned array.
myItemsArrayClone[myItemToModifyIndex] = myItemClone;
// return of new state.
return { myItems: myItemsArrayClone }

Problem:

Because as you can see from the reducer that I have modified the state and essentially created a new instances of the myItems array and a copy of the item that I have modified, Angular async pipe reacts to the change in the store and re-renders my list of components. Which in itself re-renders all instances of AppMyItemDetailsCardComponent and it causes the flag that displays details of the component to false and UI changes and details panel is hidden.

Questions:

1) Does this pattern make sense and fits in with how things are done with NGRX? (I am new to redux in angular).

2) I can resolve this by keeping the flag that displays the details panel inside of the store. Basically instead of toggling button (click)="displayDetails = !displayDetails" a boolean flag, I would dispatch an action that would change a flag on an instance of MyItem itself that would drive the display of the details panel. Can this be considered a good solution?

like image 513
milagvoniduak Avatar asked Aug 30 '18 20:08

milagvoniduak


1 Answers

redux, ngrx and state handling is all about the references.

if you create a new array reference, angular thinks it's a new list and rerenders everything.

But if you change the reference of the specific item in the array instead, angular will only push the new [item]="myItem" and cause the single my app item card to update.

It's a matter of very carefully creating the new state tree, only updating what is necessary.

I would also put the displayDetails in the item, that way it will keep in sync when you clone the state changes and not get overwritten.

like image 52
Leon Radley Avatar answered Sep 30 '22 10:09

Leon Radley