Bug
adding a new child to an existing node on the data source updates view with new child
view is not updated
the UI state is not in sync with the data source
There is a workaround in that you can set the data source data to null and then set it again which correctly updates the UI. ie
const data = this.nestedDataSource.data;
this.nestedDataSource.data = null;
this.nestedDataSource.data = data;
@Craig939393 it looks like your issue might deal with how change detection works. change detection is only run when you update this.nestedDataSource.data to a new reference (under the hood it uses a getter to check when the data property is changed to an entirely new object or an undefined value – implementation), which is why
const data = this.nestedDataSource.data;
this.nestedDataSource.data = null;
this.nestedDataSource.data = data;
causes change detection to run, updating the UI, but
this.nestedDataSource.data.someProperty = newValue;
would not trigger change detection.
The MatTree class has a renderNodeChanges() method that can be called whenever a nested property's value in the data source gets updated and might fit your use case (see the MatTree docs: https://material.angular.io/components/tree/api#MatTree)
@johnbuffington that makes sense, thanks.
@johnbuffington change detection uses identity of the objects by default. renderNodeChanges() is also called by a subscription on datasource. So it is called for a simple nested tree like the one in documentation.
I wanted to stick with nested-trees and try to render the change. The problem is in template, when functions are not re-evaluated for a data change. May be change detection vocabulary is not sufficient: node add/remove/mode. I was hoping for an "updated" verb for a node change detection to do the trick in the future :). This has to end up in IterableChanges I guess.
So what I ended up with is using a trackBy function which incorporates version of the object by simply concatenating to its id. This way, It just deletes and adds the same object (but version reported different trackBy value) and this triggers rendering again.
Most of the examples use tree flatteners so that way eases new object creation and rendering changes.
@johnbuffington this code only appears to work with a nested mat tree:
const data = this.nestedDataSource.data;
this.nestedDataSource.data = null;
this.nestedDataSource.data = data;
Using this method with a flat mat-tree with MatTreeFlatDataSource throws the error:
core.js:12584 ERROR TypeError: Cannot read property 'forEach' of null
at MatTreeFlattener.push../node_modules/@angular/material/esm5/tree.es5.js.MatTreeFlattener.flattenNodes (tree.es5.js:461)
at MatTreeFlatDataSource.set [as data] (tree.es5.js:545)
renderNodeChanges() also doesn't work... Is it not currently possible to update a flat mat-tree's dataSource?
Hi Daniel,
for me it's sufficient to just re-assign the very same array to the datasource without assigning null inbetween.
I've defined the datasource like this:
maskComponents: UiComponent[] = [];
dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
this.dataSource.data = this.maskComponents;
Then, somewhere in my code, a new entry to maskComponents is added. The UI doesn't change until I call
this.dataSource.data = this.maskComponents;
without assigning null before.
Does that help?
Best,
Holger
@holleymcfly this works. Thank you!
Your're welcome!
I know this is an old thread, and has been closed, but I'm not satisfied with the workaround, i.e. to set the dataSource to null before resetting in the source Observable subscription.
I have a tree in a scrolling pane, and if I've scrolled down and then expanded a node, the re-render takes you back to the top, losing your scrolled position.
Has anyone found any other solutions to this problem, or do I need to change my approach to building the tree?
@danww Does the re-render also collapse the expanded node in addition to losing your scroll position? In my experience, the re-render also collapses any expanded nodes.
I'm still not satisfied with the workaround myself, but I added some extra code to get around the collapsing expanded nodes issue. I can't remember if I experienced losing scroll position before, but it doesn't occur for me now anyway.
I save expanded nodes to a list before doing any re-rendering:
saveExpandedNodes() {
this.expandedNodes = new Array<BulletFlatNode>();
this.treeControl.dataNodes.forEach(node => {
if (node.expandable && this.treeControl.isExpanded(node)) {
this.expandedNodes.push(node);
}
});
}
And then I restore the expanded nodes after doing the re-rendering:
restoreExpandedNodes() {
this.expandedNodes.forEach(node => {
this.treeControl.expand(this.treeControl.dataNodes.find(n => n.id === node.id));
});
}
This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
_This action has been performed automatically by a bot._
Most helpful comment
Hi Daniel,
for me it's sufficient to just re-assign the very same array to the datasource without assigning null inbetween.
I've defined the datasource like this:
Then, somewhere in my code, a new entry to maskComponents is added. The UI doesn't change until I call
this.dataSource.data = this.maskComponents;without assigning null before.
Does that help?
Best,
Holger