网站搜索

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

检查节点在有子节点和没有子节点时平面树视图的差异。