2
votes

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



}
1
Update - The problem seems to be the form tag. Remove that and it works - problem is, I need the form. Further investigation needed.BROTES DE GERMINADOS
Its pretty hard to tell anything, many things could go wrong. If you could create a stackblitz, would be easy to answer. One thing I see wrong - your buttons does not have type attribute, so by default they are submit and whenever clicked, submits the form. Add a button type to those that are inside <form> - <button mat-icon-button type="button"Julius
Stackblitz created here: angular-mbsl9r.stackblitz.ioBROTES DE GERMINADOS

1 Answers

1
votes

The problem is using index to create a unique name attribute (name attribute is required when using ngModel in a form tag - I'm using templated forms).

*matCellDef="let entry;let i = index;"

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

It seems that adding and removing items to/from the datasource doesn't update the update index consistently and in my scenario, I end up with two entries with the same index.

Solution is to specify a trackBy function as specified in the documentation

In my case, I added this to the .ts file:

  trackByFn(index, item) {
return index;

}

And in the markup:

<mat-table [dataSource]="tmpTimesheet" [trackBy]="trackByFn"> 

That seemed to do the trick but this could have been much more clearly documented as I'm sure adding/removing rows to/from mat-table in an Angular templated form isn't exactly an uncommon operation & I'm sure there are many other scenarios where you need to have a handle on the index. On first glance, using the let i= index construct seems to suggest that all the work is done for you!