Before we move on to creating other features in the web application, let’s make one final fix to the Progress Spinner behavior.
We first swapped out the original text “loading…” message for the Angular Material Component Progress Spinner in module 17, Loading: Using a Progress Spinner.
After a while it became apparent that it was acting a little buggy.
The areas in the Vehicles component TypeScript where we used Angular ngIf
Directives and the [hide]
attribute to show and hide table elements and controls like the pager are sometimes a little buggy. Sometimes it works really well. But sometimes when there is some wait time for the data to come back and initialize the MatTable datasource the Progress Spinner just seems to keep spinning and spinning.
I’m going to just chalk this up to the fact that maybe that ngIf
and [hide]
don’t play well with the MatTable datasource.
Let’s see if we can fix this up real quick once and for all.
Modify the Vehicles TypeScript
Open the vehicles.component.ts file and make the modifications shown below in bold blue font.
FredsCars/src/app/vehicles/vehicles.component.ts
/*** existing code ***/
@Component({
selector: 'app-vehicles',
templateUrl: './vehicles.component.html',
styleUrls: ['./vehicles.component.scss']
})
export class VehiclesComponent implements OnInit {
// debug JSON var for HTML
public json = JSON;
public vehicles!: MatTableDataSource<Vehicle>
public dataReady: boolean = false;
columnsToDisplay: string[] = ['id', 'vehicleType', 'status', 'year', 'make', 'model', 'color', 'price'];
/*** existing code ***/
getVehicleData(event: PageEvent) {
// get rid of third empty click state for MatSort
if (this.sort && this.sort.direction == "") {
this.sort.direction = "asc";
}
var url = environment.baseUrl + 'api/vehicles';
var params = new HttpParams()
.set("pageIndex", event.pageIndex.toString())
.set("pageSize", event.pageSize.toString())
.set("sortColumn", (this.sort)
? this.sort.active
: this.defaultSortColumn)
.set("sortOrder", (this.sort)
? this.sort.direction
: this.defaultSortOrder);
if (this.filterValue) {
params = params
.set("filterColumns", this.defaultFilterColumns)
.set("filterValue", this.filterValue);
}
if (this.search) {
// search on categories
let categories: string = "";
if (!(this.categoryAll.selected)
&& this.categoryAll.categories?.some(c => {
return c.selected
}))
{
this.categoryAll.categories?.forEach(c => {
if (c.selected) {
categories += `${c.name},`
}
})
categories = categories.substr(0, categories.length - 1)
params = params
.set("searchValues", categories)
.set("searchColumns", "vehicleType");
}
// search on other columns.
// -- here...
}
this.http.get<any>(url, { params })
.subscribe(result => {
this.paginator.length = result.totalCount;
this.paginator.pageIndex = result.pageIndex;
this.paginator.pageSize = result.pageSize;
this.search = result.searched;
this.vehicles.data = result.data;
this.dataReady = true;
}, error => console.error(error));
}
/*** existing code ***/
In the code above we made a new TypeScript class variable called dataReady of type boolean initialized to false to act as a flag for when we think the component should stop displaying the Progress Spinner and display the table elements and controls.
Then at the bottom of the getVehicleData()
method at the end of the http.get()
call we set dataReady to true. We can use this flag in the HTML within the same ngIf
directives and [hide]
attributes we had before.
Modify the Vehicles HTML
Open the vehicles.component.html file and make the modifications shown below in bold blue font.
FredsCars/src/app/vehicles/vehicles.component.html
<mat-sidenav-container>
<mat-sidenav mode="side" #sidenav>
<div style="margin-right: 10px;">
<p><b>Search Panel</b></p>
<hr />
<p><b>Categories</b></p>
<mat-checkbox color="primary"
[checked]="allCategoriesSelected"
[indeterminate]="someSearchCategoriesSelected()"
(change)="setAllCategoriesSelected($event.checked)">
{{ categoryAll.name }}
</mat-checkbox>
<ul>
<li *ngFor="let category of categoryAll.categories">
<mat-checkbox color="accent"
[(ngModel)]="category.selected"
(ngModelChange)="updateAllCategoriesSelected()">
{{category.name}}
</mat-checkbox>
</li>
</ul>
<button mat-raised-button color="primary"
(click)="loadData(null, true)">
Search
</button>
</div>
<!-- debug -->
<!--
<div style="margin-top: 40px;">
<b>this.search:</b><br />
{{ this.search }}<br />
<b>categoryAll:</b><br />
{{ categoryAll.selected }}<br />
<b>Categories:</b>
<ul>
<li *ngFor="let category of categoryAll.categories">
{{category.name}}: {{ category.selected }}
</li>
</ul>
<b>categories (stringified):</b><br />
{{ categoryAll.categories | json }}
</div>
-->
<!-- debug -->
</mat-sidenav>
<mat-sidenav-content>
<p *ngIf="!dataReady">
<mat-spinner style="margin: 0 auto;"></mat-spinner>
</p>
<div *ngIf="dataReady"
style="margin-bottom: 10px;">
<button mat-fab extended
color="primary"
(click)="sidenav.toggle()">
<mat-icon>
search
</mat-icon>
Advanced Search
</button>
</div>
<mat-form-field [ngClass]="{'hide-results' : !dataReady}">
<mat-label>Quick Search</mat-label>
<input matInput #filter (keyup)="loadData(filter.value)"
placeholder="Filter by Make or Model" />
</mat-form-field>
<table mat-table [dataSource]="vehicles"
matSort
[matSortActive]="defaultSortColumn"
[matSortDirection]="defaultSortOrder"
(matSortChange)="loadData(filter.value, this.search)"
*ngIf="dataReady">
<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 (page)="getVehicleData($event)"
[pageSize]="10"
[pageSizeOptions]="[3, 5, 10]"
showFirstLastButtons
[ngClass]="{'hide-results' : !dataReady}">
</mat-paginator>
</mat-sidenav-content>
</mat-sidenav-container>
In the HTML above we’ve simply replaced all the ngIf
directive and [hide]
attribute expressions with dataReady
or !dataReady
rather then vehicles.datasource.length > 0
or !vehicles.datasource.length > 0
.