Angular 6:HTML 表格创建动态列和行 [英] Angular 6: HTML table create dynamic columns and rows

查看:39
本文介绍了Angular 6:HTML 表格创建动态列和行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在这里使用 Angular 6

Using Angular 6 here

我想要一些关于设计我的 UI 组件的输入.所以基本上我们已经在 AngularJs 中设计的当前功能但是在这个使用 Angular 6 的新应用我想知道是否有更好的方法.

I wanted some inputs regarding designing of one of my UI component. So basically the current functionally we had already designed in AngularJs but in this new app which is using Angular 6 I wanted to know if there are better ways to do so.

以下是我要设计的 UI 流程.

Below is the UI flow I am looking to design.

用户在顶部填写表单,基本上是一些文本框和下拉菜单.

User fills up a form at the top basically some text boxes and dropdowns.

在上面的选择之后,我显示了一个 HTML 表格,它有 3 个静态列和几个按钮,如下所示:

After the above selection I show up a HTML table having 3 static columns and few buttons for ex as below:

ID 类型 Env +添加列- 删除行+添加行

ID Type Env +Add Column -Delete Row +Add Row

在 Id、Type 和 Env 之上是 3 个始终存在的静态列.现在,如果用户想要添加更多列(动态),他可以单击添加列按钮,用户可以在其中输入他们自己的特定名称到列.还应该具有删除动态列的功能.用户完成添加列后,他可以单击添加行",这将创建一个动态行,然后用户可以向表中输入数据.一旦用户添加了该行,用户可以点击删除行按钮来删除该行.

Above Id, Type and Env are 3 static columns which are always there. Now if the user wants to add more column (dynamic) he can click the Add Column button, where user can enter their own specific name to the column. There should also be functionally of deleting the dynamic columns. Once user is done adding columns he can click on Add Row which would create a dynamic row and user can then enter data to the table. Once user adds the row, user may click on Delete Row button to delete that row.

在用户完成向表中添加列和行后,最后有一个提交按钮,它将以 Json 格式将上述内容发送到我的 API,这将然后保存整个表单.

After user is done adding columns and rows to the table there is a submit button at the end which would send the above in Json format to my API which would then save the entire form.

仅供参考,我在 angularjs 中已经有了一个工作版本,我在其中对每一行使用了 contenteditable,如下所示:

FYI I already have a working versions of this in angularjs where I am using contenteditable against each row something as below:

<tr ng-repeat="r in targetTable.rows">
 <td class="unique-id">{{r.id}}</td>
 <td contenteditable="true" ng-repeat="column in targetTable.columns" ng-model="r[column.id]" ng-blur="!r.id? addNewRow(r[column.id], r): undefined"></td>
 <td class="blank" colspan="2"></td>
</tr>

在此处查看演示:

https://codepen.io/anon/pen/QXwjwM

我需要输入的是如何设计具有在 Angular 6 中添加/删除动态行和列的所有功能的 html 表.是否有一些可用的开源或是否有人可以帮助我举一些例子.或者,如果我需要手动创建所有这些.就像我在 AngularJs 中所做的那样.

What I need inputs is to how to design this html table with all the functionally of adding/deleting dynamic rows and columns in Angular 6. Is there some open source available or if anyone can help me to some examples. Or if I need to create all this manually. in similar way as I did in AngularJs.

谢谢

有人有意见吗?

推荐答案

加入我的两个评论,我创建了这个 stackblitz

Joining my two comments, I created this stackblitz

我使用材料表是因为我懒得格式化表格.正如所评论的,我们唯一需要使用 mat-table 的东西是将表单数组的控件作为 dataSource 放置

I used material table because I'm lazy to formated a table. As commented, the only thing we need to use a mat-table is put as dataSource the controls of a Form Array

dataSource = this.myformArray.controls;

表格的列变得像

<ng-container matColumnDef="surname">
    <th mat-header-cell *matHeaderCellDef> Surname </th>
        <td mat-cell *matCellDef="let element">
       <input arrow-div [formControl]="element.get('surname')">
       </td>
  </ng-container>

是的,简单使用 [formControl]=element.get('nameOfField')

Yes, simple using [formControl]=element.get('nameOfField')

有趣的工作是让箭头键在单元格"之间移动.我使用指令.但是由于我讨厌使用 @Output() 创建指令,所以我使用了辅助服务.

The funny work is make that arrrows keys work to move between "cells". I use a directive. But as I hate create a directive with @Output() I use a auxiliar service.

如果我们不使用服务,我们的 .html 看起来像

If we not use a service, our .html looks like

<input arrow-div [formControl]="element.get('id')" (arrowEvent)="move($event)">
<input arrow-div [formControl]="element.get('name')" (arrowEvent)="move($event)">
<input arrow-div [formControl]="element.get('surname')" (arrowEvent)="move($event)">
  ...

如果我们使用服务,我们的 html 会变得更加透明

If we used a service our html become more transparent

<input arrow-div [formControl]="element.get('id')" >
<input arrow-div [formControl]="element.get('name')" >
<input arrow-div [formControl]="element.get('surname')" >
...

在应用中,我们订阅了该服务.

And in the app we subscribe to the service.

服务简单

export class KeyBoardService {
  keyBoard:Subject<any>=new Subject<any>();
  sendMessage(message:any)
  {
    this.keyBoard.next(message)
  }
}

只是一个主题和一个将值发送给主题的方法.

just a Subject and a method to send the value to subject.

该指令仅在箭头键按下时监听并发送密钥发送者.好吧,我发送一个 {element:...,acction:...} 类型的对象来发送更多信息.

The directive only listen if a arrow key is down and send the key sender. Well, I send a object of type {element:...,acction:..} to send more information.

export class ArrowDivDirective {
  constructor( private keyboardService:KeyBoardService,public element:ElementRef){}

  //@Output() arrowEvent:EventEmitter<any>=new EventEmitter();
   

  @HostListener('keydown', ['$event']) onKeyUp(e) {
    switch (e.keyCode)
    {
      case 38:
        this.keyboardService.sendMessage({element:this.element,action:'UP'})
        break;
      case 37:
        if (this.element.nativeElement.selectionStart<=0)
        {
        this.keyboardService.sendMessage({element:this.element,action:'LEFT'})
        e.preventDefault();
        }
        break;
      case 40:
        this.keyboardService.sendMessage({element:this.element,action:'DOWN'})
        break;
      case 39:
        if (this.element.nativeElement.selectionStart>=this.element.nativeElement.value.length)
        {
        this.keyboardService.sendMessage({element:this.element,action:'RIGTH'})
        e.preventDefault();
        }
        break;
    }
  }
}

好吧,当我们单击 lfet 和向右箭头时,我会考虑您何时开始或在输入的 init 中发送或不发送密钥.

Well, I take account when you're at first or at init of the input to send or not the key when we click lfet and right arrow.

app.component 只需要订阅服务并使用 ViewChildren 来存储所有输入.小心点!mat-table中viewchildren的顺序是从上到下,从左到右

The app.component only has to subscribe to the service and use ViewChildren to store all the inputs. be carefully! the order of the viewchildren in a mat-table goes from top to down and to left to rigth

@ViewChildren(ArrowDivDirective) inputs:QueryList<ArrowDivDirective>

  constructor(private keyboardService:KeyBoardService){}
  ngOnInit()
  {
    this.keyboardService.keyBoard.subscribe(res=>{
      this.move(res)
    })
  }
  move(object)
  {
    const inputToArray=this.inputs.toArray()
    const rows=this.dataSource.length
    const cols=this.displayedColumns.length
    let index=inputToArray.findIndex(x=>x.element===object.element)
    switch (object.action)
    {
      case "UP":
        index--;
        break;
      case "DOWN":
        index++;
        break;
      case "LEFT":
        if (index-rows>=0)
          index-=rows;
        else
        {
          let rowActual=index%rows;
          if (rowActual>0)
            index=(rowActual-1)+(cols-1)*rows;
        }
        break;
      case "RIGTH":
      console.log(index+rows,inputToArray.length)
        if (index+rows<inputToArray.length)
          index+=rows;
        else
        {
          let rowActual=index%rows;
          if (rowActual<rows-1)
            index=(rowActual+1);

        }
        break;
    }
    if (index>=0 && index<this.inputs.length)
    {
      inputToArray[index].element.nativeElement.focus();
    }
  }

