Context:
I have a component that loads a <table mat-table>
with a given data source.
I have added a (keydown) function to the <tr mat-row>
.
The function allows me to shift the row highlighting up and down the rows as I press the up/down arrow keys.
Issues:
In order for the (keydown) function to execute, I have to either click one of the table rows to set the focus, or use the Tab through multiple items to eventually focus on the table rows.
The (keydown) function is only changing the highlighting of the rows, and not the :focus. So if I hit the down arrow, the next row down highlights, but the :focus outline remains on the previous row.
I've tried a few solutions, and it seems I cannot focus the <tr>
tags without clicking / Tabbing. The autofocus directive is not supported for <tr>
tags. Using .focus() errors out, claiming that .focus() is not a function, even if I cast it to the HTMLElement type.
Example:
let firstRow = this.dataSource.connect().value[0] as unknown as HTMLTableRowElement;
firstRow.focus();
ERROR TypeError: firstRow.focus is not a function
I have also tried applying tabIndex="0" to the mat-row, but it does not become the first tab-able element on the page.
The table is sort-able, so I am using dataSource.connect().value[0] to find the first row, rather than dataSource.data[0].
This is because dataSource.data[0] was giving the pre-sorted array, which was defeating the purpose of the up/down arrow functionality.
TS Component:
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { TableService } from '../table.service';
import { SelectionModel } from '@angular/cdk/collections';
import { TableGrid } from '../TableGrid';
@Component({
selector: 'app-table',
templateUrl: './table.component.html',
styleUrls: ['./table.component.css']
})
export class TableComponent implements OnInit {
@ViewChild(MatSort, { static: true }) sort: MatSort;
dataSource: MatTableDataSource<TableGrid> = null;
displayedColumns = [
'locked',
'includeRe',
'includeMv'];
selection = new SelectionModel<TableGrid>(false, []);
selectedRowId = 0;
selectedRowIndex = 0;
constructor( private tableService: TableService) { }
ngOnInit() {
this.tableService.get();
this.tableService.tableGrid
.subscribe(result => {
this.selectedRowId = 0;
this.dataSource = new MatTableDataSource(result);
this.dataSource.sort = this.sort;
if (this.dataSource.data.length > 0) {
this.onRowClicked(this.dataSource.data[0]);
}
});
}
onRowClicked(row) {
this.selection.clear();
this.selection.select(row);
this.selectedRowId = row.id;
this.selectedRowIndex = this.dataSource.connect().value.indexOf(row);
this.tableService.setId(row.id, row.name, row.locked, row.isDeleteable, row.includeHistory);
}
tableKeydown(event: KeyboardEvent) {
if (!this.selection.isEmpty()) {
let newSelection;
const currentIndex = this.selectedRowIndex;
if (event.key === 'ArrowDown') {
newSelection = this.dataSource.connect().value[currentIndex + 1];
} else if (event.key === 'ArrowUp') {
newSelection = this.dataSource.connect().value[currentIndex - 1];
}
if (newSelection) {
this.selectedRowId = newSelection.id;
this.onRowClicked(newSelection);
}
}
}
}
HTML:
<div class="container">
<div class="table-container">
<table mat-table [dataSource]="dataSource" matSort aria-label="Elements">
<ng-container matColumnDef="locked">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Locked</th>
<td mat-cell *matCellDef="let row">
<i *ngIf="row.locked === true" class="material-icons">lock</i>
</td>
</ng-container>
<ng-container matColumnDef="includeRe">
<th mat-header-cell *matHeaderCellDef mat-sort-header>RE</th>
<td mat-cell *matCellDef="let row">
<i *ngIf="row.includeRe === true" class="material-icons">house</i>
</td>
</ng-container>
<ng-container matColumnDef="includeMv">
<th mat-header-cell *matHeaderCellDef mat-sort-header>MV</th>
<td mat-cell *matCellDef="let row">
<i *ngIf="row.includeMv === true" class="material-icons">directions_car</i>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky:true"></tr>
<tr mat-row tabIndex="0" *matRowDef="let row; columns: displayedColumns;"
(click)="onRowClicked(row)"
(keydown)="tableKeydown($event)"
[ngClass] = "{'highlight' : row.id == selectedRowId}">
</tr>
</table>
</div>
</div>
Desired Functionality:
I would like to set the :focus attribute to the first <tr>
of the table on init, so that the (keydown) function is immediately usable.
I would also like to get the (keydown) function to set the :focus on the <tr>
tags.
What I'm trying to avoid
I would like to avoid creating a custom focus class to reproduce the focus outline effect. I would also like to avoid disabling the focus-outline on table-rows. What I want, is to put the :focus pseudo-class on the <tr>
. If this is impossible, I will consider alternatives.
I can add more info if needed, but I'm trying to keep it relatively anonymous as this is work-related.
I'm relatively new to Angular, but have developed for a few years in React. Any advice would be appreciated.
Thank you.