I am trying to simply display data from a MySQL database using Angular (5) with a Material table. The code I have compiles successfully, and a table with the column names and pagination buttons appears, but there is no data shown in the table. I know that the API call is returning data correctly, and I know that the service is calling the API (I can see the correct data in the network inspector in the browser). There is an error shown in the browser of
TypeError: _co.unitTypeDatabase.data is undefined
Stack trace:
View_UnitTypeComponent_0/<@ng:///AppModule/UnitTypeComponent.ngfactory.js:136:9 ...
It would appear that the error is referring to the part in the html code referring to unitTypeDatabase.data.length. If that is undefined, then it would explain why there is no data in the table as well. What I can't figure out is why that is undefined. I think it has to do with how/when/where I am creating the new UnitTypeDatabase
, but given that the service is successfully being called I'm not sure where to go from here. Here is the code I have:
unit-type.component.ts
import { Component, OnInit, ViewChild } from '@angular/core';
import { UnitType } from './unit-type';
import { UnitTypeService } from './unit-type.service';
import {DataSource} from '@angular/cdk/collections';
import {MdPaginator} from '@angular/material';
import {Observable} from 'rxjs/Observable';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
@Component({
selector: 'app-unit-type',
templateUrl: './unit-type.component.html',
styleUrls: ['./unit-type.component.scss'],
providers: [UnitTypeService]
})
export class UnitTypeComponent implements OnInit {
public displayedColumns = ['unitTypeID', 'unitTypeName'];
public unitTypeDatabase: UnitTypeDatabase | null;
public dataSource: UnitTypeDataSource | null;
@ViewChild(MdPaginator) paginator: MdPaginator;
constructor(private unitTypeService: UnitTypeService) {}
ngOnInit() {
this.unitTypeDatabase = new UnitTypeDatabase(this.unitTypeService);
this.dataSource = new UnitTypeDataSource(this.unitTypeDatabase, this.paginator);
}
}
export class UnitTypeDataSource extends DataSource<UnitType> {
constructor(private _unitTypeDatabase: UnitTypeDatabase, private _paginator: MdPaginator) {
super();
}
connect(): Observable<UnitType[]> {
const displayDataChanges = [
this._unitTypeDatabase.dataChange,
this._paginator.page
];
return Observable.merge(...displayDataChanges).map(() => {
const data = this._unitTypeDatabase.data.slice();
const startIndex = this._paginator.pageIndex * this._paginator.pageSize;
return data.splice(startIndex, this._paginator.pageSize);
})
}
disconnect() {}
}
export class UnitTypeDatabase {
/** Stream that emits whenever the data has been modified. */
public dataChange: BehaviorSubject<UnitType[]> = new BehaviorSubject<UnitType[]>([]);
get data(): UnitType[] { return this.dataChange.value; }
constructor(unitTypeService: UnitTypeService) {
unitTypeService.getAllUnitTypes().subscribe(data => this.dataChange.next(data));
}
}
unit-type.component.html
<div class="example-container mat-elevation-z8">
<md-table #table [dataSource]="dataSource">
<!-- ID Column -->
<ng-container mdColumnDef="unitTypeID">
<md-header-cell *mdHeaderCellDef> ID </md-header-cell>
<md-cell *mdCellDef="let row"> {{row.unitTypeID}} </md-cell>
</ng-container>
<!-- Name Column -->
<ng-container mdColumnDef="unitTypeName">
<md-header-cell *mdHeaderCellDef> Name </md-header-cell>
<md-cell *mdCellDef="let row"> {{row.unitTypeName}} </md-cell>
</ng-container>
<md-header-row *mdHeaderRowDef="displayedColumns"></md-header-row>
<md-row *mdRowDef="let row; columns: displayedColumns;"></md-row>
</md-table>
<md-paginator #paginator
[length]="unitTypeDatabase.data.length"
[pageIndex]="0"
[pageSize]="25"
[pageSizeOptions]="[5, 10, 25, 100]">
</md-paginator>
</div>
unit-type.ts
export class UnitType {
constructor(
public unitTypeID: number,
public unitTypeName: string
){}
}
unit-type.service.ts
import { Injectable } from '@angular/core';
import { UnitType } from './unit-type';
import { Headers, Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class UnitTypeService {
private unit_types_Url = 'api/unit-types'; // URL to web api
private headers = new Headers({'Content-Type': 'application/json'});
constructor(private http: Http) { }
getAllUnitTypes(): Observable<UnitType[]> {
return this.http.get(this.unit_types_Url)
.map(response => response.json().data as UnitType[]);
}
}
I started with the example from the Material site, but that does not retrieve data using a service. I also tried to match the question and answer from How to use md-table with services in Angular 4 but I must be missing something. Hopefully it is an obvious and simple error or misunderstanding that someone can quickly spot. Thanks!
Edit The full stack trace is:
ERROR TypeError: _this._unitTypeDatabase.data is undefined
Stack trace:
[208]/UnitTypeDataSource.prototype.connect/<@http://localhost:4200/main.bundle.js:93:17
MapSubscriber.prototype._next@http://localhost:4200/vendor.bundle.js:51417:22
Subscriber.prototype.next@http://localhost:4200/vendor.bundle.js:37214:13
OuterSubscriber.prototype.notifyNext@http://localhost:4200/vendor.bundle.js:48573:9
InnerSubscriber.prototype._next@http://localhost:4200/vendor.bundle.js:117362:9
Subscriber.prototype.next@http://localhost:4200/vendor.bundle.js:37214:13
Subject.prototype.next@http://localhost:4200/vendor.bundle.js:26457:17
BehaviorSubject.prototype.next@http://localhost:4200/vendor.bundle.js:49978:9
UnitTypeDatabase/<@http://localhost:4200/main.bundle.js:122:78
SafeSubscriber.prototype.__tryOrUnsub@http://localhost:4200/vendor.bundle.js:37363:13
SafeSubscriber.prototype.next@http://localhost:4200/vendor.bundle.js:37310:17
Subscriber.prototype._next@http://localhost:4200/vendor.bundle.js:37250:9
Subscriber.prototype.next@http://localhost:4200/vendor.bundle.js:37214:13
MapSubscriber.prototype._next@http://localhost:4200/vendor.bundle.js:51423:9
Subscriber.prototype.next@http://localhost:4200/vendor.bundle.js:37214:13
onLoad@http://localhost:4200/vendor.bundle.js:53409:21
ZoneDelegate.prototype.invokeTask@http://localhost:4200/polyfills.bundle.js:6443:17
onInvokeTask@http://localhost:4200/vendor.bundle.js:4914:24
ZoneDelegate.prototype.invokeTask@http://localhost:4200/polyfills.bundle.js:6442:17
Zone.prototype.runTask@http://localhost:4200/polyfills.bundle.js:6242:28
ZoneTask/this.invoke@http://localhost:4200/polyfills.bundle.js:6496:28
The problem is in unit-type.service.ts. It should be:
I missed the fact that there was no data object as part of the returned JSON, so as soon as that was removed all the information was visible and correctly displayed in the table.
Try to use an elvis operator
?
in the template to begin with. If it is just the template issue, that should get your problem solved.The reason is when the view is rendered there is no
unitTypeDatabase
defined yet as it is only initialized in thengOnInit()
. See if that fixes your issue.