*UPDATE 如果我们想添加动态列添加新的两个变量(加上displayedColumns"

*UPDATE If we want to add dinamically columns add new two variables (plus the "displayedColumns"

displayedColumns: string[] = ['name','surname','delete'];
displayedHead:string[]=['Name','Surname']
displayedFields:string[] = ['name','surname'];

我们的桌子变得像

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
  <!-- All columns -->
   <ng-container *ngFor="let col of displayedFields;let i=index" [matColumnDef]="col">
      <th mat-header-cell *matHeaderCellDef> {{displayedHead[i]}} </th>
      <td mat-cell *matCellDef="let element">
        <input arrow-div [formControl]="element.get(col)">
      </td>
    </ng-container>
    <!---column delete-->
  <ng-container matColumnDef="delete">
    <th mat-header-cell *matHeaderCellDef></th>
    <td mat-cell *matCellDef="let element;let i=index;">
        <button arrow-div mat-button (click)="delete(i)">delete</button>
    </td>
  </ng-container>

  
  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

一个新的添加列的函数必须给数组的每个FormGroup添加一个FormControl,实现变量displayColumns、displayedHead和displayFields

A new function to add a column must add a FormControl to each FormGroup of the array, actualize the variables displayedColumns,displayedHead and displayedFields

addColumn()
  {
    let newField="Column"+(this.displayedFields.length+1)

    this.myformArray.controls.forEach((group:FormGroup)=>{
      group.addControl(newField,new FormControl())
    })
    this.displayedHead.push(newField)
    this.dataSource = [...this.myformArray.controls];
    this.displayedFields.push(newField);
    this.displayedColumns=[...this.displayedFields,"delete"];
  }

在这个另一个stackblitz 我添加了这个功能(还有如何删除一行以及如何创建一个新行)

In this another stackblitz I add this functionality (also how delete a row and how create a new row)

更新 2 回答如何不硬编码"formArray 真的就在里面.如果我们想象我们有一个像

Update 2 answer to how not "hardcode" the formArray really it's all inside. If we imagine that we has an array like

ELEMENT_DATA: any[] = [ { name: '1', surname: 'one' }, { name: '2', surname: 'two' }, { name: '3', surname: 'three' }, ]; )

我们需要使用数组给displayedHead、displayedFields 和displayedColumns 赋值:

We need use the array to give values to displayedHead, displayedFields and displayedColumns:

displayedHead:string[]=Object.keys(this.ELEMENT_DATA[0]).map(x=>x.substring(0,1).toUpperCase()+x.substring(1))
  displayedFields:string[] = Object.keys(this.ELEMENT_DATA[0]);
  displayedColumns:string[]=[...this.displayedFields,'delete']

为了初始化 FormArray,我们将改进函数add";允许将对象作为参数传递给表单

To initialize the FormArray we are going to improve the function "add" to allow pass as argument an object to give value to the form

  add(data:any=null)
  {
    const newGroup=new FormGroup({});
    this.displayedFields.forEach(x=>{
      //see that if data!=null we create the FormControl with the value
      //of data[x]
      newGroup.addControl(x,new FormControl(data?data[x]:null))
    })
    this.myformArray.push(newGroup)

    this.dataSource = [...this.myformArray.controls];
  }

至少创建一个函数initArray

At least create a function initArray

  initArray(elements:any[]){
    elements.forEach(x=>{
      this.add(x);
    })
  }

并在 ngOnInit 中调用它

And call it in ngOnInit

this.init(this.ELEMENT_DATA)

好吧,如果我们没有变量中的数组——通常我们从服务中获取值——,我们需要将所有这些放在服务的订阅函数中

Well, if we don't has the array in a variable -usually we get the value form a service-, we need put all this in the subscribe function to the service

this.myserviceData.getData().subscribe(res=>{
displayedHead:string[]=Object.keys(res[0]).map(x=>x.substring(0,1).toUpperCase()+x.substring(1))
      displayedFields:string[] = Object.keys(res);
      displayedColumns:string[]=[...this.displayedFields,'delete']
      this.initArray(res)
})

更新允许用户更改列名

在表中,头"是指每列可以是任何东西.所以我们可以定义一个变量

In a table, the "head" of each columns can be anything. So we can defined one variable

  columnSelect = -1;

我们正在创建一个更复杂的标题

And we are create a more complex header

  <th mat-header-cell *matHeaderCellDef>
     <span [style.display]="columnSelect!=i?'inline':'none'" (click)="selectColumn(i,columnName)">{{displayedHead[i]}} </span>
     <input #columnName [style.display]="columnSelect==i?'inline':'none'" [ngModel]="displayedHead[i]" (blur)="changeColumnName(i,columnName.value)"/>
     </th>

查看标题或输入或跨度(如果selectColumn"等于列.请记住,列从 0 开始编号 - 这是原因,因为如果 selectColumn=-1 有未选择任何列.

See that the header or is an input or is an span (if the "selectColumn" is equal to the column. remember that the columns are numerated from 0 -this is the reason because if selectColumn=-1 there're no column selected.

我们使用模板引用变量#columnName";将值传递给函数(模糊)以及何时(单击)跨度.这允许我们创建两个函数

We use a template reference variable "#columnName" to pass the value to the function (blur) and when (click) the span. this allow us create two functions

  selectColumn(index: number, inputField: any) {
    this.columnSelect = index; //give value to columnSelect
    setTimeout(() => {          //in a setTimeout we make a "focus" to the input
      inputField.focus();
    });
  }

必须在 setTimeout 内设置焦点,以允许 Angular 重新绘制标题,然后设置焦点.这也是我们使用 [style.display] 而不是 *ngIf 的原因.(如果我们使用 *ngIf,值inputField"为空)

It's neccesary make the focus inside a setTimeout to allow Angular repaint the header and then make the focus. This is the reason also we use [style.display] and not *ngIf. (if we use *ngIf, the value "inputField" was null)

改名字的函数有点复杂

  changeColumnName(index, columnTitle) {
    const oldName = this.displayedFields[index];
    const columnName = columnTitle.replace(/ /g, "").toLowerCase();
    if (columnName != oldName) {
      this.myformArray.controls.forEach((group:FormGroup)=>{
          group.addControl(columnName,new FormControl(group.value[oldName]))
          group.removeControl(oldName)
      })
      this.displayedHead.splice(index, 1, columnTitle);
      this.displayedColumns.splice(index, 1, columnName);
      this.displayedFields.splice(index, 1, columnName);
    }
    this.columnSelect = -1;
  }

基本上我们向数组添加一个新的 formControl 并删除旧的.我选择字段"的名称.是删除所有空格后小写的标题.

Basicaly we add a new formControl to the array and remove the older. I choose that the name of the "field" was the Title to lower case after remove all the spaces.

新的stackblitz

这篇关于Angular 6:HTML 表格创建动态列和行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