Search code examples
htmlcssangularhtml-table

How do I make sure that my table headers aligns with my table data in a nested html table?


For context, I need a nested table to group table data (by pdo, program, location). The problem is my th does not aligned with my nested td when the columns are not fully expanded. I'm using Angular 14 and I made sure that my th and td have the same width using [style.width.px]="columnWidths[i]".

When columns not fully expanded, th and td does not aligned table header table data

Component.html

<div class="container-parent overflow-auto">
  <table #parentTable class="table-hover table-parent w-100">
    <thead>
      <tr class="th-row">
        <ng-container *ngFor="let header of tableHeaders; let i = index">
          <th
            class="table-headers"
            *ngIf="i < 4"
            [ngClass]="{ 'sticky-left': i < 2 }"
            [style.display]="columns[i].visible ? '' : 'none'"
            [style.width.px]="columnWidths[i]"
          >
            <i
              class="material-icons align-middle"
              [style.cursor]="'pointer'"
              *ngIf="!columns[i + 1]?.visible && i < 3"
              (click)="toggleColumn(i, 'add')"
              >add_box</i
            >
            <i
              class="material-icons align-middle"
              [style.cursor]="'pointer'"
              *ngIf="columns[i + 1]?.visible"
              (click)="toggleColumn(i, 'remove')"
              >indeterminate_check_box</i
            >
            {{ header }}
          </th>
        </ng-container>
        <th class="table-headers" [style.width.px]="columnWidths[4]">
          {{ tableHeaders[4] }}
        </th>
      </tr>
    </thead>
    <tbody>
      <ng-container *ngIf="currentIndex == 0">
        <!-- PDO ROW -->
        <tr class="pdo-row" *ngFor="let pdo of tableData">
          <!-- PDO NAME -->
          <td
            class="td-headers"
            class="pdo-name-column sticky-left"
            [style.width.px]="columnWidths[0]"
            [style.display]="columns[0].visible ? '' : 'none'"
          >
            {{ pdo.name ?? '-' }}
          </td>
          <td class="td-headers" [colSpan]="tableHeaders.length - 1">
            <table class="w-100 h-100">
              <!-- UNIT ITEM ROW -->
              <tr class="unit-item-row">
                <!-- UNIT ITEM -->
                <td
                  class="unit-item-column"
                  [style.width.px]="columnWidths[3]"
                  [style.display]="columns[3].visible ? '' : 'none'"
                >
                  {{ programLocations?.unit_of_item ?? '-' }}
                </td>
                <!-- NO. ITEM -->
                <td class="num-item-column" [style.width.px]="columnWidths[4]">
                  {{ pdo.num_of_items_pdo | number : '1.0-0' ?? '-' }}
                </td>
              </tr>
            </table>
          </td>
        </tr>
      </ng-container>
      <ng-container *ngIf="currentIndex == 1">
        <!-- PDO ROW -->
        <tr class="pdo-row" *ngFor="let pdo of tableData">
          <!-- PDO NAME -->
          <td
            class="td-headers"
            class="pdo-name-column sticky-left"
            [style.width.px]="columnWidths[0]"
            [style.display]="columns[0].visible ? '' : 'none'"
          >
            {{ pdo.name ?? '-' }}
          </td>
          <td class="td-headers" [colSpan]="tableHeaders.length - 1">
            <table class="w-100 h-100">
              <!-- PROGRAM ROW -->
              <ng-container
                *ngFor="
                  let program of pdo.pdo_programs;
                  let programIndex = index
                "
              >
                <tr class="program-row">
                  <!-- PDO PROGRAM -->
                  <td
                    class="pdo-program-column sticky-left"
                    [style.width.px]="columnWidths[1]"
                    [style.display]="columns[1].visible ? '' : 'none'"
                  >
                    {{ program.program ?? '-' }}
                  </td>
                  <td>
                    <table class="w-100 h-100">
                      <!-- UNIT ITEM ROW -->
                      <tr class="unit-item-row">
                        <!-- UNIT ITEM -->
                        <td
                          class="unit-item-column"
                          [style.width.px]="columnWidths[3]"
                          [style.display]="columns[3].visible ? '' : 'none'"
                        >
                          {{ programLocations?.unit_of_item ?? '-' }}
                        </td>
                        <!-- NO. ITEM -->
                        <td
                          class="num-item-column"
                          [style.width.px]="columnWidths[4]"
                        >
                          {{
                            program.num_of_items_program
                              | number : '1.0-0' ?? '-'
                          }}
                        </td>
                      </tr>
                    </table>
                  </td>
                </tr>
              </ng-container>
            </table>
          </td>
        </tr>
      </ng-container>
      <ng-container *ngIf="currentIndex > 1">
        <!-- PDO ROW -->
        <tr class="pdo-row" *ngFor="let pdo of tableData">
          <!-- PDO NAME -->
          <td
            class="td-headers"
            class="pdo-name-column sticky-left"
            [style.width.px]="columnWidths[0]"
            [style.display]="columns[0].visible ? '' : 'none'"
          >
            {{ pdo.name ?? '-' }}
          </td>
          <td class="td-headers" [colSpan]="tableHeaders.length - 1">
            <table class="w-100 h-100">
              <!-- PROGRAM ROW -->
              <ng-container
                *ngFor="
                  let program of pdo.pdo_programs;
                  let programIndex = index
                "
              >
                <tr class="program-row">
                  <!-- PDO PROGRAM -->
                  <td
                    class="pdo-program-column sticky-left"
                    [style.width.px]="columnWidths[1]"
                    [style.display]="columns[1].visible ? '' : 'none'"
                  >
                    {{ program.program ?? '-' }}
                  </td>
                  <td>
                    <table class="w-100 h-100">
                      <!-- LOCATION ROW -->
                      <tr
                        class="location-row"
                        *ngFor="
                          let programLocations of program?.pdo_program_locations
                        "
                      >
                        <!-- PROGRAM LOCATION -->
                        <td
                          class="location-column"
                          [style.width.px]="columnWidths[2]"
                          [style.display]="columns[2].visible ? '' : 'none'"
                        >
                          {{ programLocations?.location?.name ?? '-' }}
                        </td>
                        <td>
                          <table class="w-100 h-100">
                            <!-- UNIT ITEM ROW -->
                            <tr class="unit-item-row">
                              <!-- UNIT ITEM -->
                              <td
                                class="unit-item-column"
                                [style.width.px]="columnWidths[3]"
                                [style.display]="
                                  columns[3].visible ? '' : 'none'
                                "
                              >
                                {{ programLocations?.unit_of_item ?? '-' }}
                              </td>
                              <!-- NO. ITEM -->
                              <td
                                class="num-item-column"
                                [style.width.px]="columnWidths[4]"
                              >
                                {{
                                  pdo.num_of_items_pdo | number : '1.0-0' ?? '-'
                                }}
                              </td>
                            </tr>
                          </table>
                        </td>
                      </tr>
                    </table>
                  </td>
                </tr>
              </ng-container>
            </table>
          </td>
        </tr>
      </ng-container>
      <!-- Grand total-->
      <tr class="grand-total-row font-weight-bold">
        <td
          class="grand-total-column sticky-left"
          [style.width.px]="columnWidths[0]"
          [style.display]="columns[0].visible ? '' : 'none'"
        >
          Grand total
        </td>
        <td
          class="grand-total-column sticky-left"
          [style.width.px]="columnWidths[1]"
          [style.display]="columns[1].visible ? '' : 'none'"
        ></td>
        <td
          class="grand-total-column"
          [style.width.px]="columnWidths[2]"
          [style.display]="columns[2].visible ? '' : 'none'"
        ></td>
        <td
          class="grand-total-column"
          [style.width.px]="columnWidths[3]"
          [style.display]="columns[3].visible ? '' : 'none'"
        ></td>
        <td class="grand-total-column" [style.width.px]="columnWidths[4]">
          {{ grandTotal.totalNumItems | number : '1.0-0' }}
        </td>
      </tr>
    </tbody>
  </table>
