问题
I need to make a table with data with nested subtasks. I need to make this table recursive. There is a lot of literature about ngFor recursively showing data, but not in a table format. There are a lot of things that make tables harder to work with(e.g. using div tags outside of td tags, etc.)
here is a stackblitz of what I got so far.
https://stackblitz.com/edit/angular-xk9nw6?file=src%2Fapp%2Ftable%2Ftable.component.html
As you can see, I can only drill down a single level before I encounter issues with 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>
component
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;
}
}
sample data
/**
* 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 },
]
}]
}
]
}
];
I am hoping to achieve a table that handles data recursively, placing subtasks under their parent tasks, and sub-sub-tasks under their parent subtasks, etc, etc.
currently, I can only drill down one level in with non dynamic markup. there are plenty of examples of how this works with unordered lists, but these methods don't seem to work with tables as well. dropping a selector tag on inside it's own markup causes issues.
回答1:
A recursive component it's only make a component like
@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;
}
I like know the "level of recursion" it's the reason of this "index". WE must use to avoid create extra html tags, and take a look too, that we must feed the component with an object with children, so. if we has an array, like
items = [{ title: "item1" },
{
title: "item2", children: [
{ title: "item 2.1" },
{ title: "item 2.2" }]
},
{ title: "item3" }
]
We can create "on fly" an object with children and our .html will be
<recursive-component [item]="{children:menu}" ></recursive-component>
But you can not do using tables, because always you'll get a table inside another table or a tr inside another tr
If you need use table you can take another aproach: generate an array based in your data and if is expanded or not. So, you can do a function like
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;
}
Then you can simple
//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)
}
In stackblitz, you can see the two options
NOTE: I simple use div (click) or tr (click) to change the "expanded" property.
NOTE2: In the stackblitz I use the "index" to change the margin or the padding of the elements
来源:https://stackoverflow.com/questions/55305905/how-can-i-create-a-nested-recursive-table-that-can-drill-down-x-levels-in-angul