HalitTalha / ng-material-extensions

Extended features for @angular/material components
Apache License 2.0
101 stars 52 forks source link

Cannot read property 'exportTable' of undefined - Angular 7 #33

Closed kristinhorton closed 4 years ago

kristinhorton commented 4 years ago

Sample code gives me this error:

ERROR TypeError: Cannot read property 'exportTable' of undefined at Object.eval [as handleEvent] (AppsComponent.html:114) at handleEvent (core.js:23107) at callWithDebugContext (core.js:24177) at Object.debugHandleEvent [as handleEvent] (core.js:23904) at dispatchEvent (core.js:20556) at core.js:21003 at HTMLButtonElement. (platform-browser.js:993) at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:423) at Object.onInvokeTask (core.js:17290) at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:422)

Here is my import into app.module.ts

`import {MatTableExporterModule} from 'mat-table-exporter';

@NgModule({ declarations: [ ], imports: [ MatTableExporterModule ], providers: [ApplicationService], bootstrap: [AppComponent] }) export class AppModule {}`

Here is my mat-table and mat-button from my table.html file `<mat-table matTableExporter class="mat-elevation-z4" [dataSource]="dataSource" *ngIf="dataSource.data.length > 0"

exporter="matTableExporter">

............. </mat-table

<button mat-button (click)="exporter.exportTable('csv')">Export `

HalitTalha commented 4 years ago

Thanks for reporting. I'll look into this soon, until then could you please check whether it works without an *ngIf directive, or you can set it true so that it always renders?

Guanaco77 commented 4 years ago

@HalitTalha I had the same issue. Removing the *ngIf directive solved it.

HalitTalha commented 4 years ago

@Guanaco77 Thanks for confirming. It turns out this is an expected behavior as per Angular docs. Template referance variables are accessible inside a template. By wrapping a component with a structural directive you happen to wrap it inside a different template and this causes the template referance variable's scope to narrow.

For example your table gets rendered to this

<ng-template [ngIf]="dataSource.data.length > 0">
<mat-table
matTableExporter
class="mat-elevation-z4"
[dataSource]="dataSource"
#exporter="matTableExporter">
</ng-template>

Since your button is out of this <ng-template/>, the exporter is undefined for it.

TLDR: If you have to use ngIf, the only sane solution is placing the export button inside the same template (the same wrapper that has the *ngIf in it) so that it can access the template referance variable. This will of course complicate your design if you need to stick with a well defined UX.

<div *ngIf="dataSource.data.length > 0">
    <mat-table matTableExporter class="mat-elevation-z4" [dataSource]="dataSource" #exporter="matTableExporter">
    <button mat-button (click)="exporter.exportTable('csv')">Export
</div>
kristinhorton commented 4 years ago

Thank you so much for your help! I was able to get it working by moving the button as you said.

RinDman77 commented 4 years ago

Sorry if it's closed. But with the latest angular feature, you can use ViewChild static : false and the button can remain outside of the *ngIf

ts (Component) : import { MatTableExporterDirective } from 'mat-table-exporter'; … @ViewChild('appTable', { static: false }) appTable: MatTableExporterDirective;

html : <button mat-icon-button (click)="appTable.exportTable('XLSX', { fileName : 'excel_blablafileName'})" matTooltip="Export Excel">

save_alt
    </button>

<table mat-table *ngIf="applications!==null" matTableExporter #appTable="matTableExporter" [dataSource]="applications" matSort class="mpbTable"> ….