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.