I have a tree structure data like this:
[{
id: 54,
name:123,
children: [{
id: 54,
name:123,
children: [{
id: 154,
name:1234,
children []...
}]
}]
}, {
...
}]
I am using Angular 2. As far as I know, change detection kicks in whenever input changes and your change detection strategy is onPush
.
To optimise the tree structure updates (e.g. toggling a node at a nested level or changing any attributes of such a node), I used Immutable.
How can Immutable help me to optimise my updates? I read that Immutable reuses references from old data to construct new objects whenever data changes.
How do I use Immutable data structures efficiently to update nodes at a nested level?
Assumptions
- I don't have a
keyPath
for any of the nodes. - Each node has a unique
id
property value which can be used to query tree data (but how?)
Problems
- How can I update a node somewhere at a nested level? What could be the most efficient way of reaching out to that node?
- How do I update multiple nodes? I heard of the
withMutations
API, but is there any other efficient way?
My approach
Deep-copy everything and then modify the newly constructed object:
var newState = deepClone(oldState) // deep copy everything and construct a new object newState.nodes.forEach(node => { if(node.id == 54) { node.id = 789; } })
What I am trying to implement:
var newState = Immutable.fromJS(oldState) // create an immutable object newState = newState.find(node => { node.set("id", 123); }); // any changes to object will return new object
With the second solution I hope to achieve re-use of nodes, as pictured below:
Realise that when you use Immutable for your tree structure, you cannot expect to replace a node without also changing the internal references to that node, which means that such a change will need to bubble up to the root of the tree, also changing the root.
More detailed: as soon as you use a method to change a certain property value, you will get a new object returned for that particular node. Then to re-inject that new node in your tree, i.e. in the parent's children list, you will use a method that will create a new children list (since the children property is immutable as well). Now the problem is to attach that children list to the parent, which will result in a new parent node, ...etc. You'll end up recreating all the ancestor nodes of the node you want to change, giving you a new tree instance, which will have some reuse of nodes that were not in the root-to-node path.
To re-use your image, you'll get something like this:
The Immutable API can do this for you with the
updateIn
method (orsetIn
if your update concerns only one property of the targeted node). You will need to pass it the key path to identify the (nested) node you want to modify.So, if for instance you know the id of the node to be changed, you could use a little helper function to find the key path to that particular node.
You need to pass it the tree, the name of the property that has the children (so
children
in your case), and the function that will identify the node you are looking for. Let's say you want the path to the node with id 4, then you would call it like this:That key path could look something like this -- an alteration of an index in the array, and the
children
property providing the deeper array:Then to modify the node at that path, you would do something like this:
Here is a demo with some sample data: