我如何创建一个嵌套的递归 table 可以在 Angular 中向下钻取 x 个级别

how can I create a nested, recursive table that can drill down x levels in Angular

我需要用嵌套子任务的数据制作一个 table。我需要使这个 table 递归。有很多关于 ngFor 递归显示数据的文献,但不是 table 格式。有很多事情使 tables 更难使用(例如,在 td 标签之外使用 div 标签等)

这里是我到目前为止得到的堆栈闪电战。

https://stackblitz.com/edit/angular-xk9nw6?file=src%2Fapp%2Ftable%2Ftable.component.html

如您所见,在遇到 ngFor 问题之前,我只能向下钻取一个级别

<H1 style="text-indent: 10px">Table</H1>

<style>
table{
  background:rgb(223, 221, 221);
}
</style>

<table  class="table">
  <thead>
  <tr>
    <th>expand</th>
    <th>task id</th>
    <th>task name</th>
    <th>button</th>
  </tr>
</thead>  
<tbody *ngFor="let datum of data; index as i">
  <tr>
      <td class="" (click)="task(datum, i); $event.stopPropagation()" > 
        <i [ngClass]="(index != i)?'fas fa-plus':'fas fa-minus'"></i> 
      </td>
      <td class="">{{datum.taskID}}</td>
      <td class="">{{datum.taskName}}</td>
      <td ALIGN="center">

        <!-- <i class="fas fa-ellipsis-h" data-toggle="dropdown"></i> -->


          <select>        
               <option (click)="dropdown(datum)" value="add">Add</option>
               <option (click)="dropdown(datum)" value="edit">Edit</option>
               <option (click)="dropdown(datum)" value="delete">Delete</option>
          </select>

        </td> 
  </tr>
  <tr [hidden]="index != i" *ngFor="let subtask of datum.subtasks; index as j" >
    <div [hidden]="!subtask.subtasks">
      <td class="" style="text-indent: 15px" (click)="subtaskQ(subtask, j)"> <i class="fas fa-plus"></i> </td>
    </div>
    <div [hidden]="subtask.subtasks">
        <td class="" style="text-indent: 15px" (click)="subtaskQ(subtask, j)"></td>
    </div>
      <td class="">{{subtask?.taskID}}</td>
      <td class="">{{subtask?.taskName}}</td>
      <td ALIGN="center">
          <select>        
               <option (click)="dropdown(subtask)" value="add">Add</option>
               <option (click)="dropdown(subtask)" value="edit">Edit</option>
               <option (click)="dropdown(subtask)" value="delete">Delete</option>
          </select>
        </td>
  </tr>
  <tr [hidden]="index != i && index !=j" *ngFor="let subtaskL of subtask; index as e" >

        <td class="" style="text-indent: 15px" (click)="subtaskQ(subtask, j)"></td>
        <td class="">{{subtaskL?.taskID}}</td>
        <td class="">{{subtaskL?.taskName}}</td>
        <td ALIGN="center">
            <select>        
                 <option (click)="dropdown(subtask)" value="add">Add</option>
                 <option (click)="dropdown(subtask)" value="edit">Edit</option>
                 <option (click)="dropdown(subtask)" value="delete">Delete</option>
            </select>
          </td>
    </tr>
</tbody> 



</table>

分量

import { Component, OnInit,ViewChild } from '@angular/core';
import { sampleData } from '../datasource';

@Component({
  selector: 'app-table',
  templateUrl: './table.component.html',
  styleUrls: ['./table.component.css']
})
export class TableComponent implements OnInit {

  constructor() { }

  @ViewChild('treegrid')
  public data: Object[];
  public foo: boolean;
  public subtaskArray: any;
  public subSubtaskArray: any;
  public index: number;

  dropdown(e){
    console.log(e);
  }

  task(e, i){
    console.log(e);

    this.subtaskArray = e.subtasks
    this.index = i;
  }

  subtaskQ(e, j){
    this.subSubtaskArray = e.subtasks;
    console.log(j);
    console.log(e);

  }

  switch(){
    if(this.foo){
      this.foo =false;
    } else {
    this.foo = true;
    }
  }

  ngOnInit(): void {
    this.data = sampleData;
}
}

示例数据

/**
 * Test cases data source
 */
export let sampleData: Object[] = [
    {
        taskID: 1,
        taskName: 'Planning',
        startDate: new Date('02/03/2017'),
        endDate: new Date('02/07/2017'),
        progress: 100,
        duration: 5,
        priority: 'Normal',
        approved: false,
        isInExpandState: true,
        subtasks: [
            { taskID: 2, taskName: 'Plan timeline', startDate: new Date('02/03/2017'), endDate: new Date('02/07/2017'), duration: 5, progress: 100, priority: 'Normal', approved: false },
            { taskID: 3, taskName: 'Plan budget', startDate: new Date('02/03/2017'), endDate: new Date('02/07/2017'), duration: 5, progress: 100, approved: true },
            { taskID: 4, taskName: 'Allocate resources', startDate: new Date('02/03/2017'), endDate: new Date('02/07/2017'), duration: 5, progress: 100, priority: 'Critical', approved: false },
            { taskID: 5, taskName: 'Planning complete', startDate: new Date('02/07/2017'), endDate: new Date('02/07/2017'), duration: 0, progress: 0, priority: 'Low', approved: true }
        ]
    },
    {
        taskID: 6,
        taskName: 'Design',
        startDate: new Date('02/10/2017'),
        endDate: new Date('02/14/2017'),
        duration: 3,
        progress: 86,
        priority: 'High',
        isInExpandState: false,
        approved: false,
        subtasks: [
            { taskID: 7, taskName: 'Software Specification', startDate: new Date('02/10/2017'), endDate: new Date('02/12/2017'), duration: 3, progress: 60, priority: 'Normal', approved: false },
            { taskID: 8, taskName: 'Develop prototype', startDate: new Date('02/10/2017'), endDate: new Date('02/12/2017'), duration: 3, progress: 100, priority: 'Critical', approved: false },
            { taskID: 9, taskName: 'Get approval from customer', startDate: new Date('02/13/2017'), endDate: new Date('02/14/2017'), duration: 2, progress: 100, approved: true },
            { taskID: 10, taskName: 'Design Documentation', startDate: new Date('02/13/2017'), endDate: new Date('02/14/2017'), duration: 2, progress: 100, approved: true },
            { taskID: 11, taskName: 'Design complete', startDate: new Date('02/14/2017'), endDate: new Date('02/14/2017'), duration: 0, progress: 0, priority: 'Normal', approved: true }
        ]
    },
    {
        taskID: 12,
        taskName: 'Implementation Phase',
        startDate: new Date('02/17/2017'),
        endDate: new Date('02/27/2017'),
        priority: 'Normal',
        approved: false,
        duration: 11,
        subtasks: [
            {
                taskID: 13,
                taskName: 'Phase 1',
                startDate: new Date('02/17/2017'),
                endDate: new Date('02/27/2017'),
                priority: 'High',
                approved: false,
                duration: 11,
                subtasks: [{
                    taskID: 14,
                    taskName: 'Implementation Module 1',
                    startDate: new Date('02/17/2017'),
                    endDate: new Date('02/27/2017'),
                    priority: 'Normal',
                    duration: 11,
                    approved: false,
                    subtasks: [
                        { taskID: 15, taskName: 'Development Task 1', startDate: new Date('02/17/2017'), endDate: new Date('02/19/2017'), duration: 3, progress: '50', priority: 'High', approved: false },
                        { taskID: 16, taskName: 'Development Task 2', startDate: new Date('02/17/2017'), endDate: new Date('02/19/2017'), duration: 3, progress: '50', priority: 'Low', approved: true },
                        { taskID: 17, taskName: 'Testing', startDate: new Date('02/20/2017'), endDate: new Date('02/21/2017'), duration: 2, progress: '0', priority: 'Normal', approved: true },
                        { taskID: 18, taskName: 'Bug fix', startDate: new Date('02/24/2017'), endDate: new Date('02/25/2017'), duration: 2, progress: '0', priority: 'Critical', approved: false },
                        { taskID: 19, taskName: 'Customer review meeting', startDate: new Date('02/26/2017'), endDate: new Date('02/27/2017'), duration: 2, progress: '0', priority: 'High', approved: false },
                        { taskID: 20, taskName: 'Phase 1 complete', startDate: new Date('02/27/2017'), endDate: new Date('02/27/2017'), duration: 0, priority: 'Low', approved: true }

                    ]
                }]
            },
            {
                taskID: 21,
                taskName: 'Phase 2',
                startDate: new Date('02/17/2017'),
                endDate: new Date('02/28/2017'),
                priority: 'High',
                approved: false,
                duration: 12,
                subtasks: [{
                    taskID: 22,
                    taskName: 'Implementation Module 2',
                    startDate: new Date('02/17/2017'),
                    endDate: new Date('02/28/2017'),
                    priority: 'Critical',
                    approved: false,
                    duration: 12,
                    subtasks: [
                        { taskID: 23, taskName: 'Development Task 1', startDate: new Date('02/17/2017'), endDate: new Date('02/20/2017'), duration: 4, progress: '50', priority: 'Normal', approved: true },
                        { taskID: 24, taskName: 'Development Task 2', startDate: new Date('02/17/2017'), endDate: new Date('02/20/2017'), duration: 4, progress: '50', priority: 'Critical', approved: true },
                        { taskID: 25, taskName: 'Testing', startDate: new Date('02/21/2017'), endDate: new Date('02/24/2017'), duration: 2, progress: '0', priority: 'High', approved: false },
                        { taskID: 26, taskName: 'Bug fix', startDate: new Date('02/25/2017'), endDate: new Date('02/26/2017'), duration: 2, progress: '0', priority: 'Low', approved: false },
                        { taskID: 27, taskName: 'Customer review meeting', startDate: new Date('02/27/2017'), endDate: new Date('02/28/2017'), duration: 2, progress: '0', priority: 'Critical', approved: true },
                        { taskID: 28, taskName: 'Phase 2 complete', startDate: new Date('02/28/2017'), endDate: new Date('02/28/2017'), duration: 0, priority: 'Normal', approved: false }

                    ]
                }]
            },

            {
                taskID: 29,
                taskName: 'Phase 3',
                startDate: new Date('02/17/2017'),
                endDate: new Date('02/27/2017'),
                priority: 'Normal',
                approved: false,
                duration: 11,
                subtasks: [{
                    taskID: 30,
                    taskName: 'Implementation Module 3',
                    startDate: new Date('02/17/2017'),
                    endDate: new Date('02/27/2017'),
                    priority: 'High',
                    approved: false,
                    duration: 11,
                    subtasks: [
                        { taskID: 31, taskName: 'Development Task 1', startDate: new Date('02/17/2017'), endDate: new Date('02/19/2017'), duration: 3, progress: '50', priority: 'Low', approved: true },
                        { taskID: 32, taskName: 'Development Task 2', startDate: new Date('02/17/2017'), endDate: new Date('02/19/2017'), duration: 3, progress: '50', priority: 'Normal', approved: false },
                        { taskID: 33, taskName: 'Testing', startDate: new Date('02/20/2017'), endDate: new Date('02/21/2017'), duration: 2, progress: '0', priority: 'Critical', approved: true },
                        { taskID: 34, taskName: 'Bug fix', startDate: new Date('02/24/2017'), endDate: new Date('02/25/2017'), duration: 2, progress: '0', priority: 'High', approved: false },
                        { taskID: 35, taskName: 'Customer review meeting', startDate: new Date('02/26/2017'), endDate: new Date('02/27/2017'), duration: 2, progress: '0', priority: 'Normal', approved: true },
                        { taskID: 36, taskName: 'Phase 3 complete', startDate: new Date('02/27/2017'), endDate: new Date('02/27/2017'), duration: 0, priority: 'Critical', approved: false },
                    ]
                }]
            }
        ]
    }
];

