In module 16 we swapped out the Vehicles table implemented with regular Angular templating and the ngFor
structural directive with the Angular Material MatTable component. And in module 17 we implemented the Angular Material Pogress Spinner to replace the text “Loading…” message. Let’s turn our attention back to the Vehicles MatTable component and implement paging and sorting.
We already noted in module 16 that one of the advantages of using a component library is it lets you develop and add functionality rapidly without having to do a lot of programming. And this is true of the AM MatTable component which has integrated support for paging and sorting. In the next several sections we will add paging and sorting into the Vehicles table.
This module is about using the default client-side paging and sorting capabilities for MatTable. It is also possible to customize paging and sorting from the server-side and take more control which we may decide to do at a later point.
Import the MatPaginatorModule and MatSortModule
As always with Angular Material the first thing we need to do is import the Modules for the features we are going to use. Open angular-material.module.ts and modify it with the code below.
FredsCars\src\app\angular-material.module.ts
import { NgModule } from '@angular/core';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatTableModule } from '@angular/material/table';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';
const angularMatModules = [
MatToolbarModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatProgressSpinnerModule,
MatPaginatorModule,
MatSortModule
]
@NgModule({
imports: [
angularMatModules
],
exports: [
angularMatModules
]
})
export class AngularMaterialModule { }
In the code above we import MatPaginatorModule and MatSortModule into our AngularMaterial module so they will be available to use in the application and the Vehicles component.
Update the Vehicles HTML template
The next thing we need to do is add the MatPaginator
component and MatSort
directives to the Vehicles component html. Open the vehicles.component.html file and modify with the code below.
FredsCars/src/app/vehicles/vehicles.component.html
<p *ngIf="!vehicles">
<mat-spinner style="margin: 0 auto;"></mat-spinner>
</p>
<table mat-table [dataSource]="vehicles"
*ngIf="vehicles" matSort>
<ng-container matColumnDef="vehicleType">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Category</th>
<td mat-cell *matCellDef="let item">
<b>{{ item.vehicleType }}</b>
</td>
</ng-container>
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>
<td mat-cell *matCellDef="let item">
{{ item.id }}
</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Status</th>
<td mat-cell *matCellDef="let item">
{{ item.status }}
</td>
</ng-container>
<ng-container matColumnDef="year">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Year</th>
<td mat-cell *matCellDef="let item">
{{ item.year }}
</td>
</ng-container>
<ng-container matColumnDef="make">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Make</th>
<td mat-cell *matCellDef="let item">
{{ item.make }}
</td>
</ng-container>
<ng-container matColumnDef="model">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Model</th>
<td mat-cell *matCellDef="let item">
{{ item.model }}
</td>
</ng-container>
<ng-container matColumnDef="color">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Color</th>
<td mat-cell *matCellDef="let item">
{{ item.color }}
</td>
</ng-container>
<ng-container matColumnDef="price">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Price</th>
<td mat-cell *matCellDef="let item">
{{ item.price | currency:"USD" }}
</td>
</ng-container>
<!-- No VIN on vehicles page. We will show this on the details page -->
<!-- Header Template -->
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<!-- Row Template -->
<tr mat-row *matRowDef="let row; columns: columnsToDisplay"></tr>
</table>
<mat-paginator
[pageSize]="5"
[pageSizeOptions]="[3, 5, 10]"
showFirstLastButtons>
</mat-paginator>
In the code above we added the matSort
attribute to the <table> element and the mat-sort-header
attribute to all of the <th> elements in the ng-container
column templates. We also added the MatPaginator component at the bottom which will display the pagination controls. In mat-paginator
we set pageSizeOptions
to an array of numbers; 3, 5, and 10. These options will be available in a dropdown labeled “Items per page” in the pagination controls. We also set pageSize to 5. So the default in the pagination dropdown will be set to 5. And we applied the showFirstLastButtons
attribute so the pager will have First Page and Last Page controls.
Update the Vehicles TypeScript
The last thing we need to do is update the Vehicles TypeScript. Open the vehicles.component.ts file and modify the code with the contents below.
FredsCars/src/app/vehicles/vehicles.component.ts
/* import ViewChild */
import { Component, OnInit, ViewChild } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Vehicle } from './vehicle';
import { environment } from '../../environments/environment';
/* import MatTableDataSource, MatPaginator, and MatSort */
import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator } from "@angular/material/paginator";
import { MatSort } from "@angular/material/sort";
@Component({
selector: 'app-vehicles',
templateUrl: './vehicles.component.html',
styleUrls: ['./vehicles.component.scss']
})
export class VehiclesComponent implements OnInit {
// replace vehicles array with a MatTableDataSource.
// public vehicles?: Vehicle[];
// Declare a MatTableDataSource.
// -- a datasource for vehicle objects
public vehicles: MatTableDataSource<Vehicle>
constructor(private http: HttpClient) {
// Instantiate the MatTableDataSource.
this.vehicles = new MatTableDataSource<Vehicle>();
}
columnsToDisplay: string[] = ['id', 'vehicleType', 'status', 'year', 'make', 'model', 'color', 'price'];
@ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(MatSort) sort!: MatSort;
ngOnInit() {
// Subscribe MatTableDataSource to the Observable.
this.http.get<Vehicle[]>(environment.baseUrl + 'api/vehicles').subscribe(result => {
// the MatTableDataSource data property is used to update
// the table displays.
this.vehicles.data = result;
}, error => console.error(error));
}
// Associate MatPaginator and MatSort with the datasource in
// ngAfterInit to ensure the child content has been queried
// and assigned to the ViewChild properties.
ngAfterViewInit() {
// paginator property associates the MatPaginator component
// with the datasource.
this.vehicles.paginator = this.paginator;
// sort property associates the MatSort directive
// with the datasource.
this.vehicles.sort = this.sort;
}
}
Let’s talk about what we just did in the code above.
Import the dependencies
At the top of the file we import ViewChild from the Angular Core package. And we import MatTableDataSource, MatPaginator, and MatSort from Angular Material.
import { Component, OnInit, ViewChild } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Vehicle } from './vehicle';
import { environment } from '../../environments/environment.development';
import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
Create the MatTable data source
To display a plain MatTable all you need is an array which is what we have used up until now. We had declared an array of Vehicles and filled it in ngOnit.
public vehicles?: Vehicle[];
But to support paging and sorting, MatTable
requires a MatTableDataSource
. So we replace the above line with the one below where we declare a variable called vehicles of type MatTableSource<Vehicle>
.
public vehicles: MatTableDataSource<Vehicle>
Then in the constructor we instantiate the MatTableDataSource.
constructor(private http: HttpClient) {
// Instantiate the MatTableDataSource.
this.vehicles = new MatTableDataSource<Vehicle>();
}
Create paginator and sort references with ViewChild
Next we create two ViewChild properties called paginator and sort of type MatPaginator and MatSort respectively and use query selectors to find the first instances of a MatPaginator and MatSort that exist in the html.
@ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(MatSort) sort!: MatSort;
Subscribe the MatDataSource to an Observable.
We were already subscribing the old Vehicles array to an observable returned from the http.get() call in ngOnit like this.
ngOnInit() {
this.http.get<Vehicle[]>(environment.baseUrl + 'api/vehicles').subscribe(result => {
this.vehicles = result;
}, error => console.error(error));
}
Now we simply subscribe the new Vehicles MatDataSource instead by assigning the result to the MatDataSource<T>.data
property.
ngOnInit() {
// Subscribe MatDataSource to the Observable.
this.http.get<Vehicle[]>(environment.baseUrl + 'api/vehicles').subscribe(result => {
// the MatTableDataSource data property is used to update
// the table displays.
this.vehicles.data = result;
}, error => console.error(error));
}
Now a new array of Vehicle will be sent to the data source any time there is a change to the data.
Associate MatPaginator and MatSort with the MatDataSource
Finally we need to associate the ViewChild paginator and sort properties we created earlier with the data source.
We do this with the MatTableDataSource<T>.paginator
and MatTableDataSource<T>.sort
properties in ngAfterInit
.
ngAfterViewInit() {
// paginator property associates the MatPaginator component
// with the datasource.
this.vehicles.paginator = this.paginator;
// sort property associates the MatSort directive
// with the datasource.
this.vehicles.sort = this.sort;
}
Using ngAfterInit
In the above code we assign the ViewChild properties paginator and sort to the MatDataSource properties of the same names. And we do this in ngAfterViewInit()
. This is the second Lifecycle hook we have used. We learned about ngOnInit in module 10 in the section, “Using a lifecycle hook: ngOnit()“. But why do we do this in ngAfterViewInit? Why not just do it in ngOnInit after we set the data source to the observable?
Well we need to first make sure that the child content, the MatPaginator and MatSort components, are queried and assigned to the ViewChild properties before we can assign them to MatDataSource properties. And ngAfterViewInit()
is called after the view is initially rendered. So @ViewChild
properties depend on it. You can’t access view members before they are rendered. And the MatPaginator and MatSort components won’t exist before that.
In short:
ngAfterViewInit() is called after all child components are initialized and checked.
ngAfterViewInit
Run the project
Now if you run the application the results in the project should look like the following.

