我正在以Angular 创建这个递归嵌套的树视图组件.目标是,无论何时 Select 一个 node ,都应该将样式应用于具有.node-active类的该 node ,而当 Select 其他 node 时,应移除先前应用的样式,并将其添加到当前选定的 node .它对一个子树工作得很好,但当我从其他子树中 Select 其他 node 时,样式也会应用到其他子树.这是Stackblitz LINK-Stackblitz

tree-view component

我正在使用基于当前 node 代码和所选 node 代码的applyStyle函数应用一个类,它对一个子树有效,但对另一个子树无效.任何帮助都是非常有帮助的.

Tree.component.ts

import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
  selector: 'app-tree',
  templateUrl: './Tree.component.html',
  styleUrls: ['./Tree.component.css'],
})
export class TreeComponent {
  @Input() tree: any;
  @Output() selectedValue = new EventEmitter<any>();
  public selectedItem: any;

  toggleChild(node) {
    this.selectedValue.emit(node);
    node.showChildren = !node.showChildren;
    node.isOpen = !node.isOpen;
  }

  /* Events are not bubbled up so emitting the parent event on <app-tree>
   * when one of the child emits an event - this will create a new EventEmitter per child.
   */
  emitOnChildClicked(node) {
    this.selectedValue.emit(node);
  }

  applyStyle(node) {
    console.log(node);
    this.selectedItem = node.code;
  }
}

Tree.component.css

.open-btn {
  transform: rotate(90deg);
  position: absolute;
  left: -30px;
  z-index: 10;
}

.close-btn {
  left: -30px;
  position: absolute;
  z-index: 10;
}

.tree li {
  list-style-type: none;
  margin: 8px;
  position: relative;
}

.node-item {
  color: var(--lbl-color);
  padding: 3px;
}

.green {
  background-color: darkred;
}

.node-active {
  pointer-events: none;
  background: #379df1;
  border-radius: 5px;
  color: white;
}

.node-item:hover {
  background-color: rgb(231, 231, 231);
  color: #0f0f0f;
  border-radius: 5px;
}

.arrow-btn {
  width: 18px;
  height: 18px;
}

.tree li::before {
  content: '';
  position: absolute;
  top: -7px;
  left: -20px;
  border-left: 1px solid #ccc;
  border-bottom: 1px solid #ccc;
  border-radius: 0 0 0 0px;
  width: 20px;
  height: 15px;
}

.tree li::after {
  position: absolute;
  content: '';
  top: 8px;
  left: -20px;
  border-left: 1px solid #ccc;
  border-top: 1px solid #ccc;
  border-radius: 0px 0 0 0;
  width: 20px;
  height: 100%;
}

.tree li:last-child::after {
  display: none;
}

.tree li:last-child:before {
  border-radius: 0 0 0 5px;
}

ul.tree > li:first-child::before {
  border-left: 1px solid #ccc;
  border-bottom: 1px solid #ccc;
}

.label-container {
  display: inline-block;
}

Tree.component.html

<ul *ngIf="tree" class="tree">
  <li *ngFor="let node of tree; let i = index">
    <div class="label-container" (click)="toggleChild(node)">
      <span *ngIf="node.children != 0">
        <img
          src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/9d/Icons8_flat_document.svg/512px-Icons8_flat_document.svg.png"
          class="arrow-btn"
          [ngClass]="node.isOpen ? 'open-btn' : 'close-btn'"
        />
      </span>
      <span
        class="node-item"
        (click)="applyStyle(node)"
        [ngClass]="{ 'node-active': selectedItem == node.code }"
      >
        {{ node.name }}
      </span>
    </div>
    <app-tree
      *ngIf="node.showChildren"
      (selectedValue)="emitOnChildClicked($event)"
      [tree]="node.children"
    ></app-tree>
  </li>
</ul>

Tree.mock.ts

export interface TreeNode {
  name: string;
  showChildren: boolean;
  children: any[];
  code: string;
}

