In this article, we are going to create the error pages and the details component. The main focus will be on the material components.

We are going to create the Not-Found component, the Server-Error component, and finally the Owner-Details component. Through these components, we will learn how to use different Angular Material components in our project.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

For the complete navigation and all the basic instructions of the Angular Material series, check out: Introduction of the Angular Material series.

The source code is available at GitHub Angular Material Error and Details Pages – Source Code

We strongly recommend reading our Angular Series prior to reading this article, if you want to restore your knowledge about that topic or to learn Angular development overall.

So, let’s start.

We are going to divide this post into several sections:

Not-Found Component and Material Progress Bar

The first thing we need to do is to create a new not-found component:

ng g component error-pages/not-found --skipTests

After that, we are going to change the routes in the main routing module:
import { NotFoundComponent } from '../error-pages/not-found/not-found.component';

const routes: Routes = [
  { path: 'home', component: HomeComponent},
  { path: 'owner', loadChildren: "./../owner/owner.module#OwnerModule" },
  { path: '404', component: NotFoundComponent}, 
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  { path: '**', redirectTo: '/404', pathMatch: 'full'}
];

Now, if we try to load some non-existing address, we will get the NotFound component instead, with the “not-found works”.

Of course, we don’t want this message, so we are going to modify the not-found.component.html file:

<section fxLayout="column wrap" fxLayoutGap="60px" fxLayoutAlign="center center">
  <div fxFlex>
    404 We are searching for your page...
  </div>
  <div fxFlex>
    <mat-progress-bar mode="indeterminate"></mat-progress-bar>
  </div>
  <div fxFlex>
    ... But we can not find it.
  </div>
</section>

As we can see, we are using the mat-progress-bar material component, and for that reason, we need to import the required module into the material.module.ts file:

import { MatProgressBarModule } from '@angular/material/progress-bar';

