Using Angular 6 here
I wanted some inputs regarding designing of one of my UI component. So basically the current functionally we had already designed in AngularJs b
Joining my two comments, I created this stackblitz
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;
The columns of the table becomes 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 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.
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.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 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 becomes 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)
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