admin管理员组

文章数量:1298182

Using Angular 6 here

I wanted some inputs regarding designing of one of my UI ponent. 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.

Below is the UI flow I am looking to design.

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

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

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

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.

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.

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>

See the demo here:

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.

Thanks

Anyone with inputs?

Using Angular 6 here

I wanted some inputs regarding designing of one of my UI ponent. 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.

Below is the UI flow I am looking to design.

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

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

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

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.

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.

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>

See the demo here:

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

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.

Thanks

Anyone with inputs?

Share Improve this question edited Jun 13, 2019 at 12:38 aman asked Jun 12, 2019 at 13:00 amanaman 6,26217 gold badges64 silver badges118 bronze badges 6
  • Can you post what have you tried so far in a stackblitz demo. – Munim Munna Commented Jun 17, 2019 at 16:41
  • @MunimMunna We are migrating from angularjs to angular6. I have done similar thing in angularjs as shared in link above. Now I want to do the same thing in angular 6 but wanted to know if there is a better way examples etc. As in my angularjs i used contenteditable on each row which made my table slow when large data was entered or uploaded. – aman Commented Jun 17, 2019 at 17:03
  • Obviously using input boxes will give you performance gain over it. I suggest you attempt to build it using angular 6, we can help if you get stuck anywhere. – Munim Munna Commented Jun 17, 2019 at 17:36
  • @MunimMunna does angular6 has contenteditable as I am using above in angularjs – aman Commented Jun 18, 2019 at 11:48
  • @karen, take a look this great article from Netanet Basal netbasal./… – Eliseo Commented Jun 18, 2019 at 18:33
 |  Show 1 more ment

2 Answers 2

Reset to default 7 +50

Joining my two ments, I created this stackblitz

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

dataSource = this.myformArray.controls;

The columns of the table bees like

<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>

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

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.

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)">
  ...

If we used a service our html bee 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.

The service is simple

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.

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;
    }
  }
}

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.

The app.ponent 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 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'];

And our table bees like

<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>

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"];
  }

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

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' }, ]; )

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']

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];
  }

At least create a function initArray

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

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)
})

Update allow user to change name of the columns

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 plex 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>

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.

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();
    });
  }

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)

The function to change the name is a bit more plex

  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;
  }

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.

The new stackblitz

There is an ui-grid third party library which gives lot of features. You can refer the link

If you can upgrade as mentioned here demo

本文标签: javascriptAngular 6 HTML table create dynamic columns and rowsStack Overflow