Skip to content

mat-tree with check box and dynamic data does no work fine #14731

@elham-sedighi

Description

@elham-sedighi

Hi
I'm using mat-tree material angular with checkboxes and dynamic data which comes from a http call service. I have two problems now:
1- all root nodes of tree are checked at the beginning! and select and deselect nodes and parent nodes does not work correctly
2- toggling parent nodes does not work fine either because there may be more that one level deeper in a parent node (which I,m trying to collapse) but only first level will be collapsed according to the count of its child. what is the solution? I even used a recursive function to handle that but it's not working fine too.
I would be grateful to help me with.
here are my codes:
view:
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl" dir="rtl"> <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle matTreeNodePadding> <button mat-icon-button disabled></button> <mat-checkbox class="checklist-leaf-node" [indeterminate]="descendantsPartiallySelected(node)" [checked]="checklistSelection.isSelected(node)" (change)="todoLeafItemSelectionToggle(node);"> {{node.Title}} </mat-checkbox> <mat-progress-bar *ngIf="node.isLoading" mode="indeterminate" class="example-tree-progress-bar"></mat-progress-bar> </mat-tree-node> <mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding> <button mat-icon-button matTreeNodeToggle [attr.aria-label]="'toggle ' + node.filename"> <mat-icon class="mat-icon-rtl-mirror"> {{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}} </mat-icon> </button> <mat-checkbox [checked]="descendantsAllSelected(node)" [indeterminate]="descendantsPartiallySelected(node)" (change)="todoItemSelectionToggle(node)">{{node.Title}}</mat-checkbox> <mat-progress-bar *ngIf="node.isLoading" mode="indeterminate" class="example-tree-progress-bar"></mat-progress-bar> </mat-tree-node> </mat-tree>
my component:
` import { CollectionViewer, SelectionChange, SelectionModel } from '@angular/cdk/collections';
import { FlatTreeControl } from '@angular/cdk/tree';
import { Component, Injectable } from '@angular/core';
import { Service } from '../service/callajaxandhttp/service.component';
import { BehaviorSubject, Observable, merge } from 'rxjs';
import { map } from 'rxjs/operators';

export class DynamicFlatNode {
constructor(
public Title: string,
public id: string,
public PID: number,
public Type: string,
public s: string,
public haschildren: boolean,
public level: number = 1,
public expandable: boolean = false,
public isLoading: boolean = false,
public childcount: number = 0,
public children: DynamicFlatNode[] = []
) { }
}

@Injectable()
export class DynamicDatabase {
dfns: DynamicFlatNode[];
Tempchilddfns: DynamicFlatNode[];

public getData(url: string, ptitle: string, parentnode: string): Promise {
ptitle = ptitle != '' ? '?ptitle=' + ptitle : '';
parentnode = parentnode != '' ? '?parent=' + parentnode : '';

return this.service.callservice('get', url + ptitle + parentnode, "").then(
  (response: any) =>
    response)
  .catch(error => console.log(error));

}

constructor(private service: Service) {
}
}

@Injectable()
export class DynamicDataSource {

dataChange: BehaviorSubject<DynamicFlatNode[]> = new BehaviorSubject<DynamicFlatNode[]>([]);
get data(): DynamicFlatNode[] { return this.dataChange.value; }
set data(value: DynamicFlatNode[]) {
this.treeControl.dataNodes = value;
this.dataChange.next(value);
}
constructor(private treeControl: FlatTreeControl,
private database: DynamicDatabase) { }
connect(collectionViewer: CollectionViewer): Observable<DynamicFlatNode[]> {
this.treeControl.expansionModel.onChange!.subscribe(change => {
if ((change as SelectionChange).added ||
(change as SelectionChange).removed) {
this.handleTreeControl(change as SelectionChange);
}
});

return merge(collectionViewer.viewChange, this.dataChange).pipe(map(() => this.data));

}

handleTreeControl(change: SelectionChange) {
if (change.added) {

  change.added.forEach((node) => this.toggleNode(node, true));
}
if (change.removed) {
  change.removed.reverse().forEach((node) => this.toggleNode(node, false));
}

}

toggleNode(node: DynamicFlatNode, expand: boolean) {
const index = this.data.indexOf(node);
if (expand) {
if (node.level == 3) return;
node.isLoading = true;
if (node.children.length == 0) {
const children = this.database.getData('Subject/scripts/Subjectw.ashx?parent=' + node.id, '', "").then(
(response: any) => {
const nodes = response.map(
ContentNode =>
new DynamicFlatNode(
ContentNode.Title,
ContentNode.id,
ContentNode.PID,
ContentNode.Type,
ContentNode.s,
ContentNode.children,
node.level + 1,
ContentNode.children))
if (!children || index < 0) { // If no children, or cannot find the node, no op
return;
}

        node.childcount = nodes.length;
        node.children = nodes;
        this.data.splice(index + 1, 0, ...nodes);
        // notify the change
        this.dataChange.next(this.data);
        node.isLoading = false;
      })
      .catch(error => console.log(error));
  }
  else {
    this.data.splice(index + 1, 0, ...node.children);
    this.dataChange.next(this.data);
    node.isLoading = false;
  }
} else {
  this.data.splice(index + 1, node.childcount,);//children.length
  this.dataChange.next(this.data);
}

}
}

@component({
selector: 'Contenttree',
templateUrl: './contenttree.component.html',
styleUrls: ['./contenttree.component.css'],
providers: [DynamicDatabase]
})
export class ContenttreeComponent {

RootContents: DynamicFlatNode[] = [];
constructor(database: DynamicDatabase, private service: Service) {
this.treeControl = new FlatTreeControl(this.getLevel, this.isExpandable);
this.dataSource = new DynamicDataSource(this.treeControl, database);

database.getData('Subject/scripts/Subjectw.ashx', '', '').then(
  (response: any) =>
    this.dataSource.data = response.map(
      ContentNode =>
        new DynamicFlatNode(
          ContentNode.Title,
          ContentNode.id,
          ContentNode.PID,
          ContentNode.Type,
          ContentNode.s,
          ContentNode.children,
          0,
          ContentNode.children))
)
  .catch(error => console.log(error));

}

dataSource: DynamicDataSource;
treeControl: FlatTreeControl;
getLevel = (node: DynamicFlatNode) => { return node.level; };
isExpandable = (node: DynamicFlatNode) => { return node.expandable; };
hasChild = (_: number, _nodeData: DynamicFlatNode) => { return nodeData.expandable; };
hasNoContent = (
: number, _nodeData: DynamicFlatNode) => { return _nodeData.Title === ''; };
checklistSelection = new SelectionModel(true /* multiple */);

/** Whether all the descendants of the node are selected. */
descendantsAllSelected(node: DynamicFlatNode): boolean {
const descendants = this.treeControl.getDescendants(node);
const descAllSelected = descendants.every(child =>
this.checklistSelection.isSelected(child)
);
return descAllSelected;
}

/** Whether part of the descendants are selected */
descendantsPartiallySelected(node: DynamicFlatNode): boolean {
const descendants = this.treeControl.getDescendants(node);
const result = descendants.some(child => this.checklistSelection.isSelected(child));
return result && !this.descendantsAllSelected(node);
}

/** Toggle the to-do item selection. Select/deselect all the descendants node */
todoItemSelectionToggle(node: DynamicFlatNode): void {
this.checklistSelection.toggle(node);
const descendants = this.treeControl.getDescendants(node);
this.checklistSelection.isSelected(node)
? this.checklistSelection.select(...descendants)
: this.checklistSelection.deselect(...descendants);

// Force update for the parent
descendants.every(child =>
  this.checklistSelection.isSelected(child)
);
this.checkAllParentsSelection(node);

}

/** Toggle a leaf to-do item selection. Check all the parents to see if they changed */
todoLeafItemSelectionToggle(node: DynamicFlatNode): void {
this.checklistSelection.toggle(node);
this.checkAllParentsSelection(node);
}

/* Checks all the parents when a leaf node is selected/unselected */
checkAllParentsSelection(node: DynamicFlatNode): void {
let parent: DynamicFlatNode | null = this.getParentNode(node);
while (parent) {
this.checkRootNodeSelection(parent);
parent = this.getParentNode(parent);
}
}

/** Check root node checked state and change it accordingly */
checkRootNodeSelection(node: DynamicFlatNode): void {
const nodeSelected = this.checklistSelection.isSelected(node);
const descendants = this.treeControl.getDescendants(node);
const descAllSelected = descendants.every(child =>
this.checklistSelection.isSelected(child)
);
if (nodeSelected && !descAllSelected) {
this.checklistSelection.deselect(node);
} else if (!nodeSelected && descAllSelected) {
this.checklistSelection.select(node);
}
}

/* Get the parent node of a node */
getParentNode(node: DynamicFlatNode): DynamicFlatNode | null {
const currentLevel = this.getLevel(node);

if (currentLevel < 1) {
  return null;
}

const startIndex = this.treeControl.dataNodes.indexOf(node) - 1;

for (let i = startIndex; i >= 0; i--) {
  const currentNode = this.treeControl.dataNodes[i];

  if (this.getLevel(currentNode) < currentLevel) {
    return currentNode;
  }
}
return null;

}
}
`

thanks alot

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions