Angular 8 Tutorial: Learn to Build Angular 8 CRUD Web App

by Didin J. on Jun 23, 2019 Angular 8 Tutorial: Learn to Build Angular 8 CRUD Web App

A comprehensive Angular 8 step by step tutorial, you will learn how to build Angular 8 CRUD Web App practically from the scratch

In this Angular 8 tutorial you will learn how to build Angular 8 CRUD Web App as the frontend from scratch, and use existing Node, Express.js, and MongoDB RESTful API as the backend. Just clone and run the RESTful API backend here or you can use your existing backend/REST API with JSON format for this Angular 8 tutorial.


Shortcut to the steps:


This Angular 8 tutorial is designed for the beginners because it's starting from scratch.

We will not describe the new Angular 8 feature here because the official Angular.io blog has explained very well. As usual, we will show you a practical walkthrough from the zero to the complete application.

The following tools, frameworks, and modules are required for this tutorial:

  1. Node.js (recommended version)
  2. Angular 8 CLI
  3. Angular 8
  4. Express and MongoDB RESTful API
  5. Terminal (Mac/Linux) or Node Command Line (Windows)
  6. IDE or Text Editor

We assume that you have installed Node.js. Now, we need to check the Node.js and NPM versions. Open the terminal or Node command line then type this commands.

node -v
v10.15.1
npm -v
6.9.0

That's the Node.js and NPM version that we are using. Now, you can go to the main steps.


Install/Update Angular 8 CLI and Create a New Application

If you are in an existing Angular 7 application, you can update the application using this command form your Angular 7 root directory. The updated modules just @angular/cli and @angular/core.

cd angular7-crud
ng update @angular/cli @angular/core

Now, you will see the new version in dependencies in `package.json` except for `@angular/material` and `@angular/cdk`.

{
  ...
  "dependencies": {
    "@angular/animations": "~8.0.2",
    "@angular/cdk": "^7.0.0",
    "@angular/common": "~8.0.2",
    "@angular/compiler": "~8.0.2",
    "@angular/core": "~8.0.2",
    "@angular/forms": "~8.0.2",
    "@angular/material": "^7.0.0",
    "@angular/platform-browser": "~8.0.2",
    "@angular/platform-browser-dynamic": "~8.0.2",
    "@angular/router": "~8.0.2",
    "core-js": "^2.5.4",
    "hammerjs": "^2.0.8",
    "rxjs": "~6.5.2",
    "tslib": "^1.9.0",
    "zone.js": "~0.9.1"
  },
  ...
}

To update Angular Material and CDK, first, you have to uninstall them manually.

npm uninstall --save @angular/material
npm uninstall --save @angular/cdk

Then install them again using Angular 8 CLI.

ng add @angular/material

Choose your default Angular Material theme during installation progress.

? Choose a prebuilt theme name, or "custom" for a custom theme:
? Indigo/Pink        [ Preview: https://material.angular.io?theme=indigo-pink ]
  Deep Purple/Amber  [ Preview: https://material.angular.io?theme=deeppurple-amber ]
  Pink/Blue Grey     [ Preview: https://material.angular.io?theme=pink-bluegrey ]
  Purple/Green       [ Preview: https://material.angular.io?theme=purple-green ]
  Custom

Leave the other question as default by type `Y`. Now if you check the `package.json` the Angular Material and CDK version updated to `8.0.1`. Next, we have to run the updated Angular 8 application after running the MongoDB and Node/Express.js API.

ng serve

Oops, there's something wrong with the CSS.

ERROR in ./src/app/product-add/product-add.component.scss
Module build failed (from ./node_modules/sass-loader/lib/loader.js):

.example-full-width:nth-last-child() {
                                  ^
      Expected "n".
   ?
17 ? .example-full-width:nth-last-child(){
   ?                                    ^
   ?
  stdin 17:36  root stylesheet

Just change all SCSS code that contains `nth-last-child()` with this.

nth-last-child(0)

You can find the reference about this here. Now, you can see the updated Angular 8 application performance in the browser by going to `http://localhost:4200` and feel the performance difference with the previous version.

Now, for the new Angular 8 just type this Angular 8 CLI command.

ng new angular8-crud

If you get the question like below, choose `Yes` and `SCSS` (or whatever you like to choose).

? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS

Next, go to the newly created Angular 8 project folder.

cd angular8-crud

Type this command to run the Angular 8 application using this command.

ng serve

Open your browser then go to this address `localhost:4200`, you should see this Angular 8 page.

Angular 8 Tutorial: Learn to Build Angular 8 CRUD Web App - Angular 8 Welcome Page


Create the Angular 8 Routes

The Angular 8 routes already added when we create new Angular 8 application in the previous step. Routes use to navigate between components. Before configuring the routes, type this command to create a new Angular 8 components.

ng g component products
ng g component product-detail
ng g component product-add
ng g component product-edit

Open `src/app/app.module.ts` then you will see those components imported and declared in `@NgModule` declarations. Next, open and edit `src/app/app-routing.module.ts` then add these imports.

import { ProductsComponent } from './products/products.component';
import { ProductDetailComponent } from './product-detail/product-detail.component';
import { ProductAddComponent } from './product-add/product-add.component';
import { ProductEditComponent } from './product-edit/product-edit.component';

Add these arrays to the existing routes constant that contain route for above-added components.

const routes: Routes = [
  {
    path: 'products',
    component: ProductsComponent,
    data: { title: 'List of Products' }
  },
  {
    path: 'product-details/:id',
    component: ProductDetailComponent,
    data: { title: 'Product Details' }
  },
  {
    path: 'product-add',
    component: ProductAddComponent,
    data: { title: 'Add Product' }
  },
  {
    path: 'product-edit/:id',
    component: ProductEditComponent,
    data: { title: 'Edit Product' }
  },
  { path: '',
    redirectTo: '/products',
    pathMatch: 'full'
  }
];

Open and edit `src/app/app.component.html` and you will see the existing router outlet. Next, modify this HTML page to fit the CRUD page.

<div style="text-align:center">
  <img width="150" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>

<div class="container">
  <router-outlet></router-outlet>
</div>

Open and edit `src/app/app.component.scss` then replace all SASS codes with this.

.container {
  padding: 20px;
}


Create an Angular 8 Service

To access RESTful API from Angular 8, we need to create an Angular 8 service which will handle all POST, GET, UPDATE, DELETE requests. The response from the RESTful API emitted by Observable that can subscribe and read from the Components. Before creating a service for RESTful API access, first, we have to install or register `HttpClientModule`. Open and edit `src/app/app.module.ts` then add these imports of FormsModule (@angular/forms) and HttpClientModule (@angular/common/http).

import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

Add it to `@NgModule` imports after `BrowserModule`.

imports: [
  BrowserModule,
  FormsModule,
  HttpClientModule,
  AppRoutingModule
],

We will use type specifier to get a typed result object. For that, create a new Typescript file `src/app/product.ts` then add these lines of Typescript codes.

export class Product {
  _id: string;
  prod_name: string;
  prod_desc: string;
  prod_price: number;
  updated_at: Date;
}

Next, generate an Angular 8 service by typing this command.

ng g service api

Next, open and edit `src/app/api.service.ts` then add these imports.

import { Observable, of, throwError } from 'rxjs';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { catchError, tap, map } from 'rxjs/operators';
import { Product } from './product';

Add these constants before the `@Injectable`.

const httpOptions = {
  headers: new HttpHeaders({'Content-Type': 'application/json'})
};
const apiUrl = "/api/v1/products";

Inject `HttpClient` module to the constructor.

constructor(private http: HttpClient) { }

Add the error handler function that returns as an Observable.

private handleError<T> (operation = 'operation', result?: T) {
  return (error: any): Observable<T> => {

    // TODO: send the error to remote logging infrastructure
    console.error(error); // log to console instead

    // Let the app keep running by returning an empty result.
    return of(result as T);
  };
}

Add the functions for all CRUD (create, read, update, delete) REST API call of products data.

getProducts(): Observable<Product[]> {
  return this.http.get<Product[]>(apiUrl)
    .pipe(
      tap(product => console.log('fetched products')),
      catchError(this.handleError('getProducts', []))
    );
}

getProduct(id: number): Observable<Product> {
  const url = `${apiUrl}/${id}`;
  return this.http.get<Product>(url).pipe(
    tap(_ => console.log(`fetched product id=${id}`)),
    catchError(this.handleError<Product>(`getProduct id=${id}`))
  );
}

addProduct(product: Product): Observable<Product> {
  return this.http.post<Product>(apiUrl, product, httpOptions).pipe(
    tap((prod: Product) => console.log(`added product w/ id=${product.id}`)),
    catchError(this.handleError<Product>('addProduct'))
  );
}

updateProduct(id: any, product: Product): Observable<any> {
  const url = `${apiUrl}/${id}`;
  return this.http.put(url, product, httpOptions).pipe(
    tap(_ => console.log(`updated product id=${id}`)),
    catchError(this.handleError<any>('updateProduct'))
  );
}

deleteProduct(id: any): Observable<Product> {
  const url = `${apiUrl}/${id}`;
  return this.http.delete<Product>(url, httpOptions).pipe(
    tap(_ => console.log(`deleted product id=${id}`)),
    catchError(this.handleError<Product>('deleteProduct'))
  );
}

You can find more details about Angular 8 Observable and RXJS here.


Display List of Products using Angular 8 Material

We will display the list of products published from API Service. The data published from the API service read by subscribing as a Product model in the Angular 8 component. For that, open and edit `src/app/products/products.component.ts` then add this import of the previously created API Service.

import { ApiService } from '../api.service';

Next, inject the API Service to the constructor.

constructor(private api: ApiService) { }

Next, for the user interface (UI) we will use Angular 8 Material and CDK. There's a CLI for generating a Material component like Table as a component, but we will create or add the Table component from scratch to existing component. Type this command to install Angular 8 Material (@angular/material).

ng add @angular/material

If there are questions like below, just use the default answer.

? Choose a prebuilt theme name, or "custom" for a custom theme: Purple/Green       [ Preview: h
ttps://material.angular.io?theme=purple-green ]
? Set up HammerJS for gesture recognition? Yes
? Set up browser animations for Angular Material? Yes

We will register all required Angular 8 Material components or modules to `src/app/app.module.ts`. Open and edit that file then add these imports of required Angular Material Components.

import {
  MatInputModule,
  MatPaginatorModule,
  MatProgressSpinnerModule,
  MatSortModule,
  MatTableModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule } from "@angular/material";

Also, modify `FormsModule` import to add `ReactiveFormsModule`.

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

Register the above modules to `@NgModule` imports.

imports: [
  BrowserModule,
  FormsModule,
  HttpClientModule,
  AppRoutingModule,
  ReactiveFormsModule,
  BrowserAnimationsModule,
  MatInputModule,
  MatTableModule,
  MatPaginatorModule,
  MatSortModule,
  MatProgressSpinnerModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule
],

Next, back to `src/app/products/products.component.ts` then add these imports.

import { Product } from '../product';

Declare the variables of Angular 8 Material Table Data Source before the constructor.

displayedColumns: string[] = ['prod_name', 'prod_price'];
data: Product[] = [];
isLoadingResults = true;

Modify the `ngOnInit` function to get a list of products immediately.

ngOnInit() {
  this.api.getProducts()
    .subscribe((res: any) => {
      this.data = res;
      console.log(this.data);
      this.isLoadingResults = false;
    }, err => {
      console.log(err);
      this.isLoadingResults = false;
    });
}

Next, open and edit `src/app/products/products.component.html` then replace all HTML tags with this Angular Material tags.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div class="button-row">
    <a mat-flat-button color="primary" [routerLink]="['/product-add']"><mat-icon>add</mat-icon></a>
  </div>
  <div class="mat-elevation-z8">
    <table mat-table [dataSource]="data" class="example-table"
           matSort matSortActive="prod_name" matSortDisableClear matSortDirection="asc">

      <!-- Product Name Column -->
      <ng-container matColumnDef="prod_name">
        <th mat-header-cell *matHeaderCellDef>Product Name</th>
        <td mat-cell *matCellDef="let row">{{row.prod_name}}</td>
      </ng-container>

      <!-- Product Price Column -->
      <ng-container matColumnDef="prod_price">
        <th mat-header-cell *matHeaderCellDef>Product Price</th>
        <td mat-cell *matCellDef="let row">$ {{row.prod_price}}</td>
      </ng-container>

      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
      <tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/product-details/', row.id]"></tr>
    </table>
  </div>
</div>

Finally, to make a little UI adjustment, open and edit `src/app/products/products.component.scss` then add this CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-table-container {
  position: relative;
  max-height: 400px;
  overflow: auto;
}

table {
  width: 100%;
}

.example-loading-shade {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 56px;
  right: 0;
  background: rgba(0, 0, 0, 0.15);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.example-rate-limit-reached {
  color: #980000;
  max-width: 360px;
  text-align: center;
}

/* Column Widths */
.mat-column-number,
.mat-column-state {
  max-width: 64px;
}

.mat-column-created {
  max-width: 124px;
}

.mat-flat-button {
  margin: 5px;
}

If you don't want to use SASS for styling, rename the file extension to SCSS if the generated style file using SASS. Then change in the `src/app/products/products.component.ts` `@Component` declarations.

@Component({
  selector: 'app-products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.scss']
})


Show and Delete Product Details using Angular 8 Material

To show product details after click or tap on the one of a row inside the Angular 8 Material table, open and edit `src/app/product-detail/product-detail.component.ts` then add these imports.

import { ActivatedRoute, Router } from '@angular/router';
import { ApiService } from '../api.service';
import { Product } from '../product';

Inject above modules to the constructor.

constructor(private route: ActivatedRoute, private api: ApiService, private router: Router) { }

Declare the variables before the constructor for hold product data that get from the API.

product: Product = { _id: '', prod_name: '', prod_desc: '', prod_price: null, updated_at: null };
isLoadingResults = true;

Add a function for getting Product data from the API.

getProductDetails(id: any) {
  this.api.getProduct(id)
    .subscribe((data: any) => {
      this.product = data;
      console.log(this.product);
      this.isLoadingResults = false;
    });
}

Call that function when the component is initiated.

ngOnInit() {
  this.getProductDetails(this.route.snapshot.params['id']);
}

Add this function for delete product.

deleteProduct(id: any) {
  this.isLoadingResults = true;
  this.api.deleteProduct(id)
    .subscribe(res => {
        this.isLoadingResults = false;
        this.router.navigate(['/products']);
      }, (err) => {
        console.log(err);
        this.isLoadingResults = false;
      }
    );
}

For the view, open and edit `src/app/product-detail/product-detail.component.html` then replace all HTML tags with this.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div class="button-row">
    <a mat-flat-button color="primary" [routerLink]="['/products']"><mat-icon>list</mat-icon></a>
  </div>
  <mat-card class="example-card">
    <mat-card-header>
      <mat-card-title><h2>{{product.prod_name}}</h2></mat-card-title>
      <mat-card-subtitle>{{product.prod_desc}}</mat-card-subtitle>
    </mat-card-header>
    <mat-card-content>
      <dl>
        <dt>Product Price:</dt>
        <dd>{{product.prod_price}}</dd>
        <dt>Updated At:</dt>
        <dd>{{product.updated_at | date}}</dd>
      </dl>
    </mat-card-content>
    <mat-card-actions>
      <a mat-flat-button color="primary" [routerLink]="['/product-edit', product._id]"><mat-icon>edit</mat-icon></a>
      <a mat-flat-button color="warn" (click)="deleteProduct(product._id)"><mat-icon>delete</mat-icon></a>
    </mat-card-actions>
  </mat-card>
</div>

Finally, open and edit `src/app/product-detail/product-detail.component.scss` then add this lines of CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-loading-shade {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 56px;
  right: 0;
  background: rgba(0, 0, 0, 0.15);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mat-flat-button {
  margin: 5px;
}

If the style file extension is not SCSS, do the same way as previous steps.


Add a Product using Angular 8 Material

To create a form for adding a Product, open and edit `src/app/product-add/product-add.component.ts` then add these imports.

import { Router } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';

Inject above modules to the constructor.

constructor(private router: Router, private api: ApiService, private formBuilder: FormBuilder) { }

Declare variables for the Form Group and all of the required fields inside the form before the constructor.

productForm: FormGroup;
prod_name = '';
prod_desc = '';
prod_price: number = null;
isLoadingResults = false;

Add initial validation for each field.

ngOnInit() {
  this.productForm = this.formBuilder.group({
    'prod_name' : [null, Validators.required],
    'prod_desc' : [null, Validators.required],
    'prod_price' : [null, Validators.required]
  });
}

Create a function for submitting or POST product form.

onFormSubmit() {
  this.isLoadingResults = true;
  this.api.addProduct(this.productForm.value)
    .subscribe((res: any) => {
        const id = res._id;
        this.isLoadingResults = false;
        this.router.navigate(['/product-details', id]);
      }, (err: any) => {
        console.log(err);
        this.isLoadingResults = false;
      });
}

Next, add this import for implementing `ErrorStateMatcher`.

import { ErrorStateMatcher } from '@angular/material/core';

Create a new class before the main class `@Components`.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Instantiate that `MyErrorStateMatcher` as a variable in the main class.

matcher = new MyErrorStateMatcher();

Next, open and edit `src/app/product-add/product-add.component.html` then replace all HTML tags with this.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div class="button-row">
    <a mat-flat-button color="primary" [routerLink]="['/products']"><mat-icon>list</mat-icon></a>
  </div>
  <mat-card class="example-card">
    <form [formGroup]="productForm" (ngSubmit)="onFormSubmit()">
      <mat-form-field class="example-full-width">
        <input matInput placeholder="Product Name" formControlName="prod_name"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!productForm.get('prod_name').valid && productForm.get('prod_name').touched">Please enter Product Name</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput placeholder="Product Desc" formControlName="prod_desc"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!productForm.get('prod_desc').valid && productForm.get('prod_desc').touched">Please enter Product Description</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput placeholder="Product Price" formControlName="prod_price"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!productForm.get('prod_price').valid && productForm.get('prod_price').touched">Please enter Product Price</span>
        </mat-error>
      </mat-form-field>
      <div class="button-row">
        <button type="submit" [disabled]="!productForm.valid" mat-flat-button color="primary"><mat-icon>save</mat-icon></button>
      </div>
    </form>
  </mat-card>
</div>

Finally, open and edit `src/app/product-add/product-add.component.scss` then add this CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child(0) {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}

If the style file extension is not SCSS, do the same way as previous steps.


Edit a Product using Angular 8 Material

We have put an edit button inside the Product Detail component for call Edit page. Now, open and edit `src/app/product-edit/product-edit.component.ts` then add these imports.

import { Router, ActivatedRoute } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';

Inject above modules to the constructor.

constructor(private router: Router, private route: ActivatedRoute, private api: ApiService, private formBuilder: FormBuilder) { }

Declare the Form Group variable and all of the required variables for the product-form before the constructor.

productForm: FormGroup;
_id = '';
prod_name = '';
prod_desc = '';
prod_price: number = null;
isLoadingResults = false;

Next, add validation for all fields when the component is initiated.

ngOnInit() {
  this.getProduct(this.route.snapshot.params['id']);
  this.productForm = this.formBuilder.group({
    'prod_name' : [null, Validators.required],
    'prod_desc' : [null, Validators.required],
    'prod_price' : [null, Validators.required]
  });
}

Create a function for getting product data that filled to each form fields.

getProduct(id: any) {
  this.api.getProduct(id).subscribe((data: any) => {
    this._id = data._id;
    this.productForm.setValue({
      prod_name: data.prod_name,
      prod_desc: data.prod_desc,
      prod_price: data.prod_price
    });
  });
}

Create a function to update the product changes.

onFormSubmit() {
  this.isLoadingResults = true;
  this.api.updateProduct(this._id, this.productForm.value)
    .subscribe((res: any) => {
        const id = res._id;
        this.isLoadingResults = false;
        this.router.navigate(['/product-details', id]);
      }, (err: any) => {
        console.log(err);
        this.isLoadingResults = false;
      }
    );
}

Add a function for handling the show product details button.

productDetails() {
  this.router.navigate(['/product-details', this._id]);
}

Next, add this import for implementing `ErrorStateMatcher`.

import { ErrorStateMatcher } from '@angular/material/core';

Create a new class before the main class `@Components`.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Instantiate that `MyErrorStateMatcher` as a variable in the main class.

matcher = new MyErrorStateMatcher();

Next, open and edit `src/app/product-edit/product-edit.component.html` then replace all HTML tags with this.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div class="button-row">
    <a mat-flat-button color="primary" (click)="productDetails()"><mat-icon>info</mat-icon></a>
  </div>
  <mat-card class="example-card">
    <form [formGroup]="productForm" (ngSubmit)="onFormSubmit()">
      <mat-form-field class="example-full-width">
        <input matInput placeholder="Product Name" formControlName="prod_name"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!productForm.get('prod_name').valid && productForm.get('prod_name').touched">Please enter Product Name</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput placeholder="Product Desc" formControlName="prod_desc"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!productForm.get('prod_desc').valid && productForm.get('prod_desc').touched">Please enter Product Description</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput placeholder="Product Price" formControlName="prod_price"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!productForm.get('prod_price').valid && productForm.get('prod_price').touched">Please enter Product Price</span>
        </mat-error>
      </mat-form-field>
      <div class="button-row">
        <button type="submit" [disabled]="!productForm.valid" mat-flat-button color="primary"><mat-icon>save</mat-icon></button>
      </div>
    </form>
  </mat-card>
</div>

Finally, open and edit `src/app/product-edit/product-edit.component.scss` then add this lines of CSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child(0) {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}

If the style file extension is not SCSS, do the same way as previous steps.


Build, Run, and Test the Angular 8 CRUD Web Application

Let's prove the performance of the Angular 8 CRUD Web Application. Now, we have to build the Angular 8 application using this command.

ng build --prod

And we have an 877KB size of the Angular 8 application build for production. Next, we have to test the whole application, first, we have to run MongoDB server and Node/Express API in the different terminal.

mongod
nodemon

Then run the Angular 8 application build, simply type this command.

ng serve

And here's our Angular 8 CRUD Web Application looks like.

That it's the Angular 8 CRUD Web App. You can find the full source code in our GitHub.

If you don’t want to waste your time design your own front-end or your budget to spend by hiring a web designer then Angular Templates is the best place to go. So, speed up your front-end web development with premium Angular templates. Choose your template for your front-end project here.

That just the basic. If you need more deep learning about MEAN Stack, Angular, and Node.js, you can take the following cheap course:

Thanks!

Loading…