我希望实现一个table递归处理数据,将子任务放在父任务下,子任务放在父子任务下,等等

目前,我只能使用非动态标记向下钻取一级。 有很多例子说明它如何处理无序列表,但这些方法似乎也不适用于 tables。将选择器标签放在它自己的标记内会导致问题。

一个递归组件它只是使一个组件像

@Component({
  selector: 'recursive-component',
  template:`
  <ng-container>
  {{index}}--some template--, e.g {{item.title}}
  </ng-container>
     <ng-container *ngIf="item.children">
      <recursive-component *ngFor="let item of item.children" [index]="index+1" [item]="item">
      </recursive-component>
     <ng-container>  `

})
export class MenuComponent {
  @Input() item:any
  @Input() index:number=0;
}

我喜欢知道 "level of recursion" 这就是这个 "index" 的原因。我们必须使用它来避免创建额外的 html 标签,并且也看一下,我们必须为组件提供 object 和 children,所以。如果我们有一个数组,比如

items = [{ title: "item1" },
    {
      title: "item2", children: [
        { title: "item 2.1" },
        { title: "item 2.2" }]
    },
    { title: "item3" }
    ]

我们可以用 children 创建 "on fly" 一个 object 并且我们的 .html 将是

<recursive-component [item]="{children:menu}"  ></recursive-component>

但是你不能使用 tables,因为你总是会在另一个 table 中得到一个 table 或者在另一个 tr

中得到一个 tr

如果您需要使用 table,您可以采取另一种方法:根据您的数据生成一个数组,以及是否扩展。所以,你可以做一个像

这样的函数
  getItems(data, items,index) {
    data.forEach(x => {
      if (!items)
        items=[];
      items.push(x);
      items[items.length-1].index=index

      if (x.subtasks && x.expanded)
        this.getItems(x.subtasks,items,index+1);
    }
    )
    return items;
  }

那么你可以简单的

//In your .html
<table>
  <tr (click)="expanded(item)" *ngFor="let item of items">
    <td>{{item.taskID}}</td>
    <td>{{item.taskName}}</td>
  </tr>
  </table>

//And in your .ts
  ngOnInit()
  {
    this.items=this.getItems(this.sampleData,null,0)
  }

  expanded(item:any)
  {
    item.expanded=!item.expanded;
    this.items=this.getItems(this.sampleData,null,0)

  }

stackblitz中可以看到两个选项

注意:我简单地使用 div(单击)或 tr(单击)来更改 "expanded" 属性。

注意 2:在 stackblitz 中,我使用 "index" 更改元素的边距或填充