0

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>

enter image description here

1 Answers1

0

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.

Mike Hill
  • 3,622
  • 23
  • 27
  • Actually, the material_projects and projects_services have different theads. and if their is neither material_projects and project_services then do not display anything –  Oct 20 '17 at 17:39
  • How can you make 3 conditions like if there is neither material_projects or project_services, then do not display that certain project? –  Oct 20 '17 at 18:08
  • @GraySingh, if you need to add a third condition then you can add another `*ngIf` structural directive to a parent node. See edits. There are many ways to solve the same problem in Angular, so there are many alternative solutions to writing this component. You might also consider abstracting things (such as the project display) into sub-components. Separating content into smaller dummy components like that is a common pattern for the contents of `*ngFor` directives. – Mike Hill Oct 20 '17 at 19:10
  • This is what I'm looking for. Thank you. –  Oct 21 '17 at 06:03