1
votes

I'm trying to follow the material example and implement it on my own data, the table renders like it should but pagination, sorting, and filtering aren't working. You can write in the filter box, change amount per page, and press to sort and get an arrow pointing up or down, but nothing changes with the table. I think it has something to do with how I load my data, but I'm unsure exactly what's going on.

Here's my component:

import { Component, OnInit, ViewChild } from '@angular/core';
import { first } from 'rxjs/operators';
import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator, MatSort } from '@angular/material';

import { Invoice } from '../_models';
import { InvoiceService } from '../_services';

@Component({
  selector: 'app-invoices',
  templateUrl: './invoices.component.html',
  styleUrls: ['./invoices.component.css']
})
export class InvoicesComponent implements OnInit {
  displayedColumns=['rating', 'amount', 'debtor', 'serial', 'dateout', 'expiration', 'daysleft', 'fid']
  invoices: Invoice[] = [];
  dataSource= new MatTableDataSource<Invoice>(this.invoices);

  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;

  constructor(
    private invoiceService: InvoiceService
  ) { }

  ngOnInit() {
    this.loadInvoices();
    this.dataSource.paginator = this.paginator;
    this.dataSource.sort = this.sort;
  }
  private loadInvoices(){
    this.invoiceService.getUserInvoices().pipe(first()).subscribe(invoices => {
      this.invoices=invoices;
    });

  }
  applyFilter(filterValue: string) {
    filterValue = filterValue.trim(); // Remove whitespace
    filterValue = filterValue.toLowerCase(); // Datasource defaults to lowercase matches
    this.dataSource.filter = filterValue;
    if (this.dataSource.paginator) {
      this.dataSource.paginator.firstPage();
    }
  }

}

And here is the html:

<h3> All Uploaded invoices:</h3>
<mat-form-field>
    <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
</mat-form-field>
<div class="mat-elevation-z8">
  <mat-table #table [dataSource]="invoices" matSort class="mat-elevation-z8">

    <ng-container matColumnDef="rating">
      <mat-header-cell *matHeaderCellDef mat-sort-header> Rating </mat-header-cell>
      <mat-cell *matCellDef="let invoice"> {{invoice.rating}} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="amount">
      <mat-header-cell *matHeaderCellDef mat-sort-header> Amount </mat-header-cell>
      <mat-cell *matCellDef="let invoice"> {{invoice.amount}} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="debtor">
      <mat-header-cell *matHeaderCellDef> Debtor </mat-header-cell>
      <mat-cell *matCellDef="let invoice"> {{invoice.debtor}} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="serial">
      <mat-header-cell *matHeaderCellDef> Serial </mat-header-cell>
      <mat-cell *matCellDef="let invoice"> {{invoice.serial}} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="dateout">
      <mat-header-cell *matHeaderCellDef> Dateout </mat-header-cell>
      <mat-cell *matCellDef="let invoice"> {{invoice.dateout}} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="expiration">
      <mat-header-cell *matHeaderCellDef> Expiration </mat-header-cell>
      <mat-cell *matCellDef="let invoice"> {{invoice.expiration}} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="daysleft">
      <mat-header-cell *matHeaderCellDef mat-sort-header > Days left </mat-header-cell>
      <mat-cell *matCellDef="let invoice"> {{invoice.daysleft}} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="fid">
      <mat-header-cell *matHeaderCellDef> Fid </mat-header-cell>
      <mat-cell *matCellDef="let invoice"> {{invoice.fid}} </mat-cell>
    </ng-container>


    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
    <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
  </mat-table>
  <mat-paginator [pageSizeOptions]="[3, 100, 200]" showFirstLastButtons></mat-paginator> 
</div>
<p><a [routerLink]="['/login']">Logout</a></p>

Lastly the invoice model:

export class Invoice {
    id: any;
    rating: any;
    serial: any;
    amount: any;
    debtor: any;
    dateout: any;
    expiration: any;
    daysleft: any;
    fid: any;
    invoicefile: File;
}

Any insight into what might be going wrong?

2

2 Answers

2
votes

I guess it doesn't work because the data source is not configured correctly. Also you should not use MatTableDataSource for server based data as explained in

https://blog.angular-university.io/angular-material-data-table/

which contains a very good walk thru to build a Material Table example with server side data retrieval.

0
votes

I needed to solve a very similar problem which involved sorting data locally for mat-table from a server via an http.post (or http.get) request, executed from a standalone service. I used MatTableDataSource to do this against the advice of Andreas (above).

Make sure you import these to the app.module.ts:


