Reputation: 336
I have the functionality working but when I switch the value of intermediate i get that error. Not sure if Im changing 2 observables so its like which value should i take. IDK lol! I added my code down below. If you need anything else just let me know. Any Ideas on how to solve this? Man Mat tree component is so annoying!
import { NestedTreeControl } from '@angular/cdk/tree';
import { Component, ChangeDetectorRef } 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<ITreeNode>;
public nestedDataSource: MatTreeNestedDataSource<ITreeNode>;
public checklistSelection = new SelectionModel<ITreeNode>(true);
constructor(private changeDetectorRef: ChangeDetectorRef) {
this.nestedTreeControl = new NestedTreeControl<ITreeNode>(
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;
}
descendantsAllSelected(node: ITreeNode): boolean {
const descendants = this.nestedTreeControl.getDescendants(node);
if (!descendants.length) {
return this.checklistSelection.isSelected(node);
}
const selected = this.checklistSelection.isSelected(node);
const allSelected = descendants.every(child => this.checklistSelection.isSelected(child));
if (!selected && allSelected) {
this.checklistSelection.select(node);
this.changeDetectorRef.markForCheck();
}
return allSelected;
}
public descendantsPartiallySelected(node: ITreeNode): boolean {
const descendants = this.nestedTreeControl.getDescendants(node);
if (!descendants.length) {
return false;
}
const result = descendants.some(child => this.checklistSelection.isSelected(child));
return result && !this.descendantsAllSelected(node);
}
public todoItemSelectionToggle(node: ITreeNode): void {
this.checklistSelection.toggle(node);
const descendants = this.nestedTreeControl.getDescendants(node);
this.checklistSelection.isSelected(node)
? this.checklistSelection.select(...descendants)
: this.checklistSelection.deselect(...descendants);
}
}
<div class="facets-container">
<div class="tree-container">
<mat-tree
[dataSource]="nestedDataSource"
[treeControl]="nestedTreeControl"
class="example-tree"
>
<mat-tree-node *matTreeNodeDef="let node" disabled="true">
<li class="mat-tree-node">
<button mat-icon-button disabled></button>
<mat-checkbox
class="checklist-leaf-node"
[checked]="checklistSelection.isSelected(node)"
(change)="todoItemSelectionToggle(node)"
>{{ node.name }}</mat-checkbox
>
</li>
</mat-tree-node>
<mat-nested-tree-node
*matTreeNodeDef="let node; when: hasNestedChild"
>
<li>
<div class="mat-tree-node">
<button
mat-icon-button
[attr.aria-label]="'toggle ' + node.name"
(click)="changeState(node)"
>
<mat-icon class="mat-icon-rtl-mirror">
{{
node.expanded
? 'chevron_right'
: 'expand_more'
}}
</mat-icon>
</button>
<mat-checkbox
*ngIf="node.name !== ''"
class="checklist-leaf-node"
[checked]="checklistSelection.isSelected(node)"
[indeterminate]="descendantsPartiallySelected(node)"
(change)="todoItemSelectionToggle(node)"
>{{ node.name }}</mat-checkbox
>
</div>
<ul [class.example-tree-invisible]="node.expanded">
<ng-container matTreeNodeOutlet></ng-container>
</ul>
</li>
</mat-nested-tree-node>
</mat-tree>
</div>
<div class="facet-actions">
<button mat-button>CLEAR</button>
<button mat-button color="primary">APPLY</button>
</div>
</div>
Upvotes: 2
Views: 3012
Reputation: 1146
Changing the checked condition in the nested nodes like following in the view gets rid of the error:
<div class="facets-container">
<div class="tree-container">
<mat-tree
[dataSource]="nestedDataSource"
[treeControl]="nestedTreeControl"
class="example-tree"
>
<mat-tree-node *matTreeNodeDef="let node" disabled="true">
<li class="mat-tree-node">
<button mat-icon-button disabled></button>
<mat-checkbox
class="checklist-leaf-node"
[checked]="checklistSelection.isSelected(node)"
(change)="todoItemSelectionToggle(node)"
>{{ node.name }}</mat-checkbox
>
</li>
</mat-tree-node>
<mat-nested-tree-node
*matTreeNodeDef="let node; when: hasNestedChild"
>
<li>
<div class="mat-tree-node">
<button
mat-icon-button
[attr.aria-label]="'toggle ' + node.name"
(click)="changeState(node)"
>
<mat-icon class="mat-icon-rtl-mirror">
{{
node.expanded
? 'chevron_right'
: 'expand_more'
}}
</mat-icon>
</button>
<mat-checkbox
*ngIf="node.name !== ''"
class="checklist-leaf-node"
[checked]="descendantsAllSelected(node)"
[indeterminate]="descendantsPartiallySelected(node)"
(change)="todoItemSelectionToggle(node)"
>{{ node.name }}</mat-checkbox
>
</div>
<ul [class.example-tree-invisible]="node.expanded">
<ng-container matTreeNodeOutlet></ng-container>
</ul>
</li>
</mat-nested-tree-node>
</mat-tree>
</div>
<div class="facet-actions">
<button mat-button>CLEAR</button>
<button mat-button color="primary">APPLY</button>
</div>
</div>
To fix the bug with the parent node state after last child node is deselected, change todoItemSelectionToggle() method like following:
import {NestedTreeControl} from '@angular/cdk/tree';
import {Component, ChangeDetectorRef, OnInit} 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: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
public nestedTreeControl: NestedTreeControl<ITreeNode>;
public nestedDataSource: MatTreeNestedDataSource<ITreeNode>;
public checklistSelection = new SelectionModel<ITreeNode>(true);
constructor(private changeDetectorRef: ChangeDetectorRef) {
}
ngOnInit() {
this.nestedTreeControl = new NestedTreeControl<ITreeNode>(
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;
}
descendantsAllSelected(node: ITreeNode): boolean {
const descendants = this.nestedTreeControl.getDescendants(node);
if (!descendants.length) {
return this.checklistSelection.isSelected(node);
}
const selected = this.checklistSelection.isSelected(node);
const allSelected = descendants.every(child => this.checklistSelection.isSelected(child));
if (!selected && allSelected) {
this.checklistSelection.select(node);
this.changeDetectorRef.markForCheck();
}
return allSelected;
}
public descendantsPartiallySelected(node: ITreeNode): boolean {
const descendants = this.nestedTreeControl.getDescendants(node);
if (!descendants.length) {
return false;
}
const result = descendants.some(child => this.checklistSelection.isSelected(child));
return result && !this.descendantsAllSelected(node);
}
public todoItemSelectionToggle(node: ITreeNode): void {
this.checklistSelection.toggle(node);
const descendants = this.nestedTreeControl.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)
);
}
}
These are from the example with the checkboxes at https://material.angular.io/components/tree/examples
Check the difference in flat tree view for nodes when they have child nodes and not.
Upvotes: 0
Reputation: 1164
This issue is more complex than it seems. This is related to how the Angular works.
Basically, the angular life cycle starts on the parent component, goes to children components, than come back to the parent and finishes.
What is happening is, the life cycle started at the parent and some variable value is A, it goes all the way down to the components tree and when it comes back to the parent the value now is B, the Angular knows the value has changed and shows this error. This error means: "Man, the value has changed while I was doing my life cycle, It not sure if the view was painted with the lasted value"
To fix it? Try to use the lifecycle functions instead of doing things on the constructor.
Upvotes: 1