简体   繁体   中英

How should I render a Angular MatTable with pagination only after data is received from a REST API?

I am attempting to create a table component where a user can add a REST endpoint to be loaded into the data. The data is expected to be an array of arbitrary objects, so no strict schema is necessarily possible here.

The issue is that I cannot get the table and it's paginator to wait to render until after I have made a request on the REST endpoint. I have a version that gets the pagination working, but the paginator (and the empty table) still appear before data is loaded.

I've tried using AfterViewChecked, as well as various different places of initializing the paginator and table.

Stackblitz sample here. Note that you don't have to enter a valid endpoint in the modal, as it is fetching from a dummy one. I left the modal in as close to my default state as possible.

The component.ts file:

import { Component, OnInit, ViewChild } from '@angular/core';
import { MatPaginator, MatTable, MatTableDataSource } from '@angular/material';
import { DomSanitizer } from '@angular/platform-browser';
import { RestService } from '../rest.service';

@Component({
    providers: [RestService],
    selector: 'table-widget',
    styleUrls: ['./table-widget.component.scss'],
    templateUrl: './table-widget.component.html',
})

export class TableWidgetComponent implements OnInit {
    public dataSource: MatTableDataSource<any> = new MatTableDataSource();
    // map of header names (which are keys in series) to whether they should be visible
    public headers: Map<string, boolean>;
    public headerKeys: string[];

    @ViewChild(MatTable) private table: MatTable<any>;
    @ViewChild(MatPaginator) private paginator: MatPaginator;

    // array of header titles, to get arround ExpressionChangedAfterItHasBeenCheckedError
    private restEndpoint: string;
    private editModalVisibility: boolean;

    constructor(private restService: RestService, private sanitizer: DomSanitizer) { }

    // called each time an AppComponent is initialized
    public ngOnInit(): void {
        this.headers = new Map<string, boolean>();
        this.editModalVisibility = false;
        this.dataSource.paginator = this.paginator;
    }

    public eventFromChild(data: string): void {
        this.restEndpoint = data;
        // return value of rest call
        this.restService.getFromEndpoint(this.restEndpoint)
            .subscribe((series) => {
                this.dataSource.data = series;
                this.table.renderRows();

                // set keys, unless series is empty
                if (series.length > 0) {
                    Object.keys(this.dataSource.data[0]).forEach((value) => {
                        this.headers.set(value, true);
                    });
                }
                this.headerKeys = Array.from(this.headers.keys());
            });
    }
    .
    .
    .
}

and the HTML file:

<div [ngClass]="['table-widget-container', 'basic-container']">
  <div [ngClass]="['table-container']">
    <mat-table [ngClass]="['mat-elevation-z8']" [dataSource]="dataSource">
      <ng-container *ngFor="let header of getVisibleHeaders()" matColumnDef={{header}}>
        <mat-header-cell *matHeaderCellDef> {{header | titlecase}} </mat-header-cell>
        <mat-cell *matCellDef="let entry"> {{entry[header]}} </mat-cell>
      </ng-container>

      <mat-header-row *matHeaderRowDef="getVisibleHeaders()"></mat-header-row>
      <mat-row *matRowDef="let row; columns: getVisibleHeaders();"></mat-row>
    </mat-table>

    <mat-paginator #paginator [pageSize]="5" [pageSizeOptions]="[5, 11, 20]"></mat-paginator>

  </div>
  <button *ngIf="dataSource.data.length" (click)="openModal()" class="add-rest-button">
    Edit Table
  </button>
  <add-rest-button (sendDataToParent)="eventFromChild($event)"></add-rest-button>
</div>

When I attempt to add an *ngIf="dataSource.data.length" to the mat-table and mat-paginator , either the paginator is shown but not "linked" to the table, or the table appears with no data, and I get the following error:

TypeError: Cannot read property 'renderRows' of undefined

I understand that this error is occurring because the table isn't defined because of the ngIf directive, but I'm not sure how to otherwise effectively hide the table.

Add a property to your component isInitialized

Update your method to:

public eventFromChild(data: string): void {
        console.log(this.dataSource.data.length);
        this.restEndpoint = data;
        // TODO: handle errors of restService
        // return value of rest call
        this.restService.getFromEndpoint(this.restEndpoint)
            .subscribe((series) => {
               this.isInitialized = true;
               ...
             }

and your html to:

<div *ngIf="isInitialized" [ngClass]="['table-widget-container', 'basic-container']">
.
.
.
</div>

I found a way of solving this problem, but I wouldn't say it addresses the root issue; simply hide the container for the table and paginator before data is ready.

First, I changed line 2 in the HTML to this:

<div [ngClass]="['table-container']" [style.display]="getTableVisibleStyle()">

Then I added the following function to the TS file:

    public getTableVisibleStyle() {
        let style: string = 'none';
        if (this.tableVisibility) {
            style = 'block';
        }
        // have to do this to get past overzealous security
        return this.sanitizer.bypassSecurityTrustStyle(style);
    }

where tableVisibility is a boolean set to false in ngOnInit, then changed to true whenever the data is updated.

It does work fairly well, though it seems hacky and not particularly robust. I may revisit this later if I discover a better way of doing this.

You could do the following to accomplish this.

Don't initialize your dataSource variable.

public dataSource: MatTableDataSource<any>;

On the table-container you can *ngIf="dataSource"

<div [ngClass]="['table-container']" *ngIf="dataSource">

Then initialize the variable when the data is received.

this.dataSource = new MatTableDataSource();
this.dataSource.data = series;
this.table.renderRows();

To hide it again, null the variable

this.dataSource = null;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM