HenrikM
HenrikM

Reputation: 457

Angular 8 - Material Table not displaying correct data on delete

I have a material table that displays available options in the form of selects.

I have a root object which is the ngModel and serves to keep the options.

The options are retrieved from a database.

So lets say i have a root item of menu. This keep tracks of drinks and fruits in an array called snacks like so:

this.menu =
  {
    id: 1,
    snacks: [
      { fruitId: 1, drinkId: 1, deleted: false },
      { fruitId: 2, drinkId: 2, deleted: false },
      { fruitId: 3, drinkId: 2, deleted: false },
      { fruitId: 3, drinkId: 2, deleted: true },
    ]
  };

And then we define some fruits and drinks the user can choose:

this.fruits = [
  { id: 1, name: "Orange" },
  { id: 2, name: "Apple" },
  { id: 3, name: "Banana" },
  { id: 4, name: "Dragon fruit" }
];
this.drinks = [
  { id: 1, name: "Milk" },
  { id: 2, name: "Water" },
  { id: 3, name: "Juice" },
  { id: 4, name: "Soda" }
];

Then we map those in the table:

<table mat-table [dataSource]="menuTableSource" #menuTable>

                        <ng-container matColumnDef="fruit">
                            <th mat-header-cell *matHeaderCellDef> Fruit </th>
                            <td mat-cell *matCellDef="let menuItem; let i = index;">
                                <mat-select [(ngModel)]="menu.snacks[i].fruitId" name="fruitId"
                                    #fruitId="ngModel">
                                    <mat-option *ngFor="let fruit of fruits" [value]="fruit.id">
                                        {{ fruit.name }}
                                    </mat-option>
                                </mat-select>
                            </td>
                        </ng-container>

                        <ng-container matColumnDef="drink">
                            <th mat-header-cell *matHeaderCellDef> Drink </th>
                            <td mat-cell *matCellDef="let menuItem; let i = index;">
                                <mat-select [(ngModel)]="menu.snacks[i].drinkId"
                                    name="drinkId" required>
                                    <mat-option *ngFor="let drink of drinks" [value]="drink.id">
                                        {{drink.name}}
                                    </mat-option>
                                </mat-select>
                            </td>
                        </ng-container>

                    <tr mat-header-row *matHeaderRowDef="menuTableColumns"></tr>
                    <tr mat-row *matRowDef="let row; columns: menuTableColumns;"></tr>
</table>

Upon removing an item from this array and setting the table source data to this and calling renderRows() the table will drop the last entry even though the array of snacks is updated as expected.

onRemoveMenuItem(menuItem: any): void {
const idx = this.menu.snacks.indexOf(menuItem);
if (idx >= 0) {
  this.menu.snacks[idx].deleted = true;
  this.menuTableSource.data = this.menu.snacks.filter(x => !x.deleted);
  this.menuTable.renderRows();
  }
}

Is this a bug or do I have to update the table in some other way?

Please see this stackblitz which demonstrates the isssue: https://stackblitz.com/edit/angular-bzdjhj

Upvotes: 1

Views: 859

Answers (2)

HenrikM
HenrikM

Reputation: 457

Looking at the issue again I realize I might be a bit of an idiot.

Maihan pointed me in the right direction.

Of course binding to the ngModel doesn't work because ngModel operates on the unfiltered collection.

But since I'd like to keep a reference to the deleted items I added a new array.

deletedSnacks = [];

And in my removeMenuItem function I push the snacks into that array, splice them and then upon submittning to the backend i simply concat them.

onRemoveMenuItem(menuItem: any): void {
const idx = this.menu.snacks.indexOf(menuItem);
if (idx >= 0) {
  this.menu.snacks[idx].deleted = true;
  this.deletedSnacks.push(this.menu.snacks[idx]);
  this.menu.snacks.splice(idx, 1);
  this.menuTableSource.data = this.menu.snacks;
  this.menuTable.renderRows();
  }
}

Upvotes: 0

Maihan Nijat
Maihan Nijat

Reputation: 9355

Updated: After looking to your code again. What I realized is that you are removing the right row and your data updates just fine but since you are displaying select dropdowns items from the fruits which creates confusion for you.

<ng-container matColumnDef="fruit">
  <th mat-header-cell *matHeaderCellDef> Fruit </th>
  <td mat-cell *matCellDef="let menuItem; let i = index;">
<mat-select [(ngModel)]="menu.snacks[i].fruitId" name="fruitId"
#fruitId="ngModel">
<mat-option *ngFor="let fruit of fruits" [value]="fruit.id">
  {{ fruit.name }}
</mat-option>
</mat-select>
</td>
</ng-container>

If you delete first item, the second item again shows orange in the select list because of this <mat-option *ngFor="let fruit of fruits" [value]="fruit.id">.

>>> Update Ends

You can do this with passing the index of menu item and removing the item from the menu using the index passed.

Pass the index:

<ng-container matColumnDef="remove">
    <th mat-header-cell *matHeaderCellDef> &nbsp; </th>
    <td mat-cell *matCellDef="let menu; let i = index">
        <span class="fill-remaining-space"></span>
        <button mat-stroked-button color="primary" (click)="onRemoveMenuItem(i)">Remove</button>
    </td>
</ng-container>

And your logic would be:

onRemoveMenuItem(i): void {
  this.menu.snacks.splice(i, 1);
  this.menuTableSource.data = this.menu.snacks;
  this.menuTable.renderRows();
}

You can later the property of the item if you do not want to remove it but just add property deleted for your other processes.

Upvotes: 1

Related Questions