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.
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.