Bug in example code
Selection model in example should be consistent with selected items.
When you select a parent tree item it shows up in the selection model, however if you select all of the items under a tree individually, the parent item will show as checked, but will not appear in the selection model.
see checkboxes example from documentation: https://material.angular.io/components/tree/examples or stackblitz clone: https://stackblitz.com/angular/nkmjydodvnp?file=app%2Ftree-checklist-example.html
to reproduce, click checkbox for a parent tree and view selectionmodel.selected
uncheck parent tree then check all child items, selection model will be different and not match display.
To provide good examples with predictable behavior.
angular material 6.02
easily fixed by a small change to decendantsAllSelected method. see working fix:
https://stackblitz.com/angular/nkmjydodvnp?file=app%2Ftree-checklist-example.ts
code:
descendantsAllSelected(node: TodoItemFlatNode): boolean {
const descendants = this.treeControl.getDescendants(node);
let allselected = descendants.every(child => this.checklistSelection.isSelected(child));
if (allselected)
this.checklistSelection.select(node);
else
this.checklistSelection.deselect(node);
return allselected;
}
There is still a bug with this.
Steps from stackblitz link:
RemindersCook DinnerCook Dinner unchecked and Reminders is partial (correct)RemindersReminders is checked (incorrect because of children being unchecked) Another error case
There's an issue with the leaf nodes. I fixed it using:
/** Whether all the descendants of the node are selected */
descendantsAllSelected(node: ContentItemModelFlat): boolean {
const descendants = this.treeControl.getDescendants(node);
if(descendants.length === 0)
return this.checklistSelection.isSelected(node)
return descendants.every(child => this.checklistSelection.isSelected(child));
}
I also had to tweak the HTML template:
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node;" matTreeNodePadding>
<button *ngIf="!node.expandable" mat-icon-button disabled></button>
<button *ngIf="node.expandable" mat-icon-button matTreeNodeToggle [attr.aria-label]="'toggle ' + node.name">
<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.name}}</mat-checkbox>
</mat-tree-node>
</mat-tree>
All seems to be working as expected now (Select All, Deselect All, Partial Selection, Leaf Nodes etc)
There's an issue with the leaf nodes. I fixed it using:
/** Whether all the descendants of the node are selected */ descendantsAllSelected(node: ContentItemModelFlat): boolean { const descendants = this.treeControl.getDescendants(node); if(descendants.length === 0) return this.checklistSelection.isSelected(node) return descendants.every(child => this.checklistSelection.isSelected(child)); }I also had to tweak the HTML template:
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl"> <mat-tree-node *matTreeNodeDef="let node;" matTreeNodePadding> <button *ngIf="!node.expandable" mat-icon-button disabled></button> <button *ngIf="node.expandable" mat-icon-button matTreeNodeToggle [attr.aria-label]="'toggle ' + node.name"> <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.name}}</mat-checkbox> </mat-tree-node> </mat-tree>All seems to be working as expected now (Select All, Deselect All, Partial Selection, Leaf Nodes etc)
I tried your solution, still facing few issues in some scenarios.
Could you please share your full solution in StackBlitz or paste the code
I also tried the suggestion and doesn't work.
Hope for solution.
Thanks
Hi, i am using this but how to do two way binding.
I have saved the record from Add page, now I am building Edit page, so out of 20 check-boxes only 10 should be checked, which will come from API call.
Any idea how to do this?
There's an issue with the leaf nodes. I fixed it using:
/** Whether all the descendants of the node are selected */ descendantsAllSelected(node: ContentItemModelFlat): boolean { const descendants = this.treeControl.getDescendants(node); if(descendants.length === 0) return this.checklistSelection.isSelected(node) return descendants.every(child => this.checklistSelection.isSelected(child)); }I also had to tweak the HTML template:
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl"> <mat-tree-node *matTreeNodeDef="let node;" matTreeNodePadding> <button *ngIf="!node.expandable" mat-icon-button disabled></button> <button *ngIf="node.expandable" mat-icon-button matTreeNodeToggle [attr.aria-label]="'toggle ' + node.name"> <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.name}}</mat-checkbox> </mat-tree-node> </mat-tree>All seems to be working as expected now (Select All, Deselect All, Partial Selection, Leaf Nodes etc)
that worked for me. thanks @secretorange
`import { NestedTreeControl } from '@angular/cdk/tree';
import { Component } from '@angular/core';
import { MatTreeNestedDataSource } from '@angular/material/tree';
import { SelectionModel } from '@angular/cdk/collections';
interface ITreeNode {
children?: ITreeNode[];
name: string;
expanded: boolean;
}
const TREE_DATA = [
{
name: 'Land Plane',
expanded: true,
children: [
{ name: 'Piston', expanded: true, children: [] },
{ name: 'Jet', expanded: true, children: [] },
{ name: 'Turboprop', expanded: true, children: [] }
]
},
{
name: 'Helicopter',
expanded: true,
children: [
{ name: 'Piston', expanded: true, children: [] },
{ name: 'Turboprop', expanded: true, children: [] }
]
},
{
name: 'Amphibian',
expanded: true,
children: [{ name: 'Turboprop', expanded: true, children: [] }]
},
{
name: 'Tiltwing',
expanded: true,
children: [{ name: 'Turboprop', expanded: true, children: [] }]
},
{
name: 'Gyrocopter',
expanded: true,
children: [{ name: 'Piston', expanded: true, children: [] }]
},
{
name: 'Tower',
expanded: true,
children: []
},
{
name: 'Gyrocopter',
expanded: true,
children: []
}
];
@Component({
selector: 'globe-source-facets',
templateUrl: './globe-source-facets.component.html',
styleUrls: ['./globe-source-facets.component.scss']
})
export class GlobeSourceFacetsComponent {
public nestedTreeControl: NestedTreeControl
public nestedDataSource: MatTreeNestedDataSource
public checklistSelection = new SelectionModel
constructor() {
this.nestedTreeControl = new NestedTreeControl
this.getChildren
);
this.nestedDataSource = new MatTreeNestedDataSource();
this.nestedDataSource.data = TREE_DATA;
}
public hasNestedChild = (_: number, nodeData: ITreeNode) =>
nodeData.children.length > 0;
public getChildren = (node: ITreeNode) => node.children;
public changeState(node) {
node.expanded = !node.expanded;
}
public descendantsAllSelected(node: ITreeNode): boolean {
const descendants = this.nestedTreeControl.getDescendants(node);
let allselected = descendants.every(child => this.checklistSelection.isSelected(child));
if (allselected)
this.checklistSelection.select(node);
else
this.checklistSelection.deselect(node);
return allselected;
}
public descendantsPartiallySelected(node: ITreeNode): boolean {
const descendants = this.nestedTreeControl.getDescendants(node);
const result = descendants.some(child =>
this.checklistSelection.isSelected(child)
);
return result && !this.descendantsAllSelected(node);
}
public todoItemSelectionToggle(node: ITreeNode): void {
this.checklistSelection.toggle(node);
console.log(this.checklistSelection)
const descendants = this.nestedTreeControl.getDescendants(node);
console.log(descendants);
this.checklistSelection.isSelected(node)
? this.checklistSelection.select(...descendants)
: this.checklistSelection.deselect(...descendants);
}
}
`