import { HttpClientModule } from '@angular/common/http';
import { MatTableModule } from '@angular/material/table';
import { MatSortModule } from '@angular/material';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';

@NgModule({

  ....,

  imports: [
    ....,
    HttpClientModule,
    MatTableModule,
    MatSortModule,
    MatProgressSpinnerModule,
  ],

  ....,

})

My html (app.component.html):

<div class="example-container mat-elevation-z8">

    <div class="example-loading-shade"
       *ngIf="isLoadingResults">
        <mat-spinner *ngIf="isLoadingResults" color="accent"></mat-spinner>
    </div>

    <div class="example-table-container">
      <table mat-table matSort [dataSource]="datasrc" hover="true" class="example-table">
        <ng-container matColumnDef="name" sticky>
          <th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
          <td mat-cell *matCellDef="let element"> {{element.name}} </td>
        </ng-container>
        <ng-container matColumnDef="age">
          <th mat-header-cell *matHeaderCellDef mat-sort-header> Age </th>
          <td mat-cell *matCellDef="let element"> {{element.age}} </td>
        </ng-container>
        <tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
        <tr mat-row 
            *matRowDef="let row; let i = index; columns: displayedColumns;" 
            (click)="onSelectRow(i)"
            class="example-element-row"></tr>
      </table>
    </div>
</div>

The app component (app.component.ts):

import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { merge, Observable, of as observableOf } from 'rxjs';
import { catchError, map, startWith, switchMap } from 'rxjs/operators';

import { MatSort, MatTableDataSource, MatTable } from '@angular/material';
import { MatTableModule } from '@angular/material/table';

import { MyDataService } from './mydata.service';
import { Person, RootObject } from './mydata.interface';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  displayedColumns: string [] = [
    'name',
    'age',
  ];

  mydataDB: MyDataService | null;
  dataSource: Person[] = [];
  datasrc = new MatTableDataSource<object>(this.dataSource);
  isLoadingResults = true;

  @ViewChild(MatSort) sort: MatSort;
  @ViewChild(MatTable) table: MatTable<any>;

  constructor(
    private myDataService: MyDataService,
    private http: HttpClient
    ) { }

  ngAfterViewInit() {
    this.updateTable();
  }

  updateTable() {
    this.mydataDB = new MyDataService(this.http);
      merge(this.sort.sortChange)
        .pipe(
          startWith({}),
          switchMap(() => {
            this.isLoadingResults = true;
            return this.mydataDB!.getData();
          }),
          map(data => {
            this.isLoadingResults = false;
            return data;
          }),
          catchError(() => {
            this.isLoadingResults = false;
            return observableOf([]);
          })
        ).subscribe(data => {
          this.dataSource = data['response'];

          this.datasrc = new MatTableDataSource(this.dataSource);
          this.datasrc.sort = this.sort;
          this.table.renderRows();
         });
  }
}

The interface (mydata.interface.ts):

export interface RootObject {
    response: Person
}

export interface Person {
    name: string;
    age: number;
}

My data service (mydata.service.ts):

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, Subscriber } from 'rxjs';
import { tap, map, filter } from 'rxjs/operators';

import { RootObject } from './remittances.interface';

@Injectable()
export class MyDataService{
    //Change this to the url you need
    public httpRoot = 'http://www.myapi.com/api/';
    private postBody: {};

    constructor(
        private http: HttpClient,
        ) {}

    getData(): Observable<RootObject[]> {
        //Add any APi parameters here if needed
        this.postBody = {
            "name":"getPeopleAPI",
            "params":{
                "filter":"country"
            }
        };
        return this.http.post<RootObject[]>(
            this.httpRoot,
            this.postBody,
            {
                headers: new HttpHeaders({
                    'Content-Type': 'application/json',
                })
            }
        );
    }
}

And finally the CSS (app.component.css):

.example-container {
  position: relative;
  min-height: 400px;
}

.example-table-container {
  position: relative;
  max-height: 400px;
  overflow: auto;
}

.example-loading-shade {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  background: rgba(0, 0, 0, 0.15);
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Column Widths */
.mat-column-number,
.mat-column-state {
  max-width: 64px;
}

.mat-column-created {
  max-width: 124px;
}

You will need to modify the interfaces to match the data you need from the API and fit it appropriately to the table. A great tool for this is http://www.jsontots.com/ .

Whenever you need to change the data in the table you can just call this.updateTable() function in the app.component.ts and pass variables through to getData() in mydata.service.ts to send them as parameters to the API. This took me way to long to get working well so hope this helps someone.