Reputation: 6289
I am trying to abstract out a tabular-data display to make it a child component that can be loaded into various parent components. I'm doing this to make the overall app "dryer". Before I was using an observable to subscribe to a service and make API calls and then printing directly to each component view (each of which had the tabular layout). Now I want to make the tabular data area a child component, and just bind the results of the observable for each of the parent components. For whatever reason, this is not working as expected.
Here is what I have in the parent component view:
<div class="page-view">
<div class="page-view-left">
<admin-left-panel></admin-left-panel>
</div>
<div class="page-view-right">
<div class="page-content">
<admin-tabs></admin-tabs>
<table-display [records]="records"></table-display>
</div>
</div>
</div>
And the component file looks like this:
import { API } from './../../../data/api.service';
import { AccountService } from './../../../data/account.service';
import { Component, OnInit, Input } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { TableDisplayComponent } from './../table-display/table-display.component';
@Component({
selector: 'account-comp',
templateUrl: 'app/views/account/account.component.html',
styleUrls: ['app/styles/app.styles.css']
})
export class AccountComponent extends TabPage implements OnInit {
private section: string;
records = [];
errorMsg: string;
constructor(private accountService: AccountService,
router: Router,
route: ActivatedRoute) {
}
ngOnInit() {
this.accountService.getAccount()
.subscribe(resRecordsData => this.records = resRecordsData,
responseRecordsError => this.errorMsg = responseRecordsError);
}
}
Then, in the child component (the one that contains the table-display view), I am including an @Input() for "records" - which is what the result of my observable is assigned to in the parent component. So in the child (table-display) component, I have this:
import { AccountService } from './../../../data/account.service';
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'table-display',
templateUrl: './table-display.component.html',
styleUrls: ['./table-display.component.less']
})
export class TableDisplayComponent {
@Input() records;
constructor() {
}
}
Lastly, here's some of the relevant code from my table-display view:
<tr *ngFor="let record of records; let i = index;">
<td>{{record.name.first}} {{record.name.last}}</td>
<td>{{record.startDate | date:"MM/dd/yy"}}</td>
<td><a class="bluelink" [routerLink]="['/client', record._id ]">{{record.name.first}} {{record.name.last}}</a></td>
When I use it with this configuration, I get "undefined" errors for the "records" properties I'm pulling in via the API/database. I wasn't getting these errors when I had both the table display and the service call within the same component. So all I've done here is abstract out the table-display so I can use it nested within several parent components, rather than having that same table-display show up in full in every parent component that needs it.
What am I missing here? What looks wrong in this configuration?
Upvotes: 1
Views: 271
Reputation: 3612
You need to protect against record being null until it comes in to your child component (and therefore it's view).
Use Elvis operators to protect your template:
<tr *ngFor="let record of records; let i = index;">
<td>{{record?.name?.first}} {{record?.name?.last}}</td>
<td>{{record?.startDate | date:"MM/dd/yy"}}</td>
<td><a class="bluelink" [routerLink]="['/client', record?._id ]"> {{record?.name?.first}} {{record?.name?.last}}</a></td>
You can also assign your input to an empty array to help with this issue:
@Input() records = [];
Upvotes: 2