But it's not so complex do it stackblitz
If our tds are like
<ng-container matColumnDef="position">
<th mat-header-cell *matHeaderCellDef> No. </th>
<td mat-cell #cell *matCellDef="let element;let i=index"
(click)="select($event,cell)"
<!--use isSelected(i,0) for the first column
isSelected(i,1) for the second column
...
-->
[ngClass]="{'selected':isSelected(i,0)}"
> {{element.position}} </td>
</ng-container>
We get the cells using viewChildren, and declare an array with the selection
selection: number[]=[];
@ViewChildren("cell", { read: ElementRef }) cells: QueryList<ElementRef>;
In cells we has all the "td" order from top to bottom and from left to rigth
The function select take account if you are pressed the shifh key or the control key
select(event: MouseEvent, cell: any) {
//search the cell "clicked"
const cellClick = this.cells.find(x => x.nativeElement == event.target);
//get the index of this cells
let indexSelected = -1;
this.cells.forEach((x, i) => {
if (x == cellClick) indexSelected = i;
});
if (event.ctrlKey) { //if ctrl pressed
if (this.selection.indexOf(indexSelected)>=0) //if its yet selected
this.selection=this.selection.filter(x=>x!=indexSelected);
else //if it's not selected
this.selection.push(indexSelected)
} else {
if (event.shiftKey) { //if the key shift is pressed
if (this.selection.length) //if there any more selected
{
//calculate the row and col of fisrt element we selected
let rowFrom=this.selection[0]%this.dataSource.data.length;
let colFrom=Math.floor(this.selection[0]/this.dataSource.data.length)
//idem from the index selected
let rowTo=indexSelected%this.dataSource.data.length;
let colTo=Math.floor(indexSelected/this.dataSource.data.length)
//interchange if from is greater than to
if (rowFrom>rowTo)
[rowFrom, rowTo] = [rowTo, rowFrom]
if (colFrom>colTo)
[colFrom, colTo] = [colTo, colFrom]
//clean the array
this.selection=[]
//we run througth all the td to check if we need push or not
this.cells.forEach((x,index)=>{
const row=index%this.dataSource.data.length;
const col=Math.floor(index/this.dataSource.data.length)
if (row>=rowFrom && row<=rowTo && col>=colFrom && col<=colTo)
this.selection.push(index)
})
}
else //if there're anything selected and the shit is pressed
this.selection = [indexSelected]
} else { //if no key shit nor key ctrl
this.selection = [indexSelected]
}
}
}
And a function isSelected give as posibility to change the class
isSelected(row,column)
{
const index=column*this.dataSource.data.length+row
return this.selection.indexOf(index)>=0
}
with a .css
table {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
td {
outline: none!important
}
.selected
{
border:1px solid red;
}
Update
In the example you need shift & ctrl. But we can has a checkbox or a select to "change the way of selection" -use [(ngModel)]- and replace the conditions event.shiftKey and event.ctrlKey.
If you allways select a range you can use two variables "fromIndex" and "toIndex",
fromIndex:number=-1
toIndex:number=-1
select(event: MouseEvent, cell: any){
....
..get the indexSelected ...
if (this.fromIndex==-1){ //If nothing select
this.fromIndex=indexSelected
this.toIndex=-1
}
else{
this.toIndex=indexSelected;
}
if (this.toIndex>=0 && this.fromIndex>=0)
{
...the code to select range
}
}