Angular Material Table With API Call

Photo by Genaro ServĂ­n from Pexels

Overview

In this example, we will create a data table with Angular Materials and retrieve the data via API call. The table will have sort and pagination. The http service will call the users option from the free API https://random-data-api.com

Code for this article

Get Source Code

If you do not have Angular Material installed please run the following CLI command from your application root folder.

ng add @angular/material

How to Create a module for Material Library imports

Here is a link on how to set up a separate module for material libraries.

Create an Interface

ng generate interface models/random-users

Add the following code to the Interface. To keep this example simple, parts of the data structure is not defined.

export interface IRandomUsers {
  id: number,
  uid:  number,
  password:  string,
  first_name:  string,
  last_name:  string,
  username:  string,
  email:  string,
  avatar:  string,
  gender:  string,
  phone_number: string,
  social_insurance_number:  string,
  date_of_birth: Date,
  address: {
    city: string,
    street_name: string,
    street_address: string,
    zip_code: string,
    state: string,
    country: string,
    coordinates: {
        lat: number,
        lng: number
    }
  }  
}

Create a Service

Create a new service labeled random-api under a folder labeled services. This service will use https://random-data-api.com users.

ng g s services/random-api

In the random-users.service.ts file add the following code.

import { Observable } from 'rxjs';
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
import { IRandomUsers } from 'src/app/models/random-users';

@Injectable({
  providedIn: 'root'
})
export class RandomApiService {

   private baseURL = 'https://random-data-api.com';
  
   constructor(private http: HttpClient) { }

   getRandomUsers(): Observable<IRandomUsers> {
    const URL = `${this.baseURL}/api/users/random_user?size=10`;
    return this.http.get<IRandomUsers>(URL);
   }
}

Building the Component

Create a new component for the data table.

ng g c components/random-users-table --module=app

Typescript

TypeScript code below in the random-users-table.component.ts file.

import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { MatSort} from '@angular/material/sort';
import { MatPaginator } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';
import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar';
import { HttpErrorResponse } from '@angular/common/http';
import { RandomApiService } from 'src/app/services/random-api.service';
import { IRandomUsers } from 'src/app/models/random-users';

@Component({
  selector: 'app-random-users-table',
  templateUrl: './random-users-table.component.html',
  styleUrls: ['./random-users-table.component.scss']
})
export class RandomUsersTableComponent implements OnInit, OnDestroy {

  private subs = new Subscription();

  displayedColumns: string[] = ['action', 'avatar', 'username', 'email', 'date_of_birth', 'latidtude', 'longitude',];

  public dataSource: MatTableDataSource<IRandomUsers>;

  @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
  @ViewChild(MatSort, { static: true }) sort: MatSort;

  private dataArray: any;

  constructor(private financeService: RandomApiService, private _snackBar: MatSnackBar) { }

  ngOnInit() {
    this.subs.add(this.financeService.getRandomUsers()
      .subscribe((res) => {
        console.log(res);
        this.dataArray = res;
        this.dataSource = new MatTableDataSource<IRandomUsers>(this.dataArray);
        this.dataSource.paginator = this.paginator;
        this.dataSource.sort = this.sort;
      },
        (err: HttpErrorResponse) => {
          console.log(err);
        }));
  }

  ngOnDestroy() {
    if (this.subs) {
      this.subs.unsubscribe();
    }
  }

  public openRecord(id: number, name: string): void {
    this._snackBar.open(`Record ${id} ${name} `, 'Close', {
      horizontalPosition: 'center',
      verticalPosition: 'top',
    });    
  }
}

Break Down of the TypeScript code

The displayColumns is a string array with column names. The columns will be displayed in the order they are defined. In this case, action is the first column on the left and the longitude is the last column to the right.

public displayedColumns: string[] = ['action', 'avatar', 'username', 'email', 'date_of_birth', 'latidtude', 'longitude'];

displayColumns is passed to the Material Table HTML table row tags and each column has to have a corresponding matColumnDef tag.

<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>

The @ViewChild() decorators are used as references for Pagination and Sorting. The Material Table will handle the logic you just need to pass it to MatTableDataSource

@ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
@ViewChild(MatSort, {static: true}) sort: MatSort;

Passing the API Data, Sort, and Pagination instances to MatTableDataSource.

 this.dataArray  = res.majorIndexesList;
 this.dataSource = new MatTableDataSource<RecordsModel>(this.dataArray);
 this.dataSource.paginator = this.paginator;
 this.dataSource.sort = this.sort; 