In the screenshot above the pagination controls are displayed under the table to the right and I have hovered over the Status column header showing the sort ascending icon.
Test the sorting
If you click the ascending icon the table data will be sorted by status ascending.

In the screenshot above I have clicked the Status column and the table data is sorted by status ascending. All rows show a status of New since the ‘N’ in New comes before the ‘U’ in Used. If I click the Status column header again the table data will be sorted by status descending.

When I click the Status column header a third time the table data is returned to its original unsorted state with a mixture of New and Used statuses.

You might find the last click and resulting data surprising. Some would expect the third click to put the data back into a state sorted by status ascending again rather then back to its original state. But this is how default sorting on the client-side works with MatTable. It does indeed have three states.
Test the paging
In the following screenshot I have clicked the “Items per page” DropDown which has the options we set for the pageSizeOptions of the mat-paginator element in the Vehicles html; 3, 5, and 10.

If I select 10 from the drop down, the table displays 10 rows of data and the MatPaginator range label reflects the selection by displaying “1-10 of 12” rows.

And if I click the Next Page icon, the last two out of twelve vehicles will be shown.


Next, if I click the First Page icon, the first page of 10 records will be shown.


And finally, if I click the Last Page icon, the last page of 2 records will be shown.


Once again, keep in mind the implementation we have so far is client-side paging and sorting. Right now we are only fetching and displaying a dozen records. But if we had hundreds of thousands of records we would be fetching them all from the server, the server would return a humongous JSON result, and all the records would all be in client-side memory even though we are not displaying all of them. And each time we paged or sorted there would definitely be a huge performance hit.
We’ll see how to mitigate this later in the chapter.
Fix the Progress Spinner
If you use the same trick we used in Module 17 to test the Progress Spinner and comment out the code in ngOnInit(), you’ll see that the Angular Material Progress Spinner is no longer working. Further more the table headers and Paginator control now show up even if there is no data.
Let’s fix this real quick before we move on.
Open the vehicles.component.scss file in the FredsCars/src/app/vehicles folder and make the modifications shown in bold blue font.
FredsCars/src/app/vehicles/vehicles.component.scss
@use '../../mixins' as mixins;
$lightBlue: #add8e6;
.hide-results {
display: none;
}
table {
/*border-spacing: 0px;*/
/* existing content */
In the code above, we have added a new CSS rule with a class selector. Now any element in the Vehicles component with a ngClass
attribute that includes a key called hide-results
with a value of true will not be rendered. (We will see this in action next in the modified HTML.)
Now open the vehicles.component.html file in the FredsCars/src/app/vehicles folder and make the modifications to the code shown below in bold blue font.
FredsCars/src/app/vehicles/vehicles.component.html
<p *ngIf="!(vehicles.data.length > 0)">
<mat-spinner style="margin: 0 auto;"></mat-spinner>
</p>
<table mat-table [dataSource]="vehicles" matSort
[ngClass]="{'hide-results': vehicles.data.length <= 0}">
<ng-container matColumnDef="vehicleType">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Category</th>
<td mat-cell *matCellDef="let item">
<b>{{ item.vehicleType }}</b>
</td>
</ng-container>
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>
<td mat-cell *matCellDef="let item">
{{ item.id }}
</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Status</th>
<td mat-cell *matCellDef="let item">
{{ item.status }}
</td>
</ng-container>
<ng-container matColumnDef="year">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Year</th>
<td mat-cell *matCellDef="let item">
{{ item.year }}
</td>
</ng-container>
<ng-container matColumnDef="make">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Make</th>
<td mat-cell *matCellDef="let item">
{{ item.make }}
</td>
</ng-container>
<ng-container matColumnDef="model">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Model</th>
<td mat-cell *matCellDef="let item">
{{ item.model }}
</td>
</ng-container>
<ng-container matColumnDef="color">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Color</th>
<td mat-cell *matCellDef="let item">
{{ item.color }}
</td>
</ng-container>
<ng-container matColumnDef="price">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Price</th>
<td mat-cell *matCellDef="let item">
<b>{{ item.price | currency:"USD" }}</b>
</td>
</ng-container>
<!-- No VIN on vehicles page. We will show this on the details page -->
<!-- Header Template -->
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<!-- Row Template -->
<tr mat-row *matRowDef="let row; columns: columnsToDisplay"></tr>
</table>
<mat-paginator
[pageSize]="5"
[pageSizeOptions]="[3, 5, 10]"
showFirstLastButtons
[ngClass]="{'hide-results': vehicles.data.length <= 0}">
</mat-paginator>
In the HTML above, we have added the following Angular ngClass
attribute to both the table element and the mat-paginator element.
[ngClass]="{'hide-results': vehicles.data.length <= 0}"
The Angular ngClass
attribute’s expression here has one single key called hide-results
which will evaluate to true if the MatTableDataSource called vehicles in the TypeScript file of the component has a length of ‘less than or zero’ for it’s data property. Otherwise it will evaluate to false if the MatTableDataSource does have data, or a data.length
property greater than zero.
In the screenshot below I have commented out the http.get()
call in ngOnInit()
in vehicles.component.html so no data will be returned and the data.length
property for the vehicles MatTableDataSource will be 0.

Press the F12 key in you browser to open the web development tool. Here you can see under the elements tab that both the table
and mat-paginator
elements now have the hide-results CSS class due to the ngClass hide-results key’s expression evaluating to true for both elements.
And as we learned about in module 16, Displaying results with MatTable, you can see in the styles pain on the right that the mat-paginator’s CSS display property is set to ‘none’ since it has the hide-results CSS class and the _ngconent-uxe-c30 attribute. We learned in module 16 that [uxe-c30] is the unique id Angular gives the Vehicles component’s scope. So it can apply the hide-results CSS rule from vehicles.compenent.scss and not to any elements in any other components.
In the screenshot below I have uncommented the http.get()
call in ngOnInit()
in vehicles.component.html so that the data will be returned this time and the data.length
property for the vehicles MatTableDataSource will be greater than 0.

Now you can see in the web development tools under the Elements tab that neither the table
or mat-paginator
elements have the hide-results CSS class due to the ngClass hide-results key’s expression evaluating to false for both elements.
The fix we used for whether or not to show the progress-spinner is a little different. We just slightly modified the ngIf
structural directive.
The reason the Progress Spinner stopped working to begin with is that we changed the type of our vehicles property in vehicles.component.ts from vehicle[] (or an array of vehicles) to a MatTableDataSource<Vehicle> (or a MatTableDataSource of type Vehicle).
So, instead of ngIf expressions like the following:
// check if the vehicles array property received data from http.get() in ngOnInit
*ngIf="vehicles"
// check if the vehicles array property is still waiting on data from http.get() in ngOnInit
*ngIf="!vehicles"
We need something more like this:
// check if the vehicles MatTableDataSource's data property is initialized with data from http.get() in ngOnInit
*ngIf="(vehicles.data.length > 0)"
// check if the vehicles MatTableDataSource's data property is still waiting to be initialized with data from http.get() in ngOnInit
*ngIf="!(vehicles.data.length > 0)"
To update the Progress Spinner HTML, we changed the structural ngIf
directive in the parent <p> element of the <mat-spinner> element
<p *ngIf="!(vehicles.data.length > 0)">
<mat-spinner style="margin: 0 auto;"></mat-spinner>
</p>
Now it checks the length of the data property of the MatTableDataSource to see if it’s greater then 0. If not, it renders the Progress Spinner.
Everything should work properly now and the Progress Spinner, MatTable, and MatPaginator should all render when appropriate.
What’s Next
In this module we looked at how to set up default paging and sorting for MatTable using MatPaginator and MatSort. In the next module we will add a side bar to the application so we can start to add in some search options for which vehicles to display.