</div>

Component.css

table,
th,
td {
  margin: 0;
  padding: 0;
  border-collapse: collapse;
  font-size: 12px;
}
th:not(:first-child) {
  position: sticky;
  top: 0;
  background-color: white;
}
th:first-child {
  position: sticky;
  top: 0;
  left: 0;
  z-index: 10;
  background-color: white;
}
.table-parent {
  min-width: 1440px;
}
.container-parent {
  max-height: 100vh;
}
.grand-total-column:not(:first-child) {
  position: sticky;
  bottom: 0;
  background-color: white;
}
.grand-total-column.sticky-left:first-child {
  position: sticky;
  bottom: 0;
  left: 0;
  z-index: 10;
  background-color: white;
}
.pdo-name-column.sticky-left {
  position: sticky;
  left: 0;
  background-color: white;
}

/* .grand-total-column.sticky-left:nth-child(2),
.table-headers.sticky-left:nth-child(2),
.pdo-program-column.sticky-left {
  position: sticky;
  left: 144px;
  background-color: white;
} */

.th-row {
  border-bottom: 2px solid #ccc;
}

.pdo-row,
.program-row:not(:last-child),
.location-row:not(:last-child),
.unit-item-row:not(:last-child) {
  border-bottom: 1px solid #ccc;
}
.grand-total-row {
  border-top: 2px solid #ccc;
}

.unit-item-column,
.grand-total-column:nth-child(4) {
  border-right: 2px solid #ccc;
}

.table-headers:nth-child(5),
.num-item-column,
.grand-total-column:not(:first-child) {
  text-align: right;
}

th,
.pdo-name-column,
.pdo-program-column,
.location-column,
.beneficiary-column,
.currency-column,
.num-beneficiaries-column,
.funding-needs-column,
.grand-total-column {
  padding: 1px;
}

For sample demo, click here


Solution

  • I was able to fix this by using [style.min-width.px] instead of [style.width.px]. Using table-layout: fixed; makes the column not alligned

    Sample demo