Added a Material SnackBar to show the ID and Username when clicking on the view button under the action column.

constructor(private financeService: RandomApiService, private _snackBar: MatSnackBar) { }

...

public openRecord(id: number, name: string): void {
  this._snackBar.open(`Record ${id} ${name} `, 'Close', {
    horizontalPosition: 'center',
    verticalPosition: 'top',
  });    
}

HTML

HTML Markup Code for the random-users-table.component.html file.

<div class="mat-elevation-z1">
    <table mat-table [dataSource]="dataSource" matSort>
  
      <ng-container matColumnDef="action">
        <th mat-header-cell *matHeaderCellDef  mat-sort-header> Action</th>
        <td mat-cell *matCellDef="let element" class="left-text">  
            <button mat-stroked-button color="primary" (click)="openRecord(element.id, element.username)">View</button>
        </td>
      </ng-container>

      <ng-container matColumnDef="avatar">
        <th mat-header-cell *matHeaderCellDef  mat-sort-header> Avatar </th>
        <td mat-cell *matCellDef="let element" class="left-text"> 
          <img src={{element.avatar}}  width="50" height="auto"  />
        </td>
      </ng-container>
  
      <ng-container matColumnDef="username">
        <th mat-header-cell *matHeaderCellDef  mat-sort-header> Username </th>
        <td mat-cell *matCellDef="let element" class="left-text"> {{element.username}} </td>
      </ng-container>
  
      <ng-container matColumnDef="email">
        <th mat-header-cell *matHeaderCellDef  mat-sort-header> Email </th>
        <td mat-cell *matCellDef="let element" class="left-text"> {{element.email | number:'2.1-2'}} </td>
      </ng-container>


      <ng-container matColumnDef="date_of_birth">
        <th mat-header-cell *matHeaderCellDef  mat-sort-header> Date of Birth </th>
        <td mat-cell *matCellDef="let element" class="left-text">{{element.date_of_birth | date: 'EEEE MMM dd, yyy'}}  </td>
      </ng-container>

      <ng-container matColumnDef="latidtude">
        <th mat-header-cell *matHeaderCellDef  mat-sort-header> Latitude </th>
        <td mat-cell *matCellDef="let element" class="left-text"> {{element.address.coordinates.lat | number:'2.1-2'}} </td>
      </ng-container>

      <ng-container matColumnDef="longitude">
        <th mat-header-cell *matHeaderCellDef  mat-sort-header>Longitude </th>
        <td mat-cell *matCellDef="let element" class="left-text"> {{element.address.coordinates.lng | number:'2.1-2'}} </td>
      </ng-container>
  
      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
      <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
    </table>
  
    <mat-paginator [pageSizeOptions]="[5]"></mat-paginator>
</div>
  

Break Down of the HTML code

This option enables how much height for the table. mat-elevation-z1 is the lowest and you can go up to mat-elevation-z8 which will have a pronounced 3D shadow effect.

 <div class="mat-elevation-z1">

Adding mat-sort to the table tag will allow the use of mat-sort-header for generating sortable columns.

<table mat-table [dataSource]="dataSource" matSort>

Inside the <th header tags use mat-sort-header to enable sort on the columns you want to have the sorting feature enabled.

Each column defined in the displayColumns String array has to match one for one with matColumnDef.

<ng-container matColumnDef="avatar">
  <th mat-header-cell *matHeaderCellDef  mat-sort-header> Avatar </th>
  <td mat-cell *matCellDef="let element" class="left-text"> 
    <img src={{element.avatar}}  width="50" height="auto"  />
  </td>
</ng-container>

Mat-Paginator defines the available page sizes.

<mat-paginator [pageSizeOptions]="[5]"></mat-paginator>

So you could add more page sizes with 10 being the default. Click here to read more about material pagination.

<mat-paginator [pageSizeOptions]="[5, 10, 20]" [pageSize]="10"></mat-paginator>

CSS or SCSS file

CSS for the random-users-table.component.scss

table {
  width: 100%;
}
mat-header-cell, mat-cell {
  justify-content: left;
}
.left-text {
  text-align: left;
}

Update Root Component to Render the Table

Finally update the root component app.module.html with the following:

<app-random-users-table></app-random-users-table>