Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to apply updateItem to multiple/all items in Ngxs?

Tags:

angular

ngxs

I need an alternative to change the same attribute, with the same value, for all items in an array in Ngxs.

I tried to use updateItem(() => true, patch({ ... })), but it doesn't work because the first function, () => true, just returns the first match and ends up updating only a single item.

The following is a simplified example of what I tried to do:

import { State, Action, StateContext } from '@ngxs/store';
import { patch, append, removeItem, insertItem, updateItem } from '@ngxs/store/operators';

export interface AnimalsStateModel {
  others: { /*...*/ }[];
  rabbits: Rabbit[];
}

export interface Rabbit {
  name: string,
  alive: boolean,
}

export class ChangeRabbitAlive {
  static readonly type = '[Animals] Change rabbit alive';
  constructor(public payload: { name: string; alive: boolean }) {}
}

export class ChangeAllRabbitsDead {
  static readonly type = '[Animals] Change all rabbits dead';
  constructor(public payload: { }) {}
}

@State<AnimalsStateModel>({
  name: 'animals',
  defaults: {
    others: [ /*...*/ ],
    rabbits: [
      { name: 'Michael', alive: true },
      { name: 'John', alive: true },
      { name: 'Jimmy', alive: true },
      { name: 'Jake', alive: true },
      { name: 'Alan', alive: true },
    ]
  }
})
export class AnimalsState {
  
  @Action(ChangeRabbitAlive)
  changeRabbitAlive(ctx: StateContext<AnimalsStateModel>, { payload }: ChangeRabbitAlive) {
    ctx.setState(
      patch({
        rabbits: updateItem<Rabbit>(rabbit => rabbit.name === payload.name, patch({ alive: payload.alive }))
      })
    );
  }
  
  @Action(ChangeAllRabbitsDead)
  changeAllRabbitsDead(ctx: StateContext<AnimalsStateModel>, { payload }: ChangeAllRabbitsDead) {
    const isAlive = { alive: false };
    ctx.setState(
      patch({
        //rabbits: updateItem<Rabbit>(name => true, patch({ alive: false }))// Type 'boolean' is not assignable to type 'false'
        rabbits: updateItem<Rabbit>(() => true, patch(isAlive))
      })
    );
  }
}

Is there any way to kill all the rabbits at once?

Extra: If you could explain why the error "Type 'boolean' is not assignable to type 'false'" occurs in that situation, that would also be appreciated.

like image 962
Edu Müller Avatar asked Sep 14 '25 06:09

Edu Müller


1 Answers

I found this thread [DOCS]: Share your custom State Operators here! that mentions a custom operator updateManyItems, apparently what I was looking for. However, it is not available in the general library, nor could I find its implementation. So I tried some alternatives mixing basic operators and came up with a simple solution using compose:

To update them all, just add an updateItem for each index in the list:

const isAlive = { alive: false };
const updateItems = ctx.getState().rabbits.map((r, i) => updateItem<Rabbit>(i, patch(isAlive)));
ctx.setState(
    patch({
        rabbits: compose(...updateItems),
    })
);

To update a portion of the items, just add an updateItem filtering by some unique identifier of the item:

const isAlive = { alive: false };
const updateItems = payload.rabbitNames.map((rn) => updateItem<Rabbit>(rabbit => rabbit.name === rn, patch(isAlive)));
ctx.setState(
    patch({
        rabbits: compose(...updateItems),
    })
);

If the item identifier may be duplicated in the list, it is possible to update by index as well:

const isAlive = { alive: false };
const indexList = ctx.getState().rabbits.reduce((result, r, i) => 
    (payload.rabbitNames.includes(r.name) ? result.push(i) : null, result) , []);
const updateItems = indexList.map((i) => updateItem<Rabbit>(i, patch(isAlive)));
ctx.setState(
    patch({
        rabbits: compose(...updateItems),
    })
);

A native operator could be better optimized, but these alternatives still work fine.

like image 180
Edu Müller Avatar answered Sep 16 '25 21:09

Edu Müller