如何过滤mat-tree组件Angular Material 6.0.1

Posted

技术标签:

【中文标题】如何过滤mat-tree组件Angular Material 6.0.1【英文标题】:How to filter a mat-tree component Angular Material 6.0.1 【发布时间】:2018-11-09 17:20:06 【问题描述】:

我正在使用 mat-tree 角度材质组件。 这是一个不错的组件,具有一些非常有用的功能,例如多选、全部展开/全部折叠。 我在他们的任何 API 中都找不到任何树过滤功能。 有没有人遇到过这个功能或做过任何工作来获得 mat-tree 过滤器?

【问题讨论】:

【参考方案1】:

在我花了几天时间完成同一个任务之后,我可以给出一些提示: 我正在使用输入事件来跟踪用户输入:

<input matInput class="form-control" 
  (input)="filterChanged($event.target.value)" 
  placeholder="Search Skill">

在这个过滤器上我附加了一个主题,以便我可以订阅它:

searchFilter: Subject<string> = new Subject<string>();
filterChanged(filter: string): void 
  this.searchFilter.next(filter);

为了方便用户,通常我们希望延迟搜索执行,您可以使用debounceTime 来完成。

this.searchFilter.pipe(debounceTime(500), distinctUntilChanged())
  .subscribe(value => 
    if (value && value.length >= 3) 
      this.filterByName(value);
     else 
      this.clearFilter();
    
);

为了执行搜索,我使用 css 类隐藏和显示节点。这是直接在平面且非常易于过滤的演示文稿集合上完成的。

treeControl: FlatTreeControl<SkillFlatNode>;
this.treeControl.dataNodes

首先,我隐藏所有,然后只显示符合条件的那些。最后,我想向他们的父母展示,但这是特定于我的树结构的。

private filterByName(term: string): void 
  const filteredItems = this.treeControl.dataNodes.filter(
    x => x.value.DisplayName.toLowerCase().indexOf(term.toLowerCase()) === -1
  );
  filteredItems.map(x => 
    x.visible = false;
  );

  const visibleItems = this.treeControl.dataNodes.filter(
    x => x.value.IsSkill &&
    x.value.DisplayName.toLowerCase().indexOf(term.toLowerCase()) > -1
  );
  visibleItems.map( x => 
    x.visible = true;
    this.markParent(x);
  );

最后,这里是清除过滤器:

private clearFilter(): void 
  this.treeControl.dataNodes.forEach(x => x.visible = true);

不要像我一样犯同样的错误并尝试过滤输入集合(在我的情况下为this.dataSource.data),因为您将丢失您的选择,或者您必须将其映射回演示文稿。 这是我的初始数据:

this.treeFlattener = new MatTreeFlattener(
  this.transformer, this._getLevel, this._isExpandable, this._getChildren
);
this.treeControl = new FlatTreeControl<SkillFlatNode>(
  this._getLevel, this._isExpandable
);
this.dataSource = new MatTreeFlatDataSource(
  this.treeControl, this.treeFlattener
);

skillService.dataChange.subscribe(data => 
  this.dataSource.data = data;
);

【讨论】:

您的数据源结构如何。想知道 markParent 将如何工作。 关键是不要过滤dataSource.data。而是在 dataNodes 上有一个属性来使用 ngClass/ngStyle 驱动显示。 这似乎是要走的路,只是我不知道如何使用可见过滤器?如果我在什么时候添加:它会分开...... 你刚刚救了我的命......它有效【参考方案2】:

我通过创建一个新的数据源(过滤)解决了这个问题。

stackblitz sample

我将解释共享链接的示例:我在ChecklistDatabase 中过滤了filter(filterText: string) 的数据并触发了dataChange 事件。然后datasource.dataTreeChecklistExample 中的已处理事件更改。因此数据源已被修改。

filter(filterText: string) 
  let filteredTreeData;

  if (filterText) 
    filteredTreeData = this.treeData.filter(
      //There is filter function in the sample
    );
   else 
    filteredTreeData = this.treeData;
  

  // file node as children.
  const data = this.buildFileTree(filteredTreeData, '0');

  // Notify the change. !!!IMPORTANT
  this.dataChange.next(data);

【讨论】:

帮帮我***.com/questions/52578743/…【参考方案3】:

我可以使用简单的递归来过滤树。下面是sn-ps的代码:

input type="text"(keyup) 上调用filter() 函数。 cloneDeep 函数是从 lodash import * as cloneDeep from 'lodash/cloneDeep'; 导入的

this.searchString 是过滤文本的字符串值。

  filter() 
    const clonedTreeLocal = cloneDeep(this.clonedTree);
    this.recursiveNodeEliminator(clonedTreeLocal);
    this.dataSource.data = clonedTreeLocal;
    this.treeControl.expandAll();
  

树结构由接口定义

export interface ITreeDataStructure 
    Id?: number;
    name: string;
    type: string;
    children?: Array<ITreeDataStructure>;

实际的过滤是由函数recursiveNodeEliminator完成的

