* ngxs: v3.2.0
* @angular/core: v6.1
The NGXS patchState method is used to do immutable object updates to the container state slice without the typical long-handed syntax. This is very neat and convenient because you do not have to use the getState and setState as well as the Object.assign(…)or the spread operator to update the state. The patchState method only offers a shallow patch and as a result is left wanting in more advanced scenarios.Â
What if it is possible to make the patchState method extensible to create a more advanced yet simple mechanism for state updates?… Let me introduce patch operators…
The basic idea of this proposal is that we could describe the modifications to the state using curried functions that are given any inputs that they need to describe the change and are assigned to the part of the state tree that they should be applied to. The curried function is then run using the state slice that they are assigned to and the returned value is used as the new value for that state slice.
The function that produces the curried function that is still awaiting the slice of state to be applied to is called a patch operator. The type of this curried function could be defined as follows:
type op<T> = (state: T) => T;
We can make a number of these patch operator functions in order to help with common scenarios for state manipulation. Some ideas are:
patch<T>(value: T)Â : op<T>set<T>(value: T)Â : op<T>iif<T>(condition: Predicate<T>, then: op<T>, else: op<T>)Â : op<T>And some array operators:
patchItem<T>(selector: number | Predicate<T>, operator: op<T>) : op<T[]>removeItem<T>(selector: number | Predicate<T>) : op<T[]>insertItem<T>(value: T, beforePosition? : number) : op<T[]>We could express some modifications to a state as follows:
ctx.patchState({
greeting: 'Howzit',
token: patch({
lastUpdate: new Date(),
active: true,
origin: iif( origin => !origin.ip, patch({ ip: '192.168.1.1'})
}),
roles: [
removeItem(3),
insertItem({id:55, name: 'Admin'}),
patchItem( x => x.id === 64, {name:'Editor'})
]
});
Please add any other ideas for operators to the comments.
Feedback welcome!
PS. Related Meduim posts here:
Initial proposal: https://medium.com/@mark.whitfeld/ngxs-proposal-patch-operators-75b24d309b91
Announcement before v3.4: https://medium.com/ngxs/ngxs-state-operators-8b339641b220
Great ideas @markwhitfeld - our team would really benefit from those array operators, that's where I think we have the most verbose patching code. This would really clean things up for us.
Currently, you can create a child state (sub state) for nested objects and trigger patchState for that same action in both parent and child states, but having this would be useful too!
@garthmason That's great to hear. I have been busy moving house lately so have not had much chance to work on it.
Any other operators that you think would be useful to you?
You could even paste code that you think could benefit from an operator and we can have a look at what it might look like.
@MurhafSousli The child state would be useful if you had a nested child object (as opposed to an array) but I see the main utility for this where you want to do nested updates of an array.
@markwhitfeld why not split that in two parts:
patchState(val: () => Partial<T> | Partial<T>)In this way, the core functionality doesn't grow much and at same time allows other kind of mutation by function, maybe passing the current state for it: patchState(val: (state: T) => Partial<T> | Partial<T>)
@juracy This is almost exactly the idea. The operators will exist in a separate package and the base patchState method will receive a slight tweak (which also will just include use of a base patch operator).
The signature will be more like this: patchState(val: (state: T) => T | Partial<T> | PatchObject<T>).
The PatchObject<T> is essentially an extension of Partial<T> where each property could be T[K] or (state: T[K]) => T[K].
This would allow for the use of something like immer using this syntax:
ctx.patchState(produce((state) => state.counter++ ));
See this stackblitz for the playground:
https://stackblitz.com/edit/ngxs-patch-operators?embed=1&file=src/patch-operators.ts
@markwhitfeld Humm, sorry, a typical TL;DR mistake (well not too long, indead) !
So, why not submit a PR for patchState right away?
I'm wanting to get to get the Typescript typings definitions working great for this first and this requires TypeScript 2.8 and above. @amcdnl is wanting to wait until Angular supports Typescript 2.8 before we take that dependency. I think that Angular 6.1 may be on TypeScript 2.9 now. I can bring this in with @amcdnl's go ahead.
I just stumbled across an issue that could've been solved with this approach, looking forward! :)
Hi everyone. So I think that this idea is evolving a bit...
I am going to be adding another option in the way that you can call ctx.setState where it could be given a function. The ctx.setState method would then execute this function, passing the previous state and set the state to the value returned by the function. For example:
Before
@Action(MyAction)
public addValue(ctx: StateContext, { payload }: MyAction) {
ctx.setState({ ...ctx.getState(), value: payload });
}
After
@Action(MyAction)
public addValue(ctx: StateContext, { payload }: MyAction) {
ctx.setState((state) => ({ ...state, value: payload }));
}
Doesn't seem like much but this function could then be extracted and reused. Also this is the exact signature of these "patch operators". So you could forseeably do this:
@Action(RemoveContact)
public removeContact(ctx: StateContext<Contact[]>, { payload }: RemoveContact) {
ctx.setState(removeItem( x => x.id === payload.id ));
}
So, what do I need from you? Since these operators could be more general purpose than just for patching we could think of a better name. Some of the options I have brainstormed with @eranshmil are:
As a general description of what they are I would say that they are functions that define transformations to apply to an existing immutable state to produce a new state.
Can I ask for your votes or suggestions for a name?
State Operators sounds awesome, @markwhitfeld !
Can be closed?
Published a teaser for the feature ahead of the 3.4 release:
https://medium.com/ngxs/ngxs-state-operators-8b339641b220
Most helpful comment
Hi everyone. So I think that this idea is evolving a bit...
I am going to be adding another option in the way that you can call
ctx.setStatewhere it could be given a function. Thectx.setStatemethod would then execute this function, passing the previous state and set the state to the value returned by the function. For example:Before
After
Doesn't seem like much but this function could then be extracted and reused. Also this is the exact signature of these "patch operators". So you could forseeably do this:
So, what do I need from you? Since these operators could be more general purpose than just for patching we could think of a better name. Some of the options I have brainstormed with @eranshmil are:
... or maybe we just keep the "Patch Operators" name.
As a general description of what they are I would say that they are functions that define transformations to apply to an existing immutable state to produce a new state.
Can I ask for your votes or suggestions for a name?