Angular Data Table

Angular data table listed under website features. A JSON powered table w/ pagination.

Last Updated on June 29, 2024 by E. Scott

Creating an Angular data table can be daunting if you’re not sure where to begin. Check out the code below to get you up and running quick. As most everything these days is powered by endpoints, this example uses JSON mock data to emulate a response. Please see the below JSON. If however you’re looking for a JavaScript version, this may be comparable.

Git Repo

Data and Interface

[
    { "year": "1967", "make": "Pontiac", "model": "GTO" },
    { "year": "1967", "make": "Pontiac", "model": "Firebird" },
    { "year": "1967", "make": "Chevrolet", "model": "Malibu SS 396" },
    { "year": "1967", "make": "Chevrolet", "model": "Camaro SS" },
    { "year": "1967", "make": "Chevrolet", "model": "Camaro Z/28" },
    { "year": "1967", "make": "Chevrolet", "model": "RS" },
    { "year": "1967", "make": "Chevrolet", "model": "Nova SS" },
    { "year": "1967", "make": "Oldsmobile", "model": "442" },
    { "year": "1967", "make": "Buick", "model": "Gran Sport" },
    { "year": "1967", "make": "Ford", "model": "Mustang GT" },
    { "year": "1967", "make": "Ford", "model": "Mustang GTA" },
    { "year": "1967", "make": "Ford", "model": "Fairlane GTA" },
    { "year": "1967", "make": "Shelby", "model": "Cobra" },
    { "year": "1967", "make": "Shelby", "model": "Mustang GT350" },
    { "year": "1967", "make": "Shelby", "model": "GT500" },
    { "year": "1967", "make": "Mercury", "model": "Cougar Special" },
    { "year": "1967", "make": "Mercury", "model": "Cyclone GT" },
    { "year": "1967", "make": "Mercury", "model": "427 Comet" },
    { "year": "1967", "make": "Plymouth", "model": "Barracuda Formula S" },
    { "year": "1967", "make": "Plymouth GTX", "model": "GTO" },
    { "year": "1967", "make": "Dodge", "model": "Dart GT" },
    { "year": "1967", "make": "Dodge Dart GTS", "model": "GTO" },
    { "year": "1967", "make": "Dodge", "model": "Charger" },
    { "year": "1967", "make": "Dodge", "model": "Coronet R/T" },
    { "year": "1967", "make": "Dodge", "model": "Coronet 500 Hemi" }
  ]

Create a representation of the data via an interface. This is in essence an object blueprint. Used to express the necessary keys. Enforced at runtime, not upon transpilation. Next, head over to ngx pagination and run the necessary CLI command. Add the module (NgxPaginationModule) to your module. To do so, add NgxPaginationModule to your module imports array. And of course ensure the import statement is automatically added. If you’re adding pagination to multiple items, add an ID to the pagination-controls component (shown at the end of the markup). Then in the paginate object in the ngFor loop, add the ID and name as a key value pair (id: ‘pagination1’, itemsPerPage: 10…);

This is one reason as to why I’m not so gung-ho on the standalone components upgrade. This pagination component is a dumb component. It simply transfers information. It’s dumb because it doesn’t need to know the larger context. It knows nothing of what else is occurring. If we wanted to use this throughout our application, we’d need to import it to every component. The flip side, is creating a shared module. Then simply import that shared module to the needed modules. In turn, we maintain a modular system, lazy loading, and stay organized.

import { NgxPaginationModule } from 'ngx-pagination
export interface CarsResponse {
    year: string;
    make: string;
    model: string;
}

Angular Data Table TypeScript

  • Create an array for the incoming data.
  • Apply the CarsResponse data type.
  • p is indicative of the current paginated page.
  • Reference the httpClient in the constructor then request the data.
  • tableDataReq() fetches the response with the usual observable suspects of next, error, and complete.
  • Subscribe to retrieve the request and set it to the cars array.
  • The pipe method is used to contain sequentially executed methods.
  • tap() passes values to the observer, but also has the ability to perform independent operations. Suppose you want to push modified values to an array. We could display those modified values as well as the original values. For this reason, it’s often used for logging purposes. But, we all know, there are unique situations when we’d need to perform atypical tasks.
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { tap } from 'rxjs';
import { CarsResponse } from './cars.interface';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent {
  private unsubscribe$ = new Subject<void>();
  cars: CarsResponse[] = [];
  p: any;

  constructor(private _http: HttpClient) {
    this.tableDataReq();
  }

  tableDataReq() {
    return this._http
      .get<CarsResponse[]>('json-path')
      .pipe(
        tap({
          next: (data: any) => {},
          error: (err: any) => console.log(err),
          complete: () => console.log('request successful')
        }),
        takeUntil(this.unsubscribe$)
      )
      .subscribe((val) => {
        this.cars = val;
      });
  }
}