recursiveNodeEliminator(tree: Array<ITreeDataStructure>): boolean 
    for (let index = tree.length - 1; index >= 0; index--) 
      const node = tree[index];
      if (node.children) 
        const parentCanBeEliminated = this.recursiveNodeEliminator(node.children);
        if (parentCanBeEliminated) 
          if (node.name.toLocaleLowerCase().indexOf(this.searchString.toLocaleLowerCase()) === -1) 
            tree.splice(index, 1);
          
        
       else 
        // Its a leaf node. No more branches.
        if (node.name.toLocaleLowerCase().indexOf(this.searchString.toLocaleLowerCase()) === -1) 
          tree.splice(index, 1);
        
      
    
    return tree.length === 0;
  

【讨论】:

太棒了!在具有虚拟滚动和数千个节点的扁平树中像魅力一样工作...... 太棒了。为我工作【参考方案4】:

Stackblitz link for mat-tree filter

如果有人需要在不修改数据源的情况下直观地过滤 mat 树,请选择此解决方案。

基本上这个想法是隐藏不属于搜索字符串的节点。

输入框

<input [(ngModel)]="searchString" />

为叶节点调用过滤函数(这是在第一个mat-tree-node中完成的)

<mat-tree-node
   *matTreeNodeDef="let node"
   [style.display]="
      filterLeafNode(node) ? 'none' : 'block'
   "
   .....
   ......

为叶子节点以外的节点调用过滤函数(这是在第二个mat-tree-node中完成的)

<mat-tree-node
   *matTreeNodeDef="let node; when: hasChild"
   [style.display]="filterParentNode(node) ? 'none' : 'block'"
   .....
   .....

filterLeafNode 函数

filterLeafNode(node: TodoItemFlatNode): boolean 
   if (!this.searchString) 
     return false
   
   return node.item.toLowerCase()
     .indexOf(this.searchString?.toLowerCase()) === -1

filterParentNode 函数

filterParentNode(node: TodoItemFlatNode): boolean 

  if (    
    !this.searchString ||
    node.item.toLowerCase()
     .indexOf(
       this.searchString?.toLowerCase()
     ) !== -1
  ) 
    return false
  
  const descendants = this.treeControl.getDescendants(node)

  if (
    descendants.some(
      (descendantNode) =>
        descendantNode.item
          .toLowerCase()
          .indexOf(this.searchString?.toLowerCase()) !== -1
    )
  ) 
    return false
  
  return true

【讨论】:

【参考方案5】:

这适用于模型过滤器。创建 2 个列表,一个用于存储所有数据,另一个用作数据源。

 SearchCategory(searchText)
      this.searchText=searchText;
      this.categories=this.categoryNameSearch(this.AllCategories,searchText)
      this.dataSource.data = this.categories;
    
    categoryNameSearch(categorys:Category[],searchText):Category[]
      let category:Category[];
      category=categorys.filter(f=>this.converter(f.name).includes(this.converter(searchText)))
      categorys.forEach(element => 
        this.categoryNameSearch(element.childrens,searchText).forEach(e=>category.push(e))
      );
      return category;
    
    converter(text) 
      var trMap = 
          'çÇ':'c',
          'ğĞ':'g',
          'şŞ':'s',
          'üÜ':'u',
          'ıİ':'i',
          'öÖ':'o'
      ;
      for(var key in trMap) 
          text = text.replace(new RegExp('['+key+']','g'), trMap[key]);
      
      return  text.replace(/[^-a-zA-Z0-9\s]+/ig, '') // remove non-alphanumeric chars
                  .replace(/\s/gi, "-") // convert spaces to dashes
                  .replace(/[-]+/gi, "-") // trim repeated dashes
                  .toLowerCase();
    
    

html

  <input type="text" (ngModelChange)="SearchCategory($event)" placeholder="Search Category" class="form-control"/>

【讨论】:

【参考方案6】:

首先在视图中添加一个输入作为过滤器。将 keyup 事件绑定到 rxjs 主题

<input type="text" matInput placeholder="search" #filter (keyup)="keyEvent.next($event)" [(ngModel)]="keyword">

然后查询您的后端以使用关键字过滤树节点

this.keyEvent.pipe(
  map((e: any) => e.target.value.toLowerCase()),
  debounceTime(500),
  distinctUntilChanged(),
  switchMap((keyword: string) => 
    if (keyword && keyword.length > 2) 
      return this.yourservice.searchForData(this.entId, keyword);
     else 
      return of();
    
  )
)
.subscribe((r) => 
  this.nestedDataSource.data = r;
  this.nestedTreeControl.dataNodes = r;
  this.nestedTreeControl.expandAll();
);

【讨论】:

这只是将过滤卸载到后端。它不会在树本身(即前端)中执行任何过滤。

以上是关于如何过滤mat-tree组件Angular Material 6.0.1的主要内容,如果未能解决你的问题,请参考以下文章

为角材料的 <mat-select> 组件实现搜索过滤器

Angular 4 - 如何使垫卡填充整个父组件区域

如何一次过滤 mat 表数据源和相关的异步属性?

如何在同一组件中重用 Material Angular Datepicker

Angular - mat-tab 中的动态组件

如何在 Angular 子组件选择器之间传递内容