0

我想使用Angular 2 或 Angular 4从 json 文件创建层次结构。位置的层次结构可能会根据要求而改变。谁能建议我如何创建所需的模板。下面提到了可能的情况。

文件名:location.json

{
  "name" : "India",
  "children" : 
      {
         "name" : "Karnataka",
         "children" : 
             {
                "name" : "Banglore",
                "children" : 
                   [
                     {"val" : "silk"},
                     {"val" : "agara"}
                   ]                                         
             }
       }
},
 {
  "name" : "India",
  "children" : 
       {
         "name" : "goa",
         "children" : 
             [
                {"val" : "panji"},
                {"val" : "abc"}
             ]                                         
      }
}

情况1

<div> India
  <div> Karnataka
    <div> Banglore
      <div> silk </div>
      <div> agara </div>
    </div>
  </div> 
</div>
<div> India 
  <div> goa
    <div> panji</div>
    <div> abc</div>
   </div>
 </div>

4

2 回答 2

1

我正在分享用于设计层次结构的工作代码。

注意:编译hierarchy.component.scss需要node-sass

安装 node-sass : npm install node-sass


层次结构.component.ts

import { Component, OnInit, Input, OnChanges, SimpleChanges, SimpleChange, Output, EventEmitter } from '@angular/core';
import { Node } from './hierarchy.model';

@Component({
  selector: 'app-hierarchy',
  templateUrl: './hierarchy.component.html',
  styleUrls: ['./hierarchy.component.scss']
})
export class HierarchyComponent implements OnInit, OnChanges {
  @Input() name: string;
  @Input() data: Array<Node>;
  @Input() selectedNodeIds: Array<string> = [];
  @Output() selectedIds: EventEmitter<Array<string>> = new EventEmitter<Array<string>>();

  public hierarchyData: Array<Node>;
  public showHiearchy: boolean;
  private actingNode: string;


  constructor() {
    this.name = 'Hierarchy';
    this.showHiearchy = false;
    this.hierarchyData = new Array<Node>();
  }

  ngOnInit(): void {}

  // go to parent and set full or partial select. Their method.
  private setParentFullOrPartialSelected(isChecked: boolean, node: Node, parent: Node) {
    if (isChecked) {
      if (parent) {
        if (this.checkAllChildOfNodeSelected(parent)) {
          parent.allSelected = true;
          this.setParentFullOrPartialSelected(isChecked, parent, parent.parentRef);
        } else {
          this.removeAllSelected(parent);
        }
      }
    } else {
      this.removeAllSelected(node);
      this.setParentPartialSelected(node);
    }
  }

  private checkAllChildOfNodeSelected(parentNode: Node): boolean {
    const nodes: Array<Node> = parentNode.children;
    let condition = true;
    (Array.isArray(nodes)) ? condition = nodes.every((node: Node) => this.isAllSelected(node)) : null;
    return condition;
  }

  public isAllSelected(node: Node): boolean {
    const condition = node.allSelected ? node.allSelected : true;
    return condition && (node.allSelected);
  }

  private removeAllSelected(node: Node) {
    node.allSelected ? node.allSelected = false : null;
  }

  private setParentPartialSelected(node: Node) {
    if (node.parentRef) {
      node.parentRef.allSelected ? node.parentRef.allSelected = false : null;
      this.setParentPartialSelected(node.parentRef);
    }
  }


  // Update parents and their method
  private updateParents(isChecked: boolean, node: Node) {
    const parent = node ? node.parentRef : null;
    if (parent) {
      if (isChecked) {
        parent.checked = isChecked;
        this.updateParents(isChecked, parent);
      } else {
        if (!this.isSomeChildOfNodeSelected(parent)) {
          parent.checked = isChecked;
          if (this.isSelected(parent)) {
            this.removeSelectedNodeId(parent);
          }
          this.updateParents(isChecked, parent);
        }
      }
    }
  }

  private isSomeChildOfNodeSelected(parentNode: Node): boolean {
    const nodes: Array<Node> = parentNode.children;
    let condition = false;
    (Array.isArray(nodes)) ? condition = nodes.some((node: Node) => node.checked) : null;
    return condition;
  }

  public checkboxChanged(isChecked: boolean, node: Node, parent: Node = null) {
    node.checked = isChecked;
    node.parentRef = parent;
    this.actingNode = node.id;
    node.allSelected = isChecked;
    // go to parent and set full or partial select.
    this.setParentFullOrPartialSelected(isChecked, node, parent);
    // go to parent and make them checked or unchecked.
    this.updateParents(isChecked, node);
    // go to child and select them or de-select.
    this.updateChildrens(isChecked, node);
    this.updateSelectedNodeIds(isChecked, node);
    this.selectedIds.emit(this.selectedNodeIds);
  }


