Components: Mat-Tree: Adding a child to an existing node does not automatically update the UI

Created on 29 Jun 2018  Â·  10Comments  Â·  Source: angular/components

Bug, feature request, or proposal:

Bug

What is the expected behavior?

adding a new child to an existing node on the data source updates view with new child

What is the current behavior?

view is not updated

What are the steps to reproduce?

  1. get a node that is on the tree (I made a flatten tree function to easily reference any node in the tree)
  2. push a new child into that nodes children

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

the UI state is not in sync with the data source

Is there anything else we should know?

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;

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:

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

All 10 comments

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kara picture kara  Â·  94Comments

vibingopal picture vibingopal  Â·  80Comments

anderflash picture anderflash  Â·  59Comments

AlanCrevon picture AlanCrevon  Â·  107Comments

lfroment0 picture lfroment0  Â·  108Comments