components icon indicating copy to clipboard operation
components copied to clipboard

Mat-tree :Tree with nested nodes adding child doesnt update the view.

Open juggernut opened this issue 6 years ago • 31 comments

Bug:

When using mat-nested-tree-node with a nested datasource if you add a child to an existing parent it does not update the view.

What is the expected behavior?

Well if you change or add a child to an existing parent in the nestedDataSource it should update the tree.

What is the current behavior?

It does not update the view when adding a child to an existing parent to the nestedDataStructure .

What is the use-case or motivation for changing an existing behavior?

Well in my case i need to update the view since i add/delete nodes .

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

versions 6 (Chrome ver 66)

Is there anything else we should know?

I found an workaround: refreshTree(){ let _data = this.nestedDataSource.data; this.nestedDataSource.data = null; this.nestedDataSource.data = _data; }

juggernut avatar May 17 '18 09:05 juggernut

Can you please provide a reproduction in stackblitz

josephperrott avatar May 30 '18 21:05 josephperrott

I have met the same issue. Could anyone please help confirm whether its a bug?

this.dataChange.next(data);

When update with new object ( e.g data = [] or a new instance), it works. When push/update 'data' object, tree view will not be re render, though this.nestedDataSource.data already updated.

wojiusihshen1 avatar May 31 '18 02:05 wojiusihshen1

I think is a bug since it works in one case but not in the other one ! Feel free to use my workaround , meanwhile 👍 .

refreshTree(){ let _data = this.nestedDataSource.data; this.nestedDataSource.data = null; this.nestedDataSource.data = _data; }

juggernut avatar May 31 '18 13:05 juggernut

I think the cdk-tree (the basis of mat-tree) has the same issue, see here:

https://stackblitz.com/edit/cdk-nested-tree?file=src%2Fapp%2Fmytree%2Fmytree.component.ts

clicking the 'add' button should push a new node onto the children array. console shows it does, but the tree is not updated.

tech-no-logical avatar Jun 06 '18 13:06 tech-no-logical

it also does not update the tree node, if we update the node. https://stackblitz.com/angular/onaqbrpjaan saving a new label to the node does not update the tree.

dodgeviper avatar Jun 26 '18 07:06 dodgeviper

Have the same issue. Any updates?

quanterion avatar Sep 05 '18 09:09 quanterion

I've had the same issue. Appears that binding and UI reflecting works only for the first layer of the tree nodes, but not for any nodes down further. Glad to see there is a workaround, thanks to @juggernut

jamadou avatar Sep 22 '18 07:09 jamadou

Any updates for this? I must use refresh method from @juggernut to refresh the datasource

nvuhung avatar Dec 04 '18 23:12 nvuhung

@juggernut's fix seems only work for the data source but treeControl still fails, the toggle doesn't work after updating the data.

zhaodong-wang avatar Feb 17 '19 14:02 zhaodong-wang

Closed because it was fixed? If we have to use the workaround it should be documented before the issue is closed, right?

SirZach avatar Feb 20 '19 12:02 SirZach

Sorry i am just stupid ... closed by mistake :)

juggernut avatar Feb 20 '19 14:02 juggernut

Any update on this? or estimation when should be fixed?

sadplace avatar Mar 20 '19 15:03 sadplace

Is a P4 problem (considered low priority ) i dont have the time to see if i can do something in the source code . If i have the time i will try to make a pull request my self . But dont hold your breath ( i am really busy ) . Hopefully someone better than me gets to this problem before me !

juggernut avatar Mar 22 '19 16:03 juggernut

@juggernut's fix seems only work for the data source but treeControl still fails, the toggle doesn't work after updating the data.

Yeah I reloaded the datasource but after I am unable to get the indexOf a node using below treeControl.dataNodes.indexOf( node ) Above returns -1

bijuML avatar Jul 11 '19 13:07 bijuML

This problem should be considered higher priority. The workaround introduces quite a serious performance issues for big trees. But it is currently the only way how to implement the asynchronous loading of the nodes

palpatine1991 avatar Aug 29 '19 11:08 palpatine1991

Any updates on this? I am about to implement a dynamically loaded folder menu and I wanted to use the nested data source/ nested tree control, but judging by this thread it seems I'm better off using the flat data source?

vpatov avatar Sep 03 '19 19:09 vpatov

@vpatov sadly there is currently no progress at it seems. Did you try using trackBy from mat-tree ? I didn't test it but if its working for child items, it will improve the performance when resetting the whole tree source...

But thats not the solution i know, and it depends on your usecase.

Update:

~~I tested the trackBy and it works for all items in the tree. This mean you can use this as workaround combined with resetting the source (as meantioned already).~~

~~In case the tracked => same items will not be rerendered,~~ ~~1) the performance is better~~ ~~2) expand / collapsed nodes will keep their state.~~

Update 2:

Sorry i was to fast with my comment, and i tested one of my old custom implemenations of mat-tree, which handles the trackBy stuff a bit different. Its a long time ago i implemented that tree but i thought i used the default behavior from mat-tree, which was not the case....

While creating a quick example in stackblitz (with the default mat-tree) i realized no matter if you use trackBy nodes will be rerenderd.

  • the workaround with setting the source to null is still working

but trackBy combined with the "source to null workaround" is NOT improving any rendering performance. As in the mat-tree docu trackBy only helps to improve performance for treeOperations....