  private updateChildrens(isChecked: boolean, node: Node) {
    const childNodes: Array<Node> = node.children;
    if (childNodes) {
      // tslint:disable-next-line: prefer-for-of
      for (let i = 0; i < childNodes.length; i++) {
          childNodes[i].checked = isChecked;
          childNodes[i].allSelected = isChecked;
          if (isChecked) {
            this.updateSelectedNodeIds(isChecked, childNodes[i]);
          } else {
            this.removeSelectedNodeId(childNodes[i]);
          }
          this.updateChildrens(isChecked, childNodes[i]);   // use recursion to update childrens within children.
      }
    }
  }

  private updateSelectedNodeIds(isChecked: boolean, node: Node) {
    if (isChecked) {
      if (!this.isSelected(node)) {
        this.selectedNodeIds.push(node.id);
      }
    } else {
      this.removeSelectedNodeId(node);
    }
  }

  private removeSelectedNodeId(node: Node) {
    if (this.isSelected(node)) {
      const index = this.selectedNodeIds.indexOf(node.id);
      this.selectedNodeIds.splice(index, 1);
    }
  }

  public isPartiallySelected(node: Node) {
      return node.checked && !node.allSelected;
  }

  public isSelected(node: Node): boolean {
    if (this.selectedNodeIds) {
      return this.selectedNodeIds.indexOf(node.id) > -1;
    }
    return false;
  }

  public toggleHierarchy() {
    this.showHiearchy = !this.showHiearchy;
  }

  public expandNode(node: Node) {
    node.expanded = !node.expanded;
  }

  /**
   *  For each node it will set referance to parent node and also ensure to initilize the variables.
   * @param nodes List of node.
   * @param parentNode parent of node.
   */
  public setNodeDefault(nodes: Array<Node>, parentNode: Node = null) {
    nodes.forEach( (node: Node) => {
      node.allSelected = false;
      node.checked = false;
      node.expanded = true;
      node.parentRef = parentNode;
      if (node.children) {
        this.setNodeDefault(node.children, node);
      }
    });
  }

  ngOnChanges(changes: SimpleChanges) {
    const dataChange: SimpleChange = changes['data'];

    if (dataChange && dataChange.currentValue) {
      this.hierarchyData = new Array<Node>( new Node('all', 'Select All', this.data));
      // If select all is not required, then comment above line and use below line code
      //  this.hierarchyData = this.data;
      this.setNodeDefault(this.hierarchyData);
    }
  }

}

层次结构.component.html

<div class="flex-inline-col dropdown">
    <div class="dropdown__btn" (click)=toggleHierarchy()>{{name}}</div>
    <div class="dropdown__content" [class.toggle-drop-down]="showHiearchy">
        <div class="dropdown__search">
            <input type="text" class="dropdown__search" autofocus>
        </div>
        <ul class="dropdown__hierarchy">
            <li class="dropdown__hierarchy__list">
                <ul class="hierarchy">
                    <ng-container *ngTemplateOutlet="hierarchyNode; context: {$implicit: hierarchyData, level: 'first'}"></ng-container>
                </ul>
            </li>
        </ul>
    </div>
</div>

