theblindprophet
theblindprophet

Reputation: 7927

How to update nested mat-tree dynamically

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

Answers (6)

eta32carinae
eta32carinae

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

Jeffrey Roosendaal
Jeffrey Roosendaal

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

Samih
Samih

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

user123456789
user123456789

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

wz2b
wz2b

Reputation: 1025

Can't you just make the dataSource a Subject then when you want to change it call dataSource.next() ?

Upvotes: 3

Craig
Craig

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

Related Questions