Reputation: 7927
I have an Angular material component that I am trying to update dynamically with new data.
@ViewChild('tree') tree: MatTree<any>;
...
treeControl: NestedTreeControl<any>;
dataSource: MatTreeNestedDataSource<any>;
...
// This does not work
this.tree.renderNodeChanges(newData);
I can change the data source data just fine and it is reflected in the logs, but the tree is never re-rendered this.dataSource.data = newData
;
This data is retrieved from an observable and I pass it in every time there is a change.
<mat-tree [dataSource]="dataSource"
[treeControl]="treeControl"
#tree
class="release-notes-tree">
...
</mat-tree>
What is the easiest way to just pass in brand new data and update the tree?
Upvotes: 16
Views: 30891
Reputation: 611
As mentioned here, the solution is using the Immutable Update to change the data source of the tree.
You can see a complete example here (but the important part is just the updateObjectInArray
method):
import {NestedTreeControl} from '@angular/cdk/tree';
import {Component, OnInit} from '@angular/core';
import {MatTreeNestedDataSource} from '@angular/material/tree';
export interface MenuItem {
name: string;
children?: MenuItem[];
}
/**
* Update Type
*/
export interface UpType {
index: number;
item: MenuItem;
}
const TREE_DATA: MenuItem[] = [
{ name: 'Fruit', children: [{name: 'Apple'}, {name: 'Banana'}, {name: 'Fruit loops'}], },
{ name: 'Vegetables', children: [{name: 'Pumpkins'}, {name: 'Carrots'}], },
];
@Component({
selector: 'mytree',
templateUrl: './tree.component.html',
styleUrls: ['./tree.component.scss']
})
export class TreeComponent {
treeControl = new NestedTreeControl<MenuItem>(node => node.children);
dataSource = new MatTreeNestedDataSource<MenuItem>();
constructor(private duplinkService: DuplinkService) {
this.dataSource.data = TREE_DATA;
}
hasChild = (_: number, node: MenuItem) => !!node.children && node.children.length > 0;
// other things ...
updateData(): void {
// the object with index 0 in tree data will be replaced by this one (updateAction.item)
const updateAction = {
index: 0,
item: { name: 'Fruit', children: [{name:'f1'}, {name:'f2'}, {name:'f3'}] }
};
this.dataSource.data = this.updateObjectInArray(this.dataSource.data, updateAction);
}
/**
* Immutable update
* @param array array to be updated
* @param action UpType
* @returns MenuItem[] updated array
*/
private updateObjectInArray(array: MenuItem[], action: UpType): MenuItem[] {
return array.map((item, index) => {
if(index !== action.index) {
// This isn't the item we care about - keep it as-is
return item;
}
// Otherwise, this is the one we want - return an updated value
return {
...item,
...action.item
}
});
}
}
Upvotes: 2
Reputation: 7147
What eventually worked for me was overwriting the previous dataSource with a new one:
this.nestedDataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
this.nestedDataSource.data = data;
Upvotes: 0
Reputation: 1088
This works for me and is less funny looking than the provided 3 liner:
this.nestedDataSource.data = this.nestedDataSource.data.slice();
Upvotes: 1
Reputation: 83
data = this.nestedDataSource.data;
this.nestedDataSource.data = null;
this.nestedDataSource.data = data;
Above will not trigger an onNgChange, because there really is no change. The change is evaluated after the method is completed. You can fake a change by duplicating the data in another structure with the same content. For example,
this.nestedDataSource.data = JSON.parse(JSON.stringify(this.nestedDataSource.data));
Upvotes: 3
Reputation: 1025
Can't you just make the dataSource a Subject then when you want to change it call dataSource.next() ?
Upvotes: 3
Reputation: 1776
Ive done some research into this which led to 2 new issues on the angular material github. It really isnt obvious whats going on here and Id argue its a bug.
triggers ngchange and updates the DOM
data = this.nestedDataSource.data
this.nestedDataSource.data = null;
this.nestedDataSource.data = data;
does not trigger ngchange
this.dataSource.data = newData
I havent tried to use this but supposedly this should work. Not sure why its not for you.
<mat-tree #treeSelector></mat-tree>
@ViewChild('treeSelector') tree: matTree;
this.tree.renderNodeChanges()
Upvotes: 15