<ng-template #hierarchyNode let-hierarchyData let-level="level" let-parent="parent">
    <li *ngFor="let node of hierarchyData" [class.last-children]="!node.children"
        class="flex-col hierarchy__item">
        <div>
            <div *ngIf="node.children" class="flex-inline hierarchy__item__icon" (click)="expandNode(node)">
                <svg *ngIf="!node.expanded" version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
                    <title>circle-right</title>
                    <path d="M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM16 29c-7.18 0-13-5.82-13-13s5.82-13 13-13 13 5.82 13 13-5.82 13-13 13z"></path>
                    <path d="M11.086 22.086l2.829 2.829 8.914-8.914-8.914-8.914-2.828 2.828 6.086 6.086z"></path>
                </svg>
                <svg *ngIf="node.expanded"  version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
                    <title>circle-down</title>
                    <path d="M32 16c0-8.837-7.163-16-16-16s-16 7.163-16 16 7.163 16 16 16 16-7.163 16-16zM3 16c0-7.18 5.82-13 13-13s13 5.82 13 13-5.82 13-13 13-13-5.82-13-13z"></path>
                    <path d="M9.914 11.086l-2.829 2.829 8.914 8.914 8.914-8.914-2.828-2.828-6.086 6.086z"></path>
                </svg>
            </div>
            <input type="checkbox" id="check_{{node.id}}"
                class="flex-inline hierarchy__item__checkbox"
                [checked]="node.checked || isSelected(node)"
                (change)="checkboxChanged($event.target.checked, node, parent)">
            <label for="check_{{node.id}}" class="flex-inline hierarchy__item__label">
                <span class="hierarchy__item__icon hierarchy__item__circle" *ngIf="!node.checked">
                    <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
                        <title>radio-unchecked</title>
                        <path d="M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM16 28c-6.627 0-12-5.373-12-12s5.373-12 12-12c6.627 0 12 5.373 12 12s-5.373 12-12 12z"></path>
                    </svg>
                </span>
                <span class="hierarchy__item__icon hierarchy__item__circle-checked" *ngIf="node.checked && node.allSelected">
                    <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
                        <title>checkmark</title>
                        <path d="M21.82 13.030l-1.002-1.002c-0.185-0.185-0.484-0.185-0.668 0l-6.014 6.013-2.859-2.882c-0.186-0.185-0.484-0.185-0.67 0l-1.002 1.003c-0.185 0.185-0.185 0.484 0 0.668l4.193 4.223c0.185 0.184 0.484 0.184 0.668 0l7.354-7.354c0.186-0.185 0.186-0.484 0-0.669zM16 3c-7.18 0-13 5.82-13 13s5.82 13 13 13 13-5.82 13-13-5.82-13-13-13zM16 26c-5.522 0-10-4.478-10-10 0-5.523 4.478-10 10-10 5.523 0 10 4.477 10 10 0 5.522-4.477 10-10 10z"></path>
                    </svg>
                </span>
                <span class="hierarchy__item__icon hierarchy__item__circle-minus" *ngIf="node.checked && !node.allSelected">
                    <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="28" viewBox="0 0 24 28">
                        <title>minus-circle</title>
                        <path d="M19 15v-2c0-0.547-0.453-1-1-1h-12c-0.547 0-1 0.453-1 1v2c0 0.547 0.453 1 1 1h12c0.547 0 1-0.453 1-1zM24 14c0 6.625-5.375 12-12 12s-12-5.375-12-12 5.375-12 12-12 12 5.375 12 12z"></path>
                    </svg>
                </span>
                {{node.name}}
            </label>
                
        </div>
        <ul *ngIf="node.children" class="hierarchy" [style.display]="!node.expanded ? 'none' : 'block'">
            <ng-container *ngTemplateOutlet="hierarchyNode; context: {$implicit: node.children, level: 'second', parent: node}"></ng-container>
        </ul>
    </li>
</ng-template>

层次结构.component.scss

* {
    &,
    &:before,
    &:after {
    box-sizing: border-box;
    margin: 0px;
    padding: 0px;
    }
}
.flex {  
    display: flex;
}

.flex-col {  
    display: flex;
    flex-direction: column;
}

.flex-inline {  
    display: inline-flex;
}

.flex-inline-col {  
    display: inline-flex;
    flex-direction: column;
}

.absCenter {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}



.dropdown {
    width: 300px;
    position: relative;

    &__btn {
        background-color: #fff;
        color: #000;
        box-shadow: 4px 4px 5px rgba(0, 0, 0, 0.4);
        height: 35px;
        padding: 10px;
        font-size: 14px;
        text-align: center;
        text-transform: UPPERCASE;
        cursor: pointer;

        &:after {
            content: "";
            width: 0; height: 0; position: absolute; right: 5px; top:45%;
            border-top: 5px solid #000;
            border-left: 5px solid transparent;
            border-right: 5px solid transparent;
        }

        &:active {
            transform: translateY(2px);
            box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2)
        }
    }

    &__content {
        display: none;
        position: absolute;
        top: 38px;
        left: 0px;
        width: 100%;
        z-index: 99;
        background: #fff;
        box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.4);
        max-height: 300px;
        overflow: hidden;
    }

    &__search {
        width: 100%;
        height: 30px;
        border-bottom: 1px solid #e0e0e0;
    }

    &__hierarchy {
        list-style: none;
        max-height: 270px;
        overflow: auto;

        &__list {
            margin-top: 5px;
        }
    }
}

.toggle-drop-down {
    display: block;
}

svg {
    width: 20px !important;
    height: 20px !important;
}

.hierarchy {
    margin-left: 10px;

    &__item {
        padding: 5px 0px;

        &__icon {
            width: 20px;
            margin-right: 10px;
        }

        &__label {
            padding-left: 10px;
        }

        &__checkbox {
            align-self: center;
            display: none;
        }
    }
}

.last-children {
    margin-left: 35px;
}

层次结构.model.ts

mockData 用于测试目的。应用程序正常运行后,删除此 mockData。

export class Node {
    constructor(
        public id: string,
        public name: string,
        public children?: Array<Node>,
        public allSelected?: boolean,
        public expanded?: boolean,
        public checked?: boolean,
        public parentRef?: Node,
    ) { }
}

