angular - Angular Material mat-tree 获取复选框值
问题描述
我正在使用Angular Material v6.0
带有复选框的 MatTreeModule (mat-tree)。但是我很难弄清楚如何确定哪些节点已被检查,哪些节点尚未被检查。在 Angular Material 的示例中,他们提供了非常好的源代码来设置它,我能够很好地设置它。
但是,我无法确定哪些复选框已选中,哪些未选中。我已经尝试了几个小时试图解决这个问题,但没有运气。
我的目标是最终用户将选中或取消选中树中的复选框,然后我需要在他们做出选择后运行一些进程。
但是我完全被困在试图弄清楚哪些 mat-tree-nodes 被检查和没有被检查,而且我在任何地方都找不到好的工作示例。
关键源代码在这里
有关 mat-tree 的更多信息可在此处找到
任何人都可以帮助确定复选框是否已被选中?
谢谢。
根据请求,我从两个代码文件中添加代码:
app/tree-checklist-example.ts app/tree-checklist-example.html
来自“app/tree-checklist-example.ts”的打字稿源代码
import {SelectionModel} from '@angular/cdk/collections';
import {FlatTreeControl} from '@angular/cdk/tree';
import {Component, Injectable} from '@angular/core';
import {MatTreeFlatDataSource, MatTreeFlattener} from '@angular/material/tree';
import {BehaviorSubject} from 'rxjs';
/**
* Node for to-do item
*/
export class TodoItemNode {
children: TodoItemNode[];
item: string;
}
/** Flat to-do item node with expandable and level information */
export class TodoItemFlatNode {
item: string;
level: number;
expandable: boolean;
}
/**
* The Json object for to-do list data.
*/
const TREE_DATA = {
Groceries: {
'Almond Meal flour': null,
'Organic eggs': null,
'Protein Powder': null,
Fruits: {
Apple: null,
Berries: ['Blueberry', 'Raspberry'],
Orange: null
}
},
Reminders: [
'Cook dinner',
'Read the Material Design spec',
'Upgrade Application to Angular'
]
};
/**
* Checklist database, it can build a tree structured Json object.
* Each node in Json object represents a to-do item or a category.
* If a node is a category, it has children items and new items can be added under the category.
*/
@Injectable()
export class ChecklistDatabase {
dataChange = new BehaviorSubject<TodoItemNode[]>([]);
get data(): TodoItemNode[] { return this.dataChange.value; }
constructor() {
this.initialize();
}
initialize() {
// Build the tree nodes from Json object. The result is a list of `TodoItemNode` with nested
// file node as children.
const data = this.buildFileTree(TREE_DATA, 0);
// Notify the change.
this.dataChange.next(data);
}
/**
* Build the file structure tree. The `value` is the Json object, or a sub-tree of a Json object.
* The return value is the list of `TodoItemNode`.
*/
buildFileTree(obj: object, level: number): TodoItemNode[] {
return Object.keys(obj).reduce<TodoItemNode[]>((accumulator, key) => {
const value = obj[key];
const node = new TodoItemNode();
node.item = key;
if (value != null) {
if (typeof value === 'object') {
node.children = this.buildFileTree(value, level + 1);
} else {
node.item = value;
}
}
return accumulator.concat(node);
}, []);
}
/** Add an item to to-do list */
insertItem(parent: TodoItemNode, name: string) {
if (parent.children) {
parent.children.push({item: name} as TodoItemNode);
this.dataChange.next(this.data);
}
}
updateItem(node: TodoItemNode, name: string) {
node.item = name;
this.dataChange.next(this.data);
}
}
/**
* @title Tree with checkboxes
*/
@Component({
selector: 'tree-checklist-example',
templateUrl: 'tree-checklist-example.html',
styleUrls: ['tree-checklist-example.css'],
providers: [ChecklistDatabase]
})
export class TreeChecklistExample {
/** Map from flat node to nested node. This helps us finding the nested node to be modified */
flatNodeMap = new Map<TodoItemFlatNode, TodoItemNode>();
/** Map from nested node to flattened node. This helps us to keep the same object for selection */
nestedNodeMap = new Map<TodoItemNode, TodoItemFlatNode>();
/** A selected parent node to be inserted */
selectedParent: TodoItemFlatNode | null = null;
/** The new item's name */
newItemName = '';
treeControl: FlatTreeControl<TodoItemFlatNode>;
treeFlattener: MatTreeFlattener<TodoItemNode, TodoItemFlatNode>;
dataSource: MatTreeFlatDataSource<TodoItemNode, TodoItemFlatNode>;
/** The selection for checklist */
checklistSelection = new SelectionModel<TodoItemFlatNode>(true /* multiple */);
constructor(private database: ChecklistDatabase) {
this.treeFlattener = new MatTreeFlattener(this.transformer, this.getLevel,
this.isExpandable, this.getChildren);
this.treeControl = new FlatTreeControl<TodoItemFlatNode>(this.getLevel, this.isExpandable);
this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
database.dataChange.subscribe(data => {
this.dataSource.data = data;
});
}
getLevel = (node: TodoItemFlatNode) => node.level;
isExpandable = (node: TodoItemFlatNode) => node.expandable;
getChildren = (node: TodoItemNode): TodoItemNode[] => node.children;
hasChild = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.expandable;
hasNoContent = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.item === '';
/**
* Transformer to convert nested node to flat node. Record the nodes in maps for later use.
*/
transformer = (node: TodoItemNode, level: number) => {
const existingNode = this.nestedNodeMap.get(node);
const flatNode = existingNode && existingNode.item === node.item
? existingNode
: new TodoItemFlatNode();
flatNode.item = node.item;
flatNode.level = level;
flatNode.expandable = !!node.children;
this.flatNodeMap.set(flatNode, node);
this.nestedNodeMap.set(node, flatNode);
return flatNode;
}
/** Whether all the descendants of the node are selected */
descendantsAllSelected(node: TodoItemFlatNode): boolean {
const descendants = this.treeControl.getDescendants(node);
return descendants.every(child => this.checklistSelection.isSelected(child));
}
/** Whether part of the descendants are selected */
descendantsPartiallySelected(node: TodoItemFlatNode): 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: TodoItemFlatNode): void {
this.checklistSelection.toggle(node);
const descendants = this.treeControl.getDescendants(node);
this.checklistSelection.isSelected(node)
? this.checklistSelection.select(...descendants)
: this.checklistSelection.deselect(...descendants);
}
/** Select the category so we can insert the new item. */
addNewItem(node: TodoItemFlatNode) {
const parentNode = this.flatNodeMap.get(node);
this.database.insertItem(parentNode!, '');
this.treeControl.expand(node);
}
/** Save the node to database */
saveNode(node: TodoItemFlatNode, itemValue: string) {
const nestedNode = this.flatNodeMap.get(node);
this.database.updateItem(nestedNode!, itemValue);
}
}
HTML source code from "app/tree-checklist-example.html":
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle matTreeNodePadding>
<button mat-icon-button disabled></button>
<mat-checkbox class="checklist-leaf-node"
[checked]="checklistSelection.isSelected(node)"
(change)="checklistSelection.toggle(node);">{{node.item}}</mat-checkbox>
</mat-tree-node>
<mat-tree-node *matTreeNodeDef="let node; when: hasNoContent" matTreeNodePadding>
<button mat-icon-button disabled></button>
<mat-form-field>
<input matInput #itemValue placeholder="New item...">
</mat-form-field>
<button mat-button (click)="saveNode(node, itemValue.value)">Save</button>
</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.item}}</mat-checkbox>
<button mat-icon-button (click)="addNewItem(node)"><mat-icon>add</mat-icon></button>
</mat-tree-node>
</mat-tree>
如前所述,要查看完整的源代码及其运行演示,请访问: https ://stackblitz.com/angular/gabkadkvybq?file=app%2Ftree-checklist-example.html
谢谢。
解决方案
我认为选择列表(在您的 Stackblitz 演示链接中)是您想要的。这意味着您必须自己跟踪选择MatTree
:它不会为您执行此操作,因为它不知道如何处理MatCheckbox
您在节点上使用的 es。
在您的演示中,这是通过使用/维护(不属于 的SelectionModel
集合)来完成的。修改后的 Stackblitz 示例在这里(只需选择一些带有子节点的节点)。@angular/cdk/collections
MatTree
MatTree
演示的重要部分是每次单击 a 都会MatCheckbox
触发@Output() change
该复选框,该复选框用于触发该todoItemSelectionToggle
方法,该方法会更新SelectionModel
:
/** Toggle the to-do item selection. Select/deselect all the descendants node */
todoItemSelectionToggle(node: TodoItemFlatNode): void {
// HERE IS WHERE THE PART OF THE MODEL RELATED TO THE CLICKED CHECKBOX IS UPDATED
this.checklistSelection.toggle(node);
// HERE WE GET POTENTIAL CHILDREN OF THE CLICKED NODE
const descendants = this.treeControl.getDescendants(node);
// HERE IS WHERE THE REST OF THE MODEL (POTENTIAL CHILDREN OF THE CLICKED NODE) IS UPDATED
this.checklistSelection.isSelected(node)
? this.checklistSelection.select(...descendants)
: this.checklistSelection.deselect(...descendants);
}
这SelectionModel
是一个基于 a 的集合Set
,由@angular
团队构建,完全由开发人员使用,使用允许多项选择的组件来帮助他们跟踪这些组件的更改。您可以在此处查看有关此集合的更多详细信息:https ://github.com/angular/components/blob/master/src/cdk/collections/selection-model.ts
就像 javascript 中的所有内容一样,这里没有魔法,基本上,它的构造函数接受一个布尔参数来定义SelectionModel<T>
(它是泛型)是存储多个值(true)还是单个值。它还具有方便的方法sort(predicate?: (a: T, b: T) => number)
,例如select(...values: T[])
添加对象、deselect(...values: T[])
删除对象、toggle(o: T)
添加(如果它不存在)或删除(如果它已经存在)。在内部,默认情况下,比较是通过引用完成的,所以{a:1} != {a:1}
.
推荐阅读
- python - 在火花中更改时间戳 TZ
- python - TypeError:逻辑回归模型中的“float”和“str”实例之间不支持“<”
- bash - 如何正确设置此变量?
- c# - 在 webp 图像上应用水印
- java - 如何将keycloak迁移到mysql数据库并修复300异常后的超时
- javascript - 无法使用 javascript 从 web url 获取文本数据
- php - 如何不允许使用 Web 服务覆盖特定语言的产品描述字段?
- git - 如何使用 yarn 从 github 添加一个使用 typescript 且尚未编译的包?
- url - 如何在 URL 中找到哪些 unicode 字母看起来不错
- .net-core - 收到“拒绝应用内联样式,因为它违反了以下内容安全策略”错误