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

607 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
kottartillsalu 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.