tl;dr; - see my answer below; solution is to provide trackBy function
I've got a simple timesheet application using mat-table where a user can add hours worked for each day of the week.
Adding and deleting adding rows with in certain sequences is not working.
Steps to reproduce issue:
Add a new row set Sunday to 1 Add another row and set Sunday to 2 Delete first row - now there will be one row with Sunday=2 as expected Now add another new row. Expected behaviour - new row is added and first row Sunday value is still 2. Actual behaviour - new row is added but first row has been reset to 0 & it's like I'm getting 2 new rows.
I know there are a number of recommended ways to set the table datasource - I've tried them all but it makes no difference. The version shown below reinstantiates the datasource each time you add/remove but I've also tried setting the datasource.data property too but it makes no difference. The underlying array always contains the expected values it's just that in the scenario outlined above, the table does not seem to bind correctly.
I've stripped out the core functionality from this app and made a bare bones demo of the issue and have uploaded here: Github. This is Angular 8 with angular-material 8. Same thing happening on angular 7.
Here is the markup
<div class='timesheets'>
<div>Timesheets</div>
<form #formControl="ngForm" autocomplete="off">
<mat-table [dataSource]="timesheetEntryDataSource" #timesheetTable matSort>
<ng-container matColumnDef="actions">
<mat-header-cell *matHeaderCellDef class='actions'>
<button mat-icon-button color="primary" (click)="add()" [disabled]="!!attendance?.approvalDate">
<mat-icon aria-label="Add">add</mat-icon>
</button>
</mat-header-cell>
<mat-cell *matCellDef="let entry" class='actions'>
<button mat-icon-button color="primary" (click)="remove(entry)" >
<mat-icon aria-label="Delete">delete</mat-icon>
</button>
</mat-cell>
<mat-footer-cell *matFooterCellDef class='actions'></mat-footer-cell>
</ng-container>
<ng-container matColumnDef="sunday">
<mat-header-cell *matHeaderCellDef>Sunday</mat-header-cell>
<mat-cell *matCellDef="let entry;let i = index;" class="hours">
<mat-form-field>
<input min=0 max=23.75 pattern="^(?!0\d)\d+(?:[.](?:25|5|75|0)0*)?$" required #today="ngModel" matInput [(ngModel)]="entry.sundayHours" name="sunday{{i}}">
</mat-form-field>
</mat-cell>
<mat-footer-cell *matFooterCellDef></mat-footer-cell>
</ng-container>
<ng-container matColumnDef="monday">
<mat-header-cell *matHeaderCellDef>Monday</mat-header-cell>
<mat-cell *matCellDef="let entry;let i = index;" class="hours">
<mat-form-field>
<input min=0 max=23.75 pattern="^(?!0\d)\d+(?:[.](?:25|5|75|0)0*)?$" required #today="ngModel" matInput [(ngModel)]="entry.mondayHours" name="monday{{i}}">
</mat-form-field>
</mat-cell>
<mat-footer-cell *matFooterCellDef></mat-footer-cell>
</ng-container>
<ng-container matColumnDef="tuesday">
<mat-header-cell *matHeaderCellDef>Tuesday</mat-header-cell>
<mat-cell *matCellDef="let entry;let i = index;" class="hours">
<mat-form-field>
<input min=0 max=23.75 pattern="^(?!0\d)\d+(?:[.](?:25|5|75|0)0*)?$" required #today="ngModel" matInput [(ngModel)]="entry.tuesdayHours" name="tuesday{{i}}">
</mat-form-field>
</mat-cell>
<mat-footer-cell *matFooterCellDef></mat-footer-cell>
</ng-container>
<ng-container matColumnDef="wednesday">
<mat-header-cell *matHeaderCellDef>Wednesday</mat-header-cell>
<mat-cell *matCellDef="let entry;let i = index;" class="hours">
<mat-form-field>
<input min=0 max=23.75 pattern="^(?!0\d)\d+(?:[.](?:25|5|75|0)0*)?$" required #today="ngModel" matInput [(ngModel)]="entry.wednesdayHours" name="wednesday{{i}}">
</mat-form-field>
</mat-cell>
<mat-footer-cell *matFooterCellDef></mat-footer-cell>
</ng-container>
<ng-container matColumnDef="thursday">
<mat-header-cell *matHeaderCellDef>Thursday</mat-header-cell>
<mat-cell *matCellDef="let entry;let i = index;" class="hours">
<mat-form-field>
<input min=0 max=23.75 pattern="^(?!0\d)\d+(?:[.](?:25|5|75|0)0*)?$" required #today="ngModel" matInput [(ngModel)]="entry.thursdayHours" name="thursday{{i}}">
</mat-form-field>
</mat-cell>
<mat-footer-cell *matFooterCellDef></mat-footer-cell>
</ng-container>
<ng-container matColumnDef="friday">
<mat-header-cell *matHeaderCellDef>Friday</mat-header-cell>
<mat-cell *matCellDef="let entry;let i = index;" class="hours">
<mat-form-field>
<input min=0 max=23.75 pattern="^(?!0\d)\d+(?:[.](?:25|5|75|0)0*)?$" required #today="ngModel" matInput [(ngModel)]="entry.fridayHours" name="friday{{i}}">
</mat-form-field>
</mat-cell>
<mat-footer-cell *matFooterCellDef></mat-footer-cell>
</ng-container>
<ng-container matColumnDef="saturday">
<mat-header-cell *matHeaderCellDef>Saturday</mat-header-cell>
<mat-cell *matCellDef="let entry;let i = index;" class="hours">
<mat-form-field>
<input min=0 max=23.75 pattern="^(?!0\d)\d+(?:[.](?:25|5|75|0)0*)?$" required #today="ngModel" matInput [(ngModel)]="entry.saturdayHours" name="saturday{{i}}">
</mat-form-field>
</mat-cell>
<mat-footer-cell *matFooterCellDef></mat-footer-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="timesheetColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: timesheetColumns;"></mat-row>
<mat-footer-row *matFooterRowDef="timesheetColumns"></mat-footer-row>
</mat-table>
</form>
</div>
And the code:
import { Component } from '@angular/core';
import { MatTableDataSource } from '@angular/material';
export class TimesheetEntry {
mondayHours:string = '0'
tuesdayHours:string = '0'
wednesdayHours:string = '0'
thursdayHours:string = '0'
fridayHours:string = '0'
saturdayHours:string = '0'
sundayHours:string = '0'
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'tabledemo';
timesheetEntryDataSource: MatTableDataSource<TimesheetEntry>;
timesheetColumns = ['actions', 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday' ];
tmpTimesheet:TimesheetEntry[] = [];
constructor( ) { }
ngOnInit() {
this.timesheetEntryDataSource = new MatTableDataSource<TimesheetEntry>([]);
}
add(){
this.tmpTimesheet.push(new TimesheetEntry());
this.timesheetEntryDataSource = new MatTableDataSource<TimesheetEntry>(this.tmpTimesheet.slice());
}
remove(exception: TimesheetEntry){
this.tmpTimesheet.splice(this.tmpTimesheet.findIndex(e => e === exception),1);
this.timesheetEntryDataSource = new MatTableDataSource<TimesheetEntry>(this.tmpTimesheet.slice());
}
typeComparatorFn(select, option) {
return select && option ? select.code === option.code : select === option;
}
}
type
attribute, so by default they aresubmit
and whenever clicked, submits the form. Add abutton
type to those that are inside<form>
-<button mat-icon-button type="button"
– Julius