I have an project api that either contains material_projects or project_services. I am confused on how would i display either one of them. If i display material_projects then i mustn't display project_services. project_services must be an empty in the api.
getAllProjects() {
this.subscription = this.projectsService.getAll()
.subscribe(
(data:any) => {
this.projects = data.projects;
console.log(data);
},
error => {
console.log(error);
});
}
html
<div class="card-block" *ngFor="let project of projects | search : searchBOM">
<h2 class="proj-name">{{ project.name | titlecase }} </h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Material SKU</th>
<th>Material Name</th>
<th>Unit</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let innerItem of project.material_projects">
<td>{{innerItem.material.sku}}</td>
<td>{{innerItem.material.name}}</td>
<td>{{innerItem.unit}}</td>
</tr>
</tbody>
</table>
</div>
If you can guarantee that constraint, then you may be able to simply iterate over both lists.
<tbody>
<tr *ngFor="let innerItem of project.material_projects">
<td>{{innerItem.material.sku}}</td>
<td>{{innerItem.material.name}}</td>
<td>{{innerItem.unit}}</td>
</tr>
<tr *ngFor="let innerItem of project.project_services">
<td>{{innerItem.service.sku}}</td>
<td>{{innerItem.service.name}}</td>
<td>{{innerItem.unit}}</td>
</tr>
</tbody>
As an alternative, you may conditionally display one or the other:
<tbody>
<ng-container *ngIf="project.material_projects.length > 0; then #materialProjectsRows; else #projectServicesRows"></ng-container>
<ng-template #materialProjectsRows>
<tr *ngFor="let innerItem of project.material_projects">
<td>{{innerItem.material.sku}}</td>
<td>{{innerItem.material.name}}</td>
<td>{{innerItem.unit}}</td>
</tr>
</ng-template>
<ng-template #projectServicesRows>
<tr *ngFor="let innerItem of project.projectServices">
<td>{{innerItem.service.sku}}</td>
<td>{{innerItem.service.name}}</td>
<td>{{innerItem.unit}}</td>
</tr>
</ng-template>
</tbody>
Or, if the DTOs are similar enough, you may consider sharing more of the view logic:
<tbody>
<!-- You may want to perform the concatenation inside of the view model for additional clarity -->
<tr *ngFor="let innerItem of project.material_projects.concat(project.project_services)">
<td>{{(innerItem.material || innerItem.service).sku}}</td>
<td>{{(innerItem.material || innerItem.service).name}}</td>
<td>{{innerItem.unit}}</td>
</tr>
</tbody>
EDIT:
If you would like to use different tables based on the existence of properties, then you will want to move your *ngIf
statements up to the <table>
element or an immediate child. Based on your comments, you might try something like the following:
<div class="card-block" *ngFor="let project of projects | search : searchBOM">
<ng-container *ngIf="getProjectType(project)">
<h2 class="proj-name">{{ project.name | titlecase }}</h2>
<table *ngIf="getProjectType(project) === 'material'" class="table table-bordered table-striped">
<thead>
<tr>
<th>Material SKU</th>
<th>Material Name</th>
<th>Unit</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let innerItem of project.material_projects">
<td>{{innerItem.material.sku}}</td>
<td>{{innerItem.material.name}}</td>
<td>{{innerItem.unit}}</td>
</tr>
</tbody>
</table>
<table *ngIf="getProjectType(project) === 'services'" class="table table-bordered table-striped">
<thead>
<tr>
<th>Project SKU</th>
<th>Project Name</th>
<th>Unit</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let innerItem of project.project_services">
<td>{{innerItem.project.sku}}</td>
<td>{{innerItem.project.name}}</td>
<td>{{innerItem.unit}}</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
Component code:
export class ProjectComponent {
...
public getProjectType(project: Project): 'material' | 'services' | null {
return project.material_projects.length > 0 ? 'material'
: project.project_services.length > 0 ? 'services'
: null;
}
...
}
There are many ways to do this. Another option would be to use a switch statement in your template, or adding additional child ("dummy") components to handle the behavior.