angularangular-servicesangular-httpangular-ng-ifangular-ng

Displaying Specific Data in Angular 2/4


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


Solution

  • 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.