Angular material 如何使动态树视图独立于每个节点
我在使用材质树视图实现角度应用程序时遇到了一个问题。 树视图是动态的,这意味着您可以在单击“添加”按钮时添加子节点 HTML代码如下所示Angular material 如何使动态树视图独立于每个节点,angular-material,treeview,Angular Material,Treeview,我在使用材质树视图实现角度应用程序时遇到了一个问题。 树视图是动态的,这意味着您可以在单击“添加”按钮时添加子节点 HTML代码如下所示 <mat-tree [dataSource]="dataSource" [treeControl]="treeControl"> <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle matTreeNodePadding> <button mat-ico
<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)="todoLeafItemSelectionToggle(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>
<mat-form-field appearance="legacy">
<input matInput type="text" [formControl]="locationField" [(ngModel)]="node.field" name="node" [matAutocomplete]="auto" placeholder="Field"/>
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let filteredFieldResult of locationFieldResults" [value]="filteredFieldResult">
{{filteredFieldResult}}
</mat-option>
</mat-autocomplete>
</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>
{{node.item}
{{FilteredFeldResult}
拯救
{{treeControl.isExpanded(节点)?'expand_more':'chevron_right'}
{{node.item}
添加
您可以看到,我有一个带有自动完成的输入字段
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';
import { FormControl } from '@angular/forms';
/**
* 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: {[key: string]: any}, 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>();
public locationField: FormControl = new FormControl();
public locationFieldResults = ["abc", "asdfa", "asdfasd"]
/** 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;
});
this.locationField.valueChanges.subscribe(inputField => {
this.filterField(inputField);
}
);
}
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);
const descAllSelected = descendants.every(child =>
this.checklistSelection.isSelected(child)
);
return descAllSelected;
}
/** 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);
// 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: TodoItemFlatNode): void {
this.checklistSelection.toggle(node);
this.checkAllParentsSelection(node);
}
/* Checks all the parents when a leaf node is selected/unselected */
checkAllParentsSelection(node: TodoItemFlatNode): void {
let parent: TodoItemFlatNode | null = this.getParentNode(node);
while (parent) {
this.checkRootNodeSelection(parent);
parent = this.getParentNode(parent);
}
}
/** Check root node checked state and change it accordingly */
checkRootNodeSelection(node: TodoItemFlatNode): 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: TodoItemFlatNode): TodoItemFlatNode | 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;
}
/** 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);
}
private filterField(value: string): string[] {
if (value) {
this.locationFieldResults = this.locationFieldResults.filter((searchFieldResult) => {
return searchFieldResult.indexOf(value) !== -1;
});
} else {
this.locationFieldResults = this.locationFieldResults;
}
return this.locationFieldResults;
}
}
从'@angular/cdk/collections'导入{SelectionModel};
从'@angular/cdk/tree'导入{FlatTreeControl};
从“@angular/core”导入{Component,Injectable};
从“@angular/material/tree”导入{MatTreeFlatDataSource,MattreeFlatter};
从“rxjs”导入{BehaviorSubject};
从'@angular/forms'导入{FormControl};
/**
*待办事项节点
*/
将类导出到doItemNode{
子项:TodoItemNode[];
项目:字符串;
}
/**具有可扩展和级别信息的平面待办事项节点*/
将类导出到DoItemFlatNode{
项目:字符串;
级别:数字;
可扩展:布尔型;
}
/**
*待办事项列表数据的Json对象。
*/
常数树_数据={
食品杂货:{
“杏仁粉”:空,
“有机鸡蛋”:空,
“蛋白粉”:空,
水果:{
苹果:空,
浆果:[“蓝莓”、“覆盆子”],
橙色:空
}
},
提醒:[
“做饭”,
“阅读材料设计规范”,
“将应用程序升级到Angular”
]
};
/**
*清单数据库,它可以构建一个树状结构的Json对象。
*Json对象中的每个节点表示一个待办事项或一个类别。
*如果节点是一个类别,则它具有子项,并且可以在该类别下添加新项。
*/
@可注射()
导出类ChecklistDatabase{
dataChange=新的行为主体([]);
get data():TodoItemNode[]{返回this.dataChange.value;}
构造函数(){
这是初始化();
}
初始化(){
//从Json对象生成树节点。结果是带有嵌套
//文件节点作为子节点。
const data=this.buildFileTree(树数据,0);
//通知变更。
this.dataChange.next(数据);
}
/**
*构建文件结构树。“value”是Json对象,或Json对象的子树。
*返回值是“TodoItemNode”的列表。
*/
buildFileTree(obj:{[key:string]:any},级别:number):TodoItemNode[]{
返回Object.keys(obj).reduce((累加器,键)=>{
常量值=对象[键];
const node=new TodoItemNode();
node.item=key;
if(值!=null){
如果(值的类型==='object'){
node.children=this.buildFileTree(值,级别+1);
}否则{
node.item=值;
}
}
返回累加器concat(节点);
}, []);
}
/**将项目添加到待办事项列表中*/
插入项(父项:TodoItemNode,名称:string){
if(父项、子项){
push({item:name}作为TodoItemNode);
this.dataChange.next(this.data);
}
}
updateItem(节点:TodoItemNode,名称:string){
node.item=名称;
this.dataChange.next(this.data);
}
}
/**
*@带有复选框的标题树
*/
@组成部分({
选择器:“树清单示例”,
templateUrl:'tree checklist example.html',
样式URL:['tree-checklist-example.css'],
提供者:[检查列表数据库]
})
导出类TreeChecklistExample{
/**从平面节点映射到嵌套节点。这有助于我们找到要修改的嵌套节点*/
flatNodeMap=新映射();
/**从嵌套节点映射到展平节点。这有助于我们保留相同的对象以供选择*/
nestedNodeMap=新映射();
public locationField:FormControl=new FormControl();
公共位置字段结果=[“abc”、“asdfa”、“asdfasd”]
/**要插入的选定父节点*/
selectedParent:TodoItemFlatNode | null=null;
/**新项目的名称*/
newItemName='';
treeControl:FlatTreeControl;
树展平机:马特里展平机;
数据源:MatTreeFlatDataSource;
/**检查表的选择*/
checklistSelection=newselectionmodel(true/*多个*/);
构造函数(私有数据库:ChecklistDatabase){
this.treeFlattener=新的MattreeFlatter(this.transformer,this.getLevel,
this.isExpandable,this.getChildren);
this.treeControl=新的FlatTreeControl(this.getLevel,this.isExpandable);
this.dataSource=新MatTreeFlatDataSource(this.treeControl,this.treeFlattener);
_database.dataChange.subscribe(数据=>{
this.dataSource.data=数据;
});
this.locationField.valueChanges.subscribe(inputField=>{
这个.filterField(输入字段);
}
);
}
getLevel=(节点:TodoItemFlatNode)=>node.level;
isExpandable=(节点:TodoItemFlatNode)=>node.expandable;
getChildren=(节点:TodoItemNode):TodoItemNode[]=>node.children;
hasChild=(\u:number,\u nodeData:TodoItemFlatNode)=>\u nodeData.expandable;
hasNoContent=(\u:number,\u nodeData:TodoItemFlatNode)=>\u nodeData.item==“”;
/**
*将嵌套节点转换为平面节点的转换器。在映射中记录节点以供以后使用。
*/
transformer=(节点:TodoItemNode,级别:number)=>{
const existingNode=this.nestedNodeMap.get(节点);
const flatNode=existingNode&&existingNode.item===node.item
?现有节点
:新建TodoItemFlatNode();
flatNode.item=node.item;
flatNode.level=级别;
flatNode.expandable=!!node.children;
此.flatNodeMap.set(flatNode,node);
this.nestedNodeMap.set(node,flatNode);
返回平面节点;
}
/**是否选择节点的所有子体*/
子体所有选定(节点:TodoItemFlatNode):布尔值{
const substands=this.treeControl.getsubstands(节点);
常数描述