export const mockData: Array<Node> = new Array<Node>(
    new Node('1000', 'First level 1', [
        new Node('1100', 'Second level 11', [
            new Node('1110', 'Third level 111', [
                new Node('1111', 'Fourth level 1111'),
                new Node('1112', 'Fourth level 1112'),
                new Node('1113', 'Fourth level 1113'),
                new Node('1114', 'Fourth level 1114')
            ]),
            new Node('1120', 'Third level 112', [
                new Node('1121', 'Fourth level 1121'),
                new Node('1122', 'Fourth level 1122'),
                new Node('1123', 'Fourth level 1123'),
                new Node('1124', 'Fourth level 1124')
            ])
        ]),
        new Node('1200', 'Second level 12', [
            new Node('1210', 'Third level 121', [
                new Node('1211', 'Fourth level 1211'),
                new Node('1212', 'Fourth level 1212'),
                new Node('1213', 'Fourth level 1213'),
                new Node('1214', 'Fourth level 1214')
            ]),
            new Node('1220', 'Third level 122', [
                new Node('1221', 'Fourth level 1221'),
                new Node('1222', 'Fourth level 1222'),
                new Node('1223', 'Fourth level 1223'),
                new Node('1224', 'Fourth level 1224')
            ])
        ])
    ]),
    new Node('2000', 'First level 2', [
        new Node('2100', 'Second level 21', [
            new Node('2110', 'Third level 211', [
                new Node('2111', 'Fourth level 2111'),
                new Node('2112', 'Fourth level 2112'),
                new Node('2113', 'Fourth level 2113'),
                new Node('2114', 'Fourth level 2114')
            ]),
            new Node('2120', 'Third level 212', [
                new Node('2121', 'Fourth level 2121'),
                new Node('2122', 'Fourth level 2122'),
                new Node('2123', 'Fourth level 2123'),
                new Node('2124', 'Fourth level 2124')
            ])
        ]),
        new Node('2200', 'Second level 22', [
            new Node('2210', 'Third level 221', [
                new Node('2211', 'Fourth level 2211'),
                new Node('2212', 'Fourth level 2212'),
                new Node('2213', 'Fourth level 2213'),
                new Node('2214', 'Fourth level 2214')
            ]),
            new Node('2220', 'Third level 222', [
                new Node('2221', 'Fourth level 2221'),
                new Node('2222', 'Fourth level 2222'),
                new Node('2223', 'Fourth level 2223'),
                new Node('2224', 'Fourth level 2224')
            ])
        ])
    ])
);

app.component.ts

import { Component } from '@angular/core';
import { Node, mockData } from './hierarchy/hierarchy.model';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  public hierarchyData: Array<Node>;
  public selectedNodeIds: Array<string> = [];
  public selectedIds: Array<string> = [];


  constructor() {
    this.hierarchyData = mockData;
  }

  public changedSelectedIds(ids: Array<string>) {
    this.selectedIds = ids;
  }
}

app.component.html

<div class="container">
    <app-hierarchy name="Hierarchy" 
        [data]="hierarchyData" 
        [selectedNodeIds]="selectedNodeIds"
        (selectedIds)="changedSelectedIds($event)">
    </app-hierarchy>
</div>

输出

在此处输入图像描述

在此处输入图像描述

于 2020-05-10T11:15:16.960 回答
0

第一种情况应该相对简单,因为它与您的数据结构相匹配。我首先将一个变量添加到保存数据返回的 .ts 文件中。类似的东西countryData。您希望在模板中添加类似以下内容。

<ul class="country">
  <li>{{countryData.name}}</li>
  <ul class="state">
    <li>{{countryData.children.name}}</li>
      <ul class="city">
        <li>{{countryData.children.children.name}}</li>
          <ul class="items">
            <li *ngFor="let item of countryData.children.children.loc">{{item.val}}</li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>
</ul>

我在顶级无序列表下添加了列表项,以确保输出是有效的 HTML。每次需要遍历对象时,模板都会有点混乱,因此在您的 .ts 中,您可以为较低的项目设置其他变量。

let countryData = [JSON call response or data here];
let stateData = countryData.children;
let cityData = countryData.children.children;
let itemData = countryData.children.children.loc;

然后您的模板将如下所示。

<ul class="country">
  <li>{{countryData.name}}</li>
  <ul class="state">
    <li>{{stateData.name}}</li>
      <ul class="city">
        <li>{{cityData.name}}</li>
          <ul class="items">
            <li *ngFor="let item of itemData">{{item.val}}</li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>
</ul>

对于案例 2,您只需要离开城市级别的无序列表并留在项目级别。对于案例 3,您需要在城市级别有一个数组,因此您需要*ngFor="let city of cityData在城市级别列表项上添加类似的内容,并调整城市和项目模板变量以引用{{city.name}}let item of city.loc以便它全部正确继承为你循环通过。

于 2018-01-17T13:19:40.553 回答