Loop over the cars array populating year, make, and model. Set the number of items per page followed by the current page. Please note however, using let i = index in the index loop does not successfully track the index. Indices are reset every page. To circumvent this, you could of course add the index to the JSON or extrapolate the indice from the backend. Perhaps a more succinct method would be to use the aforementioned i var, then simply multiple it by the current page.

<div id="table-component">
  <div class="table-details">
    <span>Popular Muscle Cars</span>
    <span>Total Vehicles: {{ cars.length }}</span>
  </div>
  <table>
    <thead>
      <tr>
        <th class="pl-15">Year</th>
        <th>Make</th>
        <th>Model</th>
      </tr>
    </thead>
    <tbody>
      <tr
        *ngFor="
          let car of cars | paginate: { itemsPerPage: 10, currentPage: p }
        "
      >
        <td class="pl-15">{{ car.year }}</td>
        <td>{{ car.make }}</td>
        <td>{{ car.model }}</td>
      </tr>
    </tbody>
  </table>
  <div class="pagination">
    <pagination-controls (pageChange)="p = $event"></pagination-controls>
  </div>
</div>

Beautify it with SCSS

Beyond the nesting, the only real anomalies here are the use of ::ng-deep. This unique selector is oftentimes frowned upon. Possibly because it’s used the wrong way in my opinion. I use it specifically to override third party library styles. These are classes that cannot be captured before rendering. The selector is used in the shadow DOM&mdash;a process using encapsulation when creating elements.

The drawback of using ::ng-deep is it disables this encapsulation process. In turn making the class global. Moreover, it has unexpected results with lazy loaded modules. Regardless, it seems to be virtually impossible to override third party library styles without it.

#table-component {
  font: normal 14px sans-serif;
  margin: 15px auto 0 auto;
  border: 1px solid black;
  border-radius: 4px;
  height: 400px;
  max-width: 500px;

  > .table-details {
    background-color: #313b3f;
    color: #d9a74a;
    margin: 0;
    padding: 10px;
    span:last-child {
      float: right;
    }
  }

  table {
    margin-top: 15px;
    width: 100%;
    border-collapse: collapse;

    tr {
      td:first-child { padding-left: 10px; }
    }

    td {
      width: 33.3%;
      font-size: 13px;
    }

    thead { font-size: 14px;
      th {
        border-bottom: 1px solid black;
        text-align: left;
        padding-bottom: 5px;
      }
    }

    .pl-15 { padding-left: 15px; }

    tbody {
      td {
        padding: 5px 0;
        border-bottom: 1px solid lightgrey;
      }
    }
  }

  .pagination {
    font-size: 14px;
    text-align: center;
    margin: 20px auto;
  }

  ::ng-deep .pagination-next a,
  ::ng-deep .pagination-previous a {
    outline: none;
  }

  ::ng-deep .ngx-pagination .current {
    border-radius: 4px;
  }

  ::ng-deep .ngx-pagination {
    padding: 0 !important;
  }

  ::ng-deep .ngx-pagination a:hover,
  ::ng-deep .ngx-pagination button:hover {
    border-radius: 4px;
  }

  ::ng-deep .ngx-pagination a,
  ::ng-deep .ngx-pagination button {
    border-radius: 4px;
    outline: none;
  }
}

@media screen and (max-width: 520px) {
  #table-component {
    margin: 15px;
  }
}

In Closing

Here’s another data table written in React. And, please see an itemized list of projects similar to this one. There’s an infinite of ways to accomplish pagination. I’ve used this method many times over though in a variety of projects. It’s always straightforward, flexible, and easy to customize. If you’re met with a challenge at any point, check out the Angular Data Table Stackblitz demo. Thanks for reading. Have a great day!

Be the first to comment

Leave a Reply

Your email address will not be published.


*