Anyway: here is the example (thats exactly the workaround the TO wrote, nothing new and not clean..):

https://stackblitz.com/edit/angular-kc77e4

sorry for spreading hope

Priemar avatar Sep 03 '19 22:09 Priemar

@Priemar add some code example , it helps people :) .

juggernut avatar Sep 04 '19 09:09 juggernut

@Priemar Thank you for your efforts, I am just using the workaround for now because it works fine for me, and doesn't seem to have a strong performance impact.

vpatov avatar Oct 07 '19 20:10 vpatov

Any update on this? We are needing this behavior as well.

Rlcolli4 avatar Nov 15 '19 19:11 Rlcolli4

For me, it DOES updates the view (I used the example "Tree with checkboxes" from here), but when adding a node to a LEAF node, it doesn't refreshing the tree well, as it doesn't present the EXPAND button for the leaf node (which is no leaf at all after adding a node under it).

The WORKAROUND for me was to save aside root's node when initializing the tree, and then in every node addition, call this method:

private refreshTree(): void {
    this.treeControl.collapse(this.rootTree);
    this.treeControl.expand(this.rootTree);
}

This causing the root tree to get collapsed and get expanded again, which builds the view properly after the changes.

MightGod avatar Dec 08 '19 11:12 MightGod

The cdk tree only renders updates of the backing datastore when the IterableDiffer sees a difference, https://github.com/angular/components/blob/master/src/cdk/tree/tree.ts#L215.

Therefore tree updates are not triggered when objects references stays the same. In order to solve that properly, all objects from the root to the relevant updated node must be updated, aka an immutable update https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns/

Would be nice if the documentation mentioned that, and in addition, had an example of an update.

Here's an example of removing / adding nodes to a nested data source. It replaces relevant nodes on the path from the root to the changed node. And it keeps the expansion state.

https://angular-material-nested-tree-with-updates.stackblitz.io/

Br. Jakob

jakobadam avatar Jan 16 '20 15:01 jakobadam

Hi Jakob,

Thanks for the info. Could you please share the stack blitz that contains the code for you example?

Kind Regards

pmalde avatar Jan 20 '20 16:01 pmalde

Could you please share the stack blitz that contains the code for you example?

Here it is: https://stackblitz.com/edit/angular-material-nested-tree-with-updates

adrianriepl avatar Jan 20 '20 19:01 adrianriepl

@adrianriepl @jakobadam

Having Ngrx & immutable js in reducer - job is perfectly done, and additional state cloning here is kind of defeating the purpose of immutable js, I think? Also I have 'normalized' model to help minimize cloning of a graph. The list of children is just an ids, and there is a Map of nodes in a state to navigate by id. I've been so excided by resultant performance of this approach, till I noticed that nested tree is barely works.

Is there a way to actually change IterableDiffer, or add some external hints, or manually trigger renderNodeChanges? The thing is - I'm aware about every change because reducer just did the job. I think I can even add the hint into state itself or add a side effect (ngrx effect) with hints, but I don't want to waste resources and let IterableDiffer or sometihng to actually find what was changed in the tree, this sounds like extra dummy work if reducer have this knowledge. May be any ideas?

UPD: I just found some inspiration here!

gusarov avatar Oct 16 '20 21:10 gusarov

2021 still open, since so i bump, cuz iam having the described problem. will check the "workarounds"

janpauldahlke avatar Mar 18 '21 14:03 janpauldahlke

I've got the same issue too. Using MatTreeFlatDataSource I'm able to add new nodes to existing parents but when adding to a leaf node the changes are not being detected. I haven't had much luck with the workarounds either. Any other ideas?

mlota avatar Mar 24 '21 10:03 mlota

Same problem, adding/removing a root node is fine most of the time, but changing a child node does not trigger an update. The workaround does work but for large tree is can take several hundreds of milliseconds.

I found another workaround which does not involve changing the datasource reference: after changing a child node, change the corresponding root node in-place with a shallow copy of itself.

// one of the child nodes of nestedDataSource.data[5] was changed
const tmp = this.nestedDataSource.data;
tmp[5] = Object.assign({}, tmp[5]);
this.nestedDataSource.data = tmp;

The tmp variable is important because we need to trigger the nestedDataSource.data setter to update the tree.

SadiinsoSnowfall avatar May 14 '21 14:05 SadiinsoSnowfall

Spent lots of time try to figure out to solve this issue. Above workaround works in some way, but when try to drag tree node over another node to expand that node with add children to that node, above workaround will not work.

At the end found this https://stackoverflow.com/questions/53919593/angular-material-tree-does-not-show-child-elements-with-array-is-populated and demo: https://stackblitz.com/edit/angular-addchildtonestedtree will solve my add/remove node children. Still need special take care the root node, possible if initial node children is null when add node children.

lujian98 avatar Jun 16 '21 23:06 lujian98

Trying to revive this in 2022, any solutions apart from the workaround?

RodrigoAN97 avatar Feb 21 '22 13:02 RodrigoAN97

It probably makes sense for the community to have a custom pipe setup for tree-cdk and mat-tree. Something I am going to think on. Not being able to implement using your class ngrx/store | async scenario is a but cumbersome. If I do this I will let people now and make it public

CharlieGreenman avatar Jul 19 '22 19:07 CharlieGreenman