1

在我的家庭项目中,我尝试在树层次结构中显示数据。我向 API 发送 get 调用,我收到了这个 json:

{"items":[{"id":"1","name":"Adam","coWorkerId": 22 },{"id":"2","name":"Cris","age":32, "coWorkerId": 33}]}

我想在下面显示类似树的东西:

在此处输入图像描述

然后,当我单击例如 Adam 时,我需要发送 id 为 22 的 api 请求,因为"coWorkerId": 22 可能还有更多 coWorkerIds:例如:

{"items":[{"id":"22","name":"Jean","coWorkerId": 44 },{"id":"12","name":"Noah","age":32, "coWorkerId": 55}]}

在此处输入图像描述

我想按需将这些数据加载到树层次结构中。我认为每次点击都应该产生 api 调用。当然,Everey 的另一个调用可能不是 las,因为我的节点可以拥有更多的子节点。

所以在html中我尝试了:

<ng-container *ngTemplateOutlet="treeViewList; 
                                 context:{$implicit:users}">
</ng-container>

<ng-template #treeViewList let-list>
    <ul>
        <li *ngFor="let user of list; let i = index">
            <button (click)="onClick(coWorkerId.id)"> {{user.name}}</button>
        </li>
    </ul>
</ng-template>

数据服务.ts


@Injectable({
  providedIn: 'root'
})
export class DataService {

  constructor(private httpClient: HttpClient) { }

  public getUsers(): Observable<UserResult> {
    return this.httpClient.get<UserResult>(baseUrl);
  }

    public getUsers(id): Observable<UserResult>{
    return this.httpClient.get<UserResult>(leafUsersUrl + id);
  }
}

组件.ts

export class SidebarComponent implements OnInit {
  users: Users[];
  coWorkerId: String;

  constructor(private dataService: DataService) { }

  ngOnInit(): void {
     this.getRootUsers();
  }

  onClick(coWorkerId: String) {
    this.coWorkerId = coWorkerId;
    this.getLeafUsers(this.coWorkerId);
  }

  private getRootUsers() {
    this.dataService.getRootUsers().subscribe(
      data => {
        this.users = data.items;
      }
    )
  }

  private getLeafUsers(id: String) {
    this.dataService.getUsers(id).subscribe(
      data => {
        this.users = data.items;
      }
    );
  }
}

我得到了列表而不是层次结构

-Adam
-Cris 

当我点击 Adam 时,整个列表变为:

-Jean
-Noah

而不是创建层次结构。我正在考虑使用import {MatTreeModule} from '@angular/material/tree';. 任何想法如何完成这棵树?

4

1 回答 1

0

这是因为您正在覆盖您的用户列表,而不是添加新用户。一个非常简单的解决方案是向用户添加一个“儿童”字段,然后执行类似的操作

<ul>
  <li *ngFor="let user of list">
      <button (click)="onClick(user)"> {{user.name}}</button>
      <ul *ngIf="user.children != null">
          <li *ngFor="let child of user.children">{{child.name}}</li>
      </ul>
  </li>
</ul>

onClick 将加载的用户添加到子字段的位置

onClick(user) {
    this.dataService.getUsers(user.id).subscribe(x => user.children = x);
}

如果您还添加一个“扩展”字段,然后基于该字段展开/折叠子节点,您将拥有一个简单的功能树。

这是一个非常简单的解决方案,但并不理想。您使用 mat-tree 的想法很好,但它非常复杂。

你需要定义你的树节点

export class StackOverflowNode {
  constructor(public item: User, public level = 1, public expandable = false,
              public isLoading = false) {}
}

定义您的数据源

export class StackOverflowDataSource implements DataSource<StackOverflowNode> {
  dataChange = new BehaviorSubject<StackOverflowNode[]>([]);

  get data(): StackOverflowNode[] { return this.dataChange.value; }
  set data(value: StackOverflowNode[]) {
    this._treeControl.dataNodes = value;
    this.dataChange.next(value);
  }

 constructor(private _treeControl: FlatTreeControl<StackOverflowNode>,
              private _service: UserService) {}

 connect(collectionViewer: CollectionViewer): Observable<StackOverflowNode[]> {
    this._treeControl.expansionModel.changed.subscribe(change => {
      if ((change as SelectionChange<StackOverflowNode>).added ||
        (change as SelectionChange<StackOverflowNode>).removed) {
        this.handleTreeControl(change as SelectionChange<StackOverflowNode>);
      }
    });

    return merge(collectionViewer.viewChange, this.dataChange).pipe(map(() => this.data));
  }

  disconnect(collectionViewer: CollectionViewer): void {}

  /** Handle expand/collapse behaviors */
  handleTreeControl(change: SelectionChange<StackOverflowNode>) {
    if (change.added) {
      change.added.forEach(node => this.toggleNode(node, true));
    }
    if (change.removed) {
      change.removed.slice().reverse().forEach(node => this.toggleNode(node, false));
    }
  }

  /**
   * Toggle the node, remove from display list
   */
  toggleNode(node: StackOverflowNode, expand: boolean) {
    node.isLoading = true;

    this._service.getUsers(node.item.id).subscribe(children => {
      const index = this.data.indexOf(node);
      if (!children || index < 0) { // If no children, or cannot find the node, no op
        return;
      }

      if (expand) {
        const nodes = children.map(child =>
          new StackOverflowNode(child, node.level + 1, false));
        this.data.splice(index + 1, 0, ...nodes);
      } else {
        let count = 0;
        for (let i = index + 1; i < this.data.length
          && this.data[i].level > node.level; i++, count++) {}
         this.data.splice(index + 1, count);
      }

      // notify the change
      this.dataChange.next(this.data);
      node.isLoading = false;      
    });

  }

并在您的组件中使用它

@Component({
  selector: 'app-sidebar',
  templateUrl: './sidebar.component.html',
  styleUrls: ['./sidebar.component.scss']
})
export class SidebarComponent {

  treeControl: FlatTreeControl<StackOverflowNode>;

  dataSource: StackOverflowDataSource;

  getLevel = (node: StackOverflowNode) => node.level;

  isExpandable = (node: StackOverflowNode) => node.expandable;

  hasChild = (_: number, _nodeData: StackOverflowNode) => _nodeData.expandable

  constructor(private dataService: DataService) { 
    this.treeControl = new FlatTreeControl<StackOverflowNode>(this.getLevel, this.isExpandable);
    this.dataSource = new StackOverflowDataSource(this.treeControl, this.userService);

    this.dataService.getUsers().subscribe(x => this.dataSource.data  = x.map(data => new StackOverflowNode(data,0,true,false)))

  }


}

并在您的 html 中使用它

<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
    <mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
      <button mat-icon-button disabled></button>
      {{node.item.name}}
    </mat-tree-node>
    <mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
      <button mat-icon-button
              [attr.aria-label]="'toggle ' + node.filename" matTreeNodeToggle>
        <mat-icon class="mat-icon-rtl-mirror">
          {{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
        </mat-icon>
      </button>
      {{node.item.name}}
      <mat-progress-bar *ngIf="node.isLoading"
                        mode="indeterminate"
                        class="example-tree-progress-bar"></mat-progress-bar>
    </mat-tree-node>
  </mat-tree>

这已针对来自https://material.angular.io/components/tree/examples的数据源进行了调整。您可以研究该页面以了解此代码的工作原理。请注意,为了简单起见,我假设您的列表只有 2 个级别 - 而不是实际检查一个节点是否有子节点,我自动假设 0 级节点有而 1 级节点没有。

于 2020-06-20T22:01:05.290 回答