Everything works but i get this error. Any Ideas?
`import { NestedTreeControl } from '@angular/cdk/tree';
import { Component } from '@angular/core';
import { MatTreeNestedDataSource } from '@angular/material/tree';
import { SelectionModel } from '@angular/cdk/collections';interface ITreeNode {
children?: ITreeNode[];
name: string;
expanded: boolean;
}const TREE_DATA = [
{
name: 'Land Plane',
expanded: true,
children: [
{ name: 'Piston', expanded: true, children: [] },
{ name: 'Jet', expanded: true, children: [] },
{ name: 'Turboprop', expanded: true, children: [] }
]
},
{
name: 'Helicopter',
expanded: true,
children: [
{ name: 'Piston', expanded: true, children: [] },
{ name: 'Turboprop', expanded: true, children: [] }
]
},
{
name: 'Amphibian',
expanded: true,
children: [{ name: 'Turboprop', expanded: true, children: [] }]
},
{
name: 'Tiltwing',
expanded: true,
children: [{ name: 'Turboprop', expanded: true, children: [] }]
},
{
name: 'Gyrocopter',
expanded: true,
children: [{ name: 'Piston', expanded: true, children: [] }]
},
{
name: 'Tower',
expanded: true,
children: []
},
{
name: 'Gyrocopter',
expanded: true,
children: []
}
];@component({
selector: 'globe-source-facets',
templateUrl: './globe-source-facets.component.html',
styleUrls: ['./globe-source-facets.component.scss']
})
export class GlobeSourceFacetsComponent {
public nestedTreeControl: NestedTreeControl;
public nestedDataSource: MatTreeNestedDataSource;
public checklistSelection = new SelectionModel(true);
constructor() {
this.nestedTreeControl = new NestedTreeControl(
this.getChildren
);
this.nestedDataSource = new MatTreeNestedDataSource();
this.nestedDataSource.data = TREE_DATA;
}public hasNestedChild = (_: number, nodeData: ITreeNode) => nodeData.children.length > 0; public getChildren = (node: ITreeNode) => node.children; public changeState(node) { node.expanded = !node.expanded; } public descendantsAllSelected(node: ITreeNode): boolean { const descendants = this.nestedTreeControl.getDescendants(node); let allselected = descendants.every(child => this.checklistSelection.isSelected(child)); if (allselected) this.checklistSelection.select(node); else this.checklistSelection.deselect(node); return allselected; } public descendantsPartiallySelected(node: ITreeNode): boolean { const descendants = this.nestedTreeControl.getDescendants(node); const result = descendants.some(child => this.checklistSelection.isSelected(child) ); return result && !this.descendantsAllSelected(node); } public todoItemSelectionToggle(node: ITreeNode): void { this.checklistSelection.toggle(node); console.log(this.checklistSelection) const descendants = this.nestedTreeControl.getDescendants(node); console.log(descendants); this.checklistSelection.isSelected(node) ? this.checklistSelection.select(...descendants) : this.checklistSelection.deselect(...descendants); }}
`
Everything works but i get this error. Any Ideas?
You can sove this error using the ChangeDetectorRef.
public ngDoCheck() {
this.cdRef.detectChanges();
}
Then call this.ngDoCheck();
I had the same error and this worked for me.
Example from my code:
ngAfterViewInit() {
this.ngDoCheck(); <---
// tslint:disable-next-line:forin
for (const i in this.formCRUD.controls) {
this.formCRUD.controls[i].markAsTouched();
}
if (this.inputEmpresaElement) {
this.inputEmpresaElement.nativeElement.focus();
}
if (!this.param || this.param === 'novo') {
this.operation = 'insert';
} else if (this.param && this.param !== 'novo') {
this.operation = 'edit';
}
this.updateAtivo();
}
I am trying to limit the number of nodes to add on a list, when I check a parent node. For example:
I want to limit it to 3 elements, so if I check Fruits, I want to only get selected and added first 3 descendant nodes.
Here's a code similar to mine on stackblitz. How do I have to do it in a code like this one?
I have an issue open the link add a tree key called testing:null in TREE_DATA ,
then by default the parent Groceries should be selected . I tried by creating an object and calling the method which the same is used from html but unable to do it. Can any one help the same ..?
https://stackblitz.com/angular/rlqgljjemqyk?file=src%2Fapp%2Ftree-checklist-example.ts
This implementation seems to handle all cases:
descendantsAllSelected(node: TodoItemFlatNode): boolean {
const descendants = this.treeControl.getDescendants(node);
if (descendants.length === 0) {
return this.checklistSelection.isSelected(node);
}
const allselected = descendants.every(child => this.checklistSelection.isSelected(child));
if (allselected) {
this.checklistSelection.select(node);
} else {
this.checklistSelection.deselect(node);
}
return allselected;
}
This implementation seems to handle all cases:
descendantsAllSelected(node: TodoItemFlatNode): boolean { const descendants = this.treeControl.getDescendants(node); if (descendants.length === 0) { return this.checklistSelection.isSelected(node); } const allselected = descendants.every(child => this.checklistSelection.isSelected(child)); if (allselected) { this.checklistSelection.select(node); } else { this.checklistSelection.deselect(node); } return allselected; }
Did it worked for you?
There's an issue with the leaf nodes. I fixed it using:
/** Whether all the descendants of the node are selected */ descendantsAllSelected(node: ContentItemModelFlat): boolean { const descendants = this.treeControl.getDescendants(node); if(descendants.length === 0) return this.checklistSelection.isSelected(node) return descendants.every(child => this.checklistSelection.isSelected(child)); }I also had to tweak the HTML template:
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl"> <mat-tree-node *matTreeNodeDef="let node;" matTreeNodePadding> <button *ngIf="!node.expandable" mat-icon-button disabled></button> <button *ngIf="node.expandable" mat-icon-button matTreeNodeToggle [attr.aria-label]="'toggle ' + node.name"> <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.name}}</mat-checkbox> </mat-tree-node> </mat-tree>All seems to be working as expected now (Select All, Deselect All, Partial Selection, Leaf Nodes etc)
It worked for me and I have no idea how it worked :) Thanks a lot
Most helpful comment
This implementation seems to handle all cases: