Paginator in material angular doesn't work with rows added via dialog

601 Views Asked by At

I'm trying to populate a table with a mat dialog and to use pagination after 5 added rows. Adding the data from the dialog works, but I cannot make the paginator work. I tried a lot of tutorial, but they only use static data or data loaded from API.

This is my code:

order.component.html:

<mat-grid-list cols="2" rowHeight="45px">
  <mat-grid-tile id="customer">
    <h1>{{ customer.name }} ({{ customer.code }})</h1>
  </mat-grid-tile>
  <mat-grid-tile id="buttons">
    <button mat-button color="basic" (click)="openHelpDialog()">Help</button>
    <button mat-button color="warn">Annulla (ESC)</button>
    <button mat-button color="primary">Salva (F10)</button>
  </mat-grid-tile>
</mat-grid-list>

<mat-grid-list cols="2" rowHeight="67px" style="font-size: 14px">
  <mat-grid-tile>
    <mat-form-field appearance="standard">
      <mat-label>Pagamento</mat-label>
      <input matInput #payment placeholder="Inserisci metodo di pagamento" maxlength="220">
      <mat-hint align="end">{{ headNote.value.length}}/220</mat-hint>
    </mat-form-field>
  </mat-grid-tile>
  <mat-grid-tile>
    <mat-form-field appearance="standard">
      <mat-label>Nota di testata</mat-label>
      <input matInput #headNote placeholder="Aggiungi nota di testata" maxlength="220">
      <mat-hint align="end">{{ headNote.value.length}}/220</mat-hint>
    </mat-form-field>
  </mat-grid-tile>
</mat-grid-list>

<mat-grid-list cols="2" rowHeight="67px" style="font-size: 14px">
  <mat-grid-tile>
    <mat-form-field appearance="standard">
      <mat-label>Cerca</mat-label>
      <input #autoInput autofocus type="text"
            placeholder="Cerca un prodotto"
            aria-label="Cerca"
            matInput
            [formControl]="myControl"
            [matAutocomplete]="auto">
      <mat-autocomplete autoActiveFirstOption #auto="matAutocomplete" (optionSelected)="openDialog($event)">
        <mat-option *ngFor="let option of filteredOptions | async" [value]="option">
          {{option}}
        </mat-option>
      </mat-autocomplete>
      <button matSuffix mat-icon-button aria-label="Clear" (click)="myControl.reset()">
        <mat-icon>close</mat-icon>
      </button>
    </mat-form-field>
  </mat-grid-tile>

  <mat-grid-tile>
    <mat-form-field appearance="standard">
      <mat-label>Nota di riga</mat-label>
      <input matInput #rowNote placeholder="Aggiungi nota di riga" (keydown.enter)="onSaveRowNote($event)" (keydown.escape)="onEscape($event)" maxlength="75">
      <mat-hint align="end">{{ rowNote.value.length}}/75</mat-hint>
    </mat-form-field>
  </mat-grid-tile>
</mat-grid-list>

<table #table mat-table [dataSource]="dataSource.data">
  <ng-container matColumnDef="id">
    <th mat-header-cell *matHeaderCellDef>
      #
    </th>
    <td mat-cell *matCellDef="let element"> {{element.id}} </td>
  </ng-container>

  <ng-container matColumnDef="productCode">
    <th mat-header-cell *matHeaderCellDef>
      Codice Prodotto
    </th>
    <td mat-cell *matCellDef="let element"> {{element.productCode}} </td>
  </ng-container>

  <ng-container matColumnDef="productName">
    <th mat-header-cell *matHeaderCellDef>
      Nome prodotto
    </th>
    <td mat-cell *matCellDef="let element"> {{element.productName}} </td>
  </ng-container>

  <ng-container matColumnDef="aliasDescription">
    <th mat-header-cell *matHeaderCellDef>
      Alias
    </th>
    <td mat-cell *matCellDef="let element"> {{element.aliasDescription}} </td>
  </ng-container>

  <ng-container matColumnDef="amount">
    <th mat-header-cell *matHeaderCellDef>
      Quantità
    </th>
    <td mat-cell *matCellDef="let element"> {{element.amount}} </td>
  </ng-container>

  <ng-container matColumnDef="initialPrice">
    <th mat-header-cell *matHeaderCellDef>
      Prezzo di listino
    </th>
    <td mat-cell *matCellDef="let element"> {{element.initialPrice}} </td>
  </ng-container>

  <ng-container matColumnDef="discount">
    <th mat-header-cell *matHeaderCellDef>
      Sconto
    </th>
    <td mat-cell *matCellDef="let element"> {{element.discount}} </td>
  </ng-container>

  <ng-container matColumnDef="unitPrice">
    <th mat-header-cell *matHeaderCellDef>
      Prezzo scontato
    </th>
    <td mat-cell *matCellDef="let element"> {{element.unitPrice}} </td>
  </ng-container>

  <ng-container matColumnDef="totalPrice">
    <th mat-header-cell *matHeaderCellDef>
      Prezzo totale
    </th>
    <td mat-cell *matCellDef="let element"> {{ getTotalRow(element) | currency:'EUR' }} </td>
  </ng-container>

  <ng-container matColumnDef="comment">
    <th mat-header-cell *matHeaderCellDef>
      Comment
    </th>
    <td mat-cell *matCellDef="let element"> {{element.comment}} </td>
  </ng-container>

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

<mat-paginator 
  *ngIf="dataSource.data.length > 0"
  [length]="dataSource.data.length"
  [pageSizeOptions]="[2, 10, 20]"
  showFirstLastButtons
  aria-label="Seleziona la pagina degli ordini"></mat-paginator>

<div class="row">
  <div>
    <h5>Quantità totale</h5>
    {{ getTotalQuantity() }}
  </div>
  <div>
    <h5>Importo totale</h5>
    {{ getTotalCost() | currency:'EUR' }}
  </div>
</div>

order.component.ts:

import { Component, Inject, ViewChild, ElementRef, AfterViewInit, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';

import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';

import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatTable } from '@angular/material/table';
import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';


export interface Customer {
  name: string;
  code: string;
}

export interface OrderRow {
  id: number;
  type: string;
  productCode: string;
  productName: string;
  amount: number;
  initialPrice: number;
  unitPrice: number;
  discount: string;
  aliasCode: string;
  aliasDescription: string;
  totalPrice: number;
  comment: string;
}

const ELEMENT_DATA: OrderRow[] = [];

@Component({
  selector: 'app-order',
  templateUrl: './order.component.html',
  styleUrls: ['./order.component.scss']
})

export class OrderComponent implements AfterViewInit, OnInit {
  customer: Customer = { name: "01 PROVA CLIENTE", code: "029.19599" }
  row = {} as OrderRow;

  myControl = new FormControl('');
  
  displayedColumns: string[] = ['id', 'productCode', 'productName', 'aliasDescription', 'amount', 'initialPrice', 'discount', 'unitPrice', 'totalPrice'];
  options: string[] = ['One', 'Two', 'Three', 'productCode', 'productName', 'amount', 'initialPrice'];
  filteredOptions: Observable<string[]>;
  dataSource = new MatTableDataSource(ELEMENT_DATA);

  @ViewChild('table', { static: true, read: MatTable }) table: any;
  @ViewChild(MatPaginator, {read: true}) paginator: MatPaginator;
  @ViewChild('autoInput', { static: false })
  set autoInput(element: ElementRef<HTMLInputElement>) {
    if(element) {
      element.nativeElement.focus()
    }
  }

  constructor(public dialog: MatDialog) { }

  ngOnInit() {
    this.filteredOptions = this.myControl.valueChanges.pipe(
      startWith(''),
      map(value => this._filter(value || '')),
    );
  }

  ngAfterViewInit() {
    this.dataSource.paginator = this.paginator;
  }

  private _filter(value: string): string[] {
    if (value.length < 3) { return []}
    const filterValue = value.toLowerCase();
    return this.options.filter(option => option.toLowerCase().includes(filterValue));
  }

  openHelpDialog() {
    this.dialog.open(HelpDialog);
  }

  openDialog(event: MatAutocompleteSelectedEvent): void {
    const dialogRef = this.dialog.open(AddProductDialog, {
      data: {
        id: this.dataSource.data.length + 1,
        type: "product",
        productCode: 2,
        productName: event.option.value,
        amount: 2,
        initialPrice: 2,
        unitPrice: 2,
        discount: "20+10",
        aliasCode: this.row.aliasCode,
        aliasDescription: this.row.aliasDescription,
      },

    });

    dialogRef.afterClosed().subscribe(result => {
      if (result != undefined && Object.values(result).some(value => value != undefined && value != "" && value != null)) {
        const data = this.dataSource.data;
        data.unshift(result);
        this.dataSource.data = data;
        this.table.renderRows();
        this.myControl.reset();
        this.dataSource.paginator = this.paginator;
      }
    });
  }

  onEscape(e: any): void {
    e.target.value = '';
  }

  onSaveRowNote(e: any): void {
    let noteRow = {} as OrderRow;
    noteRow.id =  this.dataSource.data.length + 1;
    noteRow.type = 'comment';
    noteRow.amount = 0;

    noteRow.productCode = "";
    noteRow.productName = "";
    noteRow.aliasCode = "";
    noteRow.aliasDescription = "";

    noteRow.initialPrice = 0;
    noteRow.discount = "0";
    noteRow.unitPrice = 0;

    noteRow.comment = e.target.value;

    this.dataSource.data.unshift(noteRow);
    this.table.renderRows();
    this.onEscape(e);
  }

  getTotalCost() {
    return  this.dataSource.data.map(t => t.amount * t.unitPrice).reduce((acc, value) => acc + value, 0);
  }

  getTotalQuantity() {
    return  this.dataSource.data.map(t => t.amount * 1).reduce((acc, value) => acc + value, 0);
  }

  getTotalRow(element: any) {
    return element.amount * element.unitPrice;  
  }

}

@Component({
  selector: 'app-order-help-dialog',
  templateUrl: 'help-dialog.component.html',
})
export class HelpDialog {

  constructor(
    public dialogRef: MatDialogRef<HelpDialog>
  ) {}

  onNoClick(): void {
    this.dialogRef.close();
  }

}

@Component({
  selector: 'app-order-add-product',
  templateUrl: 'add-product-dialog.html',
  host: {
    '(document:keydown)': 'handleKeyboardEvent($event)'
  }
})
export class AddProductDialog {
  constructor(
    public dialogRef: MatDialogRef<AddProductDialog>,
    @Inject(MAT_DIALOG_DATA) public data: OrderRow,
  ) {}

  onNoClick(): void {
    this.dialogRef.close();
  }

  handleKeyboardEvent(event: KeyboardEvent): void {
    if (event.key === "F10"){
      event.preventDefault();
      console.log(this.data)
      this.dialogRef.close((result: any) => { result });
    }
  }

}

add-product-dialog.html

<h1 mat-dialog-title>Seleziona prodotto</h1>
<div mat-dialog-content>
  <mat-form-field appearance="standard">
    <mat-label>Codice prodotto</mat-label>
    <input matInput [(ngModel)]="data.productCode">
  </mat-form-field>
  <mat-form-field appearance="standard">
    <mat-label>Nome prodotto</mat-label>
    <input matInput [(ngModel)]="data.productName">
  </mat-form-field>
  <mat-form-field appearance="standard">
    <mat-label>Quantità</mat-label>
    <input matInput [(ngModel)]="data.amount">
  </mat-form-field>
  <mat-form-field appearance="standard">
    <mat-label>Prezzo iniziale</mat-label>
    <input matInput [(ngModel)]="data.initialPrice">
  </mat-form-field>
  <mat-form-field appearance="standard">
    <mat-label>Prezzo unitario</mat-label>
    <input matInput [(ngModel)]="data.unitPrice">
  </mat-form-field>
  <mat-form-field appearance="standard">
    <mat-label>Sconto</mat-label>
    <input matInput [(ngModel)]="data.discount">
  </mat-form-field>
  <mat-form-field appearance="standard">
    <mat-label>Codice EAN</mat-label>
    <input matInput [(ngModel)]="data.aliasCode">
  </mat-form-field>
  <mat-form-field appearance="standard">
    <mat-label>Descrizione Alias</mat-label>
    <input matInput [(ngModel)]="data.aliasDescription">
  </mat-form-field>
</div>
<div mat-dialog-actions>
  <button mat-button (click)="onNoClick()">Annulla</button>
  <button mat-button [mat-dialog-close]="data">Aggiungi</button>
</div>
1

There are 1 best solutions below

2
On

looks like you are trying to modify dataSource.data directly. You can read about this issue on Angular Materials Github here

I'd recommend you try to update it like so:

const newData = this.dataSource.data;
newData.push(newRowItem);
this.dataSource.data = newData

I've also created a Stackblitz which you look at to see how I solved it.