imports: [
    MatProgressBarModule,

exports: [
    MatProgressBarModule,

Finally, let’s add some styles to the not-found.component.css file:
section div:nth-child(1), section div:nth-child(3){
    color: blue;
    font-size: 50px;
}

section div:nth-child(1){
    margin-top: 20px;
}

section div:nth-child(2){
    width: 50%;
}

That is it. We can inspect our result by typing a none-existing URL (localhost:4200/something):

not-found - angular material progress bar

 

This looks good.

Let’s continue with the Server-Error component.

Server-Error Component With Spinner and CheckBox Material Components

We are going to start with Server-Error component creation:

ng g component error-pages/server-error --skipTests

Having that done, let’s modify the routing file:
import { ServerErrorComponent } from '../error-pages/server-error/server-error.component';

const ownerRoutes: Routes = [
  { path: 'home', component: HomeComponent },
  { path: 'owner', loadChildren: "./../owner/owner.module#OwnerModule" },
  { path: '404', component: NotFoundComponent },
  { path: '500', component: ServerErrorComponent },
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  { path: '**', redirectTo: '/404', pathMatch: 'full' }
];

For the visual experience, we need to modify the server-error.component.html file:
<section fxLayout="column wrap" fxLayoutAlign="center center" fxLayoutGap="30px">
  <div fxFlex>
    <p>500 Server Error</p>
    <p>We are sorry for the inconvinience, plese report this error.</p>
  </div>
  <div fxFlex>
    <mat-checkbox (change)="checkChanged($event)" color="primary">I want to report this error.</mat-checkbox>
  </div>
  <div fxFlex *ngIf="reportedError">
      <mat-progress-spinner mode="determinate" [value]="errorPercentage"></mat-progress-spinner>
      <h1>{{errorPercentage}}%</h1>
  </div>
</section>

Because we are using the checkbox and progress-spinner components, we need to import their modules into the material.module.ts file:
import {MatCheckboxModule } from '@angular/material/sheckbox';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'

imports: [
    MatProgressSpinnerModule,
    MatCheckboxModule,

exports: [
    MatProgressSpinnerModule,
    MatCheckboxModule,

Ok, we have imported all the necessary modules and now we are going to  modify the server-error.component.ts file:
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-server-error',
  templateUrl: './server-error.component.html',
  styleUrls: ['./server-error.component.css']
})
export class ServerErrorComponent implements OnInit {
  public reportedError: boolean;
  public errorPercentage: number = 0;
  public timer;

  constructor() { }

  ngOnInit() {
  }

  public checkChanged = (event) => {
    this.reportedError = event.checked;

    this.reportedError ? this.startTimer() : this.stopTimer();
  }

  private startTimer = () => {
    this.timer = setInterval(() => {
      this.errorPercentage += 1;

      if (this.errorPercentage === 100) {
        clearInterval(this.timer);
      }
    }, 30);
  }

  private stopTimer = () => {
    clearInterval(this.timer);
    this.errorPercentage = 0;
  }

}

And finally, let’s modify the server-error.component.css file:
section div p:nth-child(1){
    font-size: 50px;
    text-align: center;
    color: #f44336;
}

section div p:nth-child(2){
    font-size: 20px;
    text-align: center;
    color: #3f51b5;
}

mat-checkbox {
    color: #3f51b5;
}

section div h1{
    text-align: center;
    color: #3f51b5;
    position: relative;
    top: -85px;
}

Our result should look like this:

server-error finished - angular material progress bar

Error Handling Service

It is not enough just to have the error pages, we need to handle errors and to redirect the user to the required page. For that, we are going to create an error-handler service.

We are just going to write our code, without any explanation because this is all angular specific. We have a great article .NET Core 2.0, Angular and Mysql. Error Handling in which we have explained everything related to the topic. Additionally, you can use H

So, let’s create a service and modify it:

ng g service shared/error-handler --skipTests

import { Injectable } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { Router } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class ErrorHandlerService {
  public errorMessage: string = '';

  constructor(private router: Router) { }

  public handleError = (error: HttpErrorResponse) => {
    if(error.status === 500){
      this.handle500Error(error);
    }
    else if(error.status === 404){
      this.handle404Error(error)
    }
    else{
      this.handleOtherError(error);
    }
  }
 
  private handle500Error = (error: HttpErrorResponse) => {
    this.createErrorMessage(error);
    this.router.navigate(['/500']);
  }
 
  private handle404Error = (error: HttpErrorResponse) => {
    this.createErrorMessage(error);
    this.router.navigate(['/404']);
  }
 
  private handleOtherError = (error: HttpErrorResponse) => {
    this.createErrorMessage(error);
    //TODO: this will be fixed later;
  }
 
  private createErrorMessage(error: HttpErrorResponse){
    this.errorMessage = error.error ? error.error : error.statusText;
  }
}

For now, this service can be implemented only in the owner-list component, so let’s do that:

import { ErrorHandlerService } from '../../shared/error-handler.service';
...
constructor(private repoService: RepositoryService, private errorService: ErrorHandlerService) { }
...
public getAllOwners = () => {
    this.repoService.getData('api/owner')
    .subscribe(res => {
      this.dataSource.data = res as Owner[];
    },
    (error) => {
      this.errorService.handleError(error);
    })
  }

That is it. Now if our server returns the not found response we will redirect a user to the not found page. The same will happen for the internal server error, just another page.

Of course, if you want, you can implement the Http Interceptor to intercept the request and throw an error, but the principle is the same.

Details Page – Card, Select and Expansion Panel Components

In this section, we are going to create details functionality for our application. To do that, let’s first create the owner-details component:

ng g component owner/owner-details --skipTests

Then, let’s configure the routes for this new component the owner-routing.module.ts file:
import { OwnerDetailsComponent } from '../owner-details/owner-details.component';

const routes: Routes = [
  { path: 'owners', component: OwnerListComponent },
  { path: 'details/:id', component: OwnerDetailsComponent}
];

After that, let’s modify the owner-list.component.ts file:
import { Router } from '@angular/router';

constructor(private repoService: RepositoryService, private errorService: ErrorHandlerService, private router: Router) { }

public redirectToDetails = (id: string) => {
    let url: string = `/owner/details/${id}`;
    this.router.navigate([url]);
  }

Right now, we can navigate to our details page by clicking on the Details button on the owner-list component. The next thing we are going to do is to add an additional interface and change our existing one:
export interface Account{
    id: string;
    dateCreated: Date;
    accountType: string;
    ownerId?: string;
}

import { Account } from './account.model';
export interface Owner{
    id: string;
    name: string;
    dateOfBirth: Date;
    address: string;

    accounts?: Account[];
}

After all of these changes, we need to modify the owner-details component, to show our details data on the page.

So, let’s start with the owner-details.component.ts file:

import { Component, OnInit } from '@angular/core';
import { Owner } from './../../_interface/owner.model';
import { Router, ActivatedRoute } from '@angular/router';
import { RepositoryService } from './../../shared/repository.service';
import { ErrorHandlerService } from './../../shared/error-handler.service';

@Component({
  selector: 'app-owner-details',
  templateUrl: './owner-details.component.html',
  styleUrls: ['./owner-details.component.css']
})
export class OwnerDetailsComponent implements OnInit {
  public owner: Owner;
  public showAccounts;

  constructor(private repository: RepositoryService, private router: Router, 
    private activeRoute: ActivatedRoute, private errorHandler: ErrorHandlerService) { }

  ngOnInit() {
    this.getOwnerDetails();
  }

  private getOwnerDetails = () =>{
    let id: string = this.activeRoute.snapshot.params['id'];
    let apiUrl: string = `api/owner/${id}/account`;
 
    this.repository.getData(apiUrl)
    .subscribe(res => {
      this.owner = res as Owner;
    },
    (error) =>{
      this.errorHandler.handleError(error);
    })
  }
}

Excellent.

We have prepared the logic to fetch the data from the server, so the obvious continuation is to show that data on the HTML page.

Because we are going to have a lot of code for this component, we are going to create two additional components to spread our HTML code between them.

That being said, let’s create those components:

ng g component owner/owner-details/owner-data --skipTests
ng g component owner/owner-details/account-data --skipTests

Owner-Data Component

We are going to modify the owner-data component first.

The HTML part:

<section fxLayout="row wrap" fxLayoutAlign="center center">
    <mat-card fxFlex="500px" fxFlex.xs="100%">
        <mat-card-title>Details for the clicked owner</mat-card-title>
        <mat-card-content>
          <div fxLayout="column wrap" fxLayoutGap="40px">
            <div fxLayout="row wrap" fxFlex>
              <div fxFlex><strong>Owner's name:</strong></div>
              <div fxFlex>{{owner?.name}}</div>
            </div>
      
            <div fxLayout="row wrap" fxFlex>
              <div fxFlex><strong>Date of birth:</strong></div>
              <div fxFlex>{{owner?.dateOfBirth | date}}</div>
            </div>
      
            <div fxLayout="row wrap" fxFlex *ngIf='owner?.accounts.length <= 2; else advancedUser'>
              <div fxFlex><strong>Type of user:</strong></div>
              <div fxFlex class="beginner-color">Beginner user.</div>
            </div>
            <ng-template #advancedUser>
              <div fxLayout="row wrap" fxFlex>
                <div fxFlex><strong>Type of user:</strong></div>
                <div fxFlex class="advanced-color">Advanced user</div>
              </div>
            </ng-template>
          </div>
        </mat-card-content>
        <mat-card-actions>
          <mat-form-field>
            <mat-select placeholder="Show accounts" (selectionChange)="onChange($event)">
              <mat-option *ngFor="let opt of selectOptions" [value]="opt.value">
                {{opt.name}}
              </mat-option>
            </mat-select>
          </mat-form-field>
        </mat-card-actions>
      </mat-card>
</section>

Because we are using the mat-card component and the mat-select component, we need to import modules inside the material.module.ts file:
import { MatCardModule } from '@angular/material/card';
import { MatSelectModule } from '@angular/material/select';

imports: [
    MatSelectModule,
    MatCardModule,

exports: [
    MatSelectModule,
    MatCardModule,

Next thing we need to do is to modify the owner-data.component.ts file:
import { Owner } from './../../../_interface/owner.model';
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-owner-data',
  templateUrl: './owner-data.component.html',
  styleUrls: ['./owner-data.component.css']
})
export class OwnerDataComponent implements OnInit {
  @Input() public owner: Owner;
  public selectOptions = [{name:'Show', value: 'show'}, {name: `Don't Show`, value: ''}];
  @Output() selectEmitt = new EventEmitter();

  constructor() { }

  ngOnInit() {
  }

  public onChange = (event) => {
    this.selectEmitt.emit(event.value);
  }

}

And finally, let’s modify the CSS file:
mat-card-content, mat-card-title, mat-card-actions {
    text-align: center;
}

mat-card-content{
    padding-top: 20px;
    padding-bottom: 20px;
}

.advanced-color{
    color: #3f51b5;
}

.beginner-color{
    color: #f44336;
}

In order to show our data, we need to include our components inside the owner-details component and check the current progress:
<app-owner-data [owner]='owner' (selectEmitt)='showAccounts = $event'></app-owner-data>
<app-account-data *ngIf='showAccounts'></app-account-data>

owner details part 1

Account-Data Component

Let’s modify the HTML part of this component first:

<section fxLayout="row wrap" fxLayoutAlign="center center">
    <mat-accordion fxFlex="500px" fxFlex.xs="100%">
        <mat-expansion-panel *ngFor="let account of accounts; let i = index">
          <mat-expansion-panel-header>
            <mat-panel-title>
              {{i+1}}. Account
            </mat-panel-title>
            <mat-panel-description>
              Account Information
            </mat-panel-description>
          </mat-expansion-panel-header>
      
          <div fxLayout="row wrap" fxLayoutAlign="center center">
            <div fxFlex="35%" class="text-color"><strong>type:</strong> &nbsp; {{account?.accountType}}</div>
            <div fxFlex class="text-color"><strong>created:</strong> &nbsp; {{account?.dateCreated | date}}</div>
          </div>

        </mat-expansion-panel>
      </mat-accordion>
</section>

Then, let’s import the module for the accordion:
import { MatExpansionModule } from '@angular/material/expansion';

imports: [
    MatExpansionModule,

exports: [
    MatExpansionModule,

After that, we need to modify the account-data.component.ts file:
import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-account-data',
  templateUrl: './account-data.component.html',
  styleUrls: ['./account-data.component.css']
})
export class AccountDataComponent implements OnInit {
  @Input() public accounts: Account[];

  constructor() { }

  ngOnInit() {
  }

}

And, to modify the CSS file:
.text-color {
    color: #3f51b5;
}

mat-accordion{
    margin-top: 20px;
}

Excellent.

All we have left to do is to modify our owner-details.component.html file:

<app-owner-data [owner]='owner' (selectEmitt)='showAccounts = $event'></app-owner-data>
<app-account-data *ngIf='showAccounts' [accounts]='owner?.accounts'></app-account-data>

Our completed page should look like this:

owner details completed

Conclusion

And there you go. We have created additional pages in our project and made them functional and prettier by using Angular Material Components.

So, to summarize, we have learned:

  • How to use Material Progress Bar and Spinner components
  • The way to use CheckBox, Select and Card components
  • To show additional data by using the Expansion-Panel component

In the next article, we are going to show you how to use Material Input components and to validate forms as well.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!