export const NODES: TreeNode[] = [
  {
    code: 'AFRI',
    name: 'Africa',
    showChildren: false,
    children: [
      {
        code: 'ALGE',
        name: 'Algeria',
        showChildren: false,
        children: [
          {
            code: 'ARIS',
            name: 'Algeris',
            showChildren: false,
            children: [],
          },
          {
            code: 'ACI2',
            name: 'Algeria child 2',
            showChildren: false,
            children: [],
          },
        ],
      },
      {
        code: 'ANGO',
        name: 'Angola',
        showChildren: false,
        children: [],
      },
      {
        code: 'BENI',
        name: 'Benin',
        showChildren: false,
        children: [],
      },
    ],
  },
  {
    code: 'ASIA',
    name: 'Asia',
    showChildren: false,
    children: [
      {
        code: 'AFGH',
        name: 'Afghanistan',
        showChildren: false,
        children: [
          {
            code: 'KABU',
            name: 'Kabul',
            showChildren: false,
            children: [],
          },
        ],
      },
      {
        code: 'ARME',
        name: 'Armenia',
        showChildren: false,
        children: [],
      },
      {
        code: 'AZER',
        name: 'Azerbaijan',
        showChildren: false,
        children: [],
      },
    ],
  },
  {
    code: 'EURO',
    name: 'Europe',
    showChildren: false,
    children: [
      {
        code: 'ROMA',
        name: 'Romania',
        showChildren: false,
        children: [
          {
            code: 'BUCU',
            name: 'Bucuresti',
            showChildren: false,
            children: [],
          },
        ],
      },
      {
        code: 'HUNG',
        name: 'Hungary',
        showChildren: false,
        children: [],
      },
      {
        code: 'BNIN',
        name: 'Benin',
        showChildren: false,
        children: [],
      },
    ],
  },
  {
    code: 'NOAM',
    name: 'North America',
    showChildren: false,
    children: [],
  },
];

推荐答案

最简单的解决方案是只向所有子(可选)元素添加一个新属性,比如active,并将其初始设置为false,然后无论何时单击某个元素:将所有元素设置为false,将所选元素设置为true.

这意味着你需要把它加到你的TreeNode型号上:

active?: boolean;

(在这里作为可选添加,因为在后面的代码中,您不会将值manually添加到所有 node ,而是以编程方式添加).

然后,Tree组件的模板部分只需要一次简单的更改: 从[ngClass]="{ 'node-active': selectedItem == node.code }"[ngClass]="node.active ? 'node-active' : ''".因此,子对象的CSS样式将不依赖于selectedItem,而取决于它们自己的active属性值.

最后,需要对组件的ts/logic部分进行大部分更改:

创建一个递归迭代tree的方法,并将所有active个属性设置为false:

setAllToFalse(nodes) {
   for (let child of nodes) {
     child.active = false;
     if (child.children && Array.isArray(child.children) && child.children.length > 0) this.setAllToFalse(child.children);
   }
}

将ngOnInit添加到组件中,并在其内部调用此递归方法,以使该特定树的所有 node 的active最初设置为False:

ngOnInit() {
   this.setAllToFalse(this.tree);
}

更改您的applyStyle方法并调用setAllToFalse将所有值设置为false,然后将此特定 node 的active设置为true:

applyStyle(node) {
  this.selectedItem = node.code;
  this.setAllToFalse(this.tree);
  node.active = true;
}

最后,由于您还在模板中使用递归,因此还需要编辑您的emitOnChildClicked方法,因为组件的上下文中的tree可以表示‘原始’树中的任何子树:

emitOnChildClicked(node) {
  this.selectedValue.emit(node);
  this.setAllToFalse(this.tree);
  node.active = true;
}

工作Stackblitz个例子.

Css相关问答推荐

Vue3日期 Select 器:禁用向左和向右箭头

ReactJS 连续循环动画滚动

如何将CSS动画从第一列移动到第二列?

如何使用 Cypress 从 React 下拉列表中进行 Select

使用 CSS 关键帧动画更改内容

如何在不使用 !important 的情况下提高优先级?

在 React Native 中创建背景

CSS网格使sibling 项目拉伸

基于变量动态生成 CSS 类 - SCSS

使用 CSS 剪辑/裁剪背景图像

弹性盒项目之间的间距

将一行文本保留为单行 - 换行或不换行

如何将 textarea 宽度扩展到父级宽度的 100%(或如何将任何 HTML 元素扩展到父级宽度的 100%)?

如何保持标题静态,滚动时始终位于顶部?

溢出的文本可以居中吗?

将未知大小的大图像居中在一个较小的 div 中,并隐藏溢出

如何对齐标签和文本区域?

CSS:悬停一个元素,多个元素的效果?

Rails 中正确的 SCSS assets资源 struct

使用css调整div中背景图像的大小