angular - 表达式在被检查后已更改 --> 复选框 mat-tree
我的功能可以正常运行,但当我切换 intermediate 的值时就会出现错误。不确定是不是我在更改两个可观察对象,所以就像‘我应该取哪个值’一样。我也不知道 lol!我把我的代码贴在下面。如果你还需要什么,请告诉我。有什么解决办法吗?天啊,Mat 树组件真烦人!
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>答案#1
这个问题比看起来更复杂。这与 Angular 的工作原理有关。
基本上,Angular 的生命周期从父组件开始,传递到子组件,然后回到父组件并结束。
发生的情况是,生命周期从父组件开始,某个变量的值是A,它会一直传递到组件树的各个子组件,当它返回到父组件时,值现在是B,Angular 知道值已经改变并显示这个错误。这个错误的意思是:“伙计,在我执行生命周期的时候,值已经改变,不确定视图是否已经使用了最新的值进行渲染。”
要修复它?尝试使用生命周期函数,而不是在构造函数中处理事情。
答案#2
在视图中像下面这样更改嵌套节点的选中状态可以消除错误:
<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>
要修复在最后一个子节点被取消选择后父节点状态的错误,请按如下方式修改 todoItemSelectionToggle() 方法:
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)
);
}
}
这些来自于带复选框的示例,网址为 https://material.angular.io/components/tree/examples
检查节点在有子节点和没有子节点时平面树视图的差异。
