Ionic 4 Angular 8 Tutorial: Learn to Build CRUD Mobile Apps

by Didin J. on Jun 26, 2019 Ionic 4 Angular 8 Tutorial: Learn to Build CRUD Mobile Apps

A comprehensive Ionic 4 Angular 8 tutorial, learn to build CRUD (Create, Read, Update, Delete) Mobile Apps (Android/iOS)

A comprehensive Ionic 4 Angular 8 tutorial, learn to build CRUD (Create, Read, Update, Delete) Mobile Apps (Android/iOS). The Angular 8 just released a few weeks ago, but Ionic 4 still using Angular 7 (updated: now already integrated with Angular 8, so you can start with Ionic-Angular version 8). As usual, we will start this Ionic Angular tutorial using Ionic CLI with the new version 5. There's a lot of improvement in new Angular 8 that will affect to the Ionic application performance.

List of the steps:

The Ionic 4 Angular 8 application flow will look like this. It just a regular CRUD (Create, Read, Update, Delete) function. The Ionic app starts with a Home Page which is the list of products and add a product button, the list item clickable to the product details page, inside product details page there are edit and delete button, edit button go to edit page, and delete button to delete a product data.

Ionic 4 Angular 8 App Flow

In this tutorial, we will use more Angular 8 than Ionic 4 components itself. So, if you are new to Hybrid Mobile Apps development using Ionic and familiar with Angular then this is your easy way to implement your Angular skill for Mobile Apps development.

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

  1. Node.js (Recommended version)
  2. Ionic 4
  3. Angular 8
  4. Angular 8 CLI
  5. Express and MongoDB API
  6. Terminal or Node command line
  7. IDE or Text Editor

Remember always use the latest Ionic 4 and Angular 8 CLI to decrease compatibility issues

Before going to the main steps, we assume that you have to install Node.js. Next, upgrade or install new Ionic 4 CLI by open the terminal or Node command line then type this command.

sudo npm install -g ionic

You will get the latest Ionic 4 CLI in your terminal or command line. Check the version by type this command.

ionic -v
5.1.0

To update the Angular CLI, type this command.

sudo npm install -g @angular/cli

Now, the Angular version should be like this.

ng version
Angular CLI: 7.3.9


Create Ionic 4 Application and Update to Angular 8

We will be using Ionic CLI to create a new Ionic 4 application. Type this command to create it.

ionic start ionic4-angular8-crud --type=angular

[Updated] You can skip the upgrade works because the latest Ionic CLI already use Angular 8 as default. The created Ionic 4 application still using Angular 7.2.2, for that we have to upgrade the Angular 7 to Angular 8. Go to the newly created folder then type this command using Angular CLI.

ng update @angular/cli @angular/core

If you get dependency incompatibility like below.

Package "@ionic/angular" has an incompatible peer dependency to "zone.js" (requires "^0.8.26", would install "0.9.1").
Incompatible peer dependencies found. See above.

Uninstall then install again the required dependency version.

npm uninstall --save zone.js
npm install --save zone.js

Then run again the Angular 8 update command. Next, run the Ionic 4 and Angular 7 app for the first time, but before run as `lab` mode, type this command to install `@ionic/lab`.

npm install --save-dev @ionic/lab
ionic serve -l

Now, open the browser and you will the Ionic 4 and Angular 8 app with the iOS, Android, or Windows view. If you see a normal Ionic 4 blank application, that's mean you ready to go to the next steps.

Ionic 4 Angular 8 Home Page


Install Angular 8 Material and CDK

For UI, we will use Angular 8 Material and CDK. Angular Material is a UI component library for Angular application that uses Google Material Design specification. Angular Material components help in constructing attractive, consistent, and functional web pages and web applications while adhering to modern web design principles like browser portability, device independence, and graceful degradation. The Component Dev Kit (CDK) is a set of tools that implement common interaction patterns whilst being unopinionated about their presentation. It represents an abstraction of the core functionalities found in the Angular Material library, without any styling specific to Material Design. To install Angular 8 Material and CDK, simply run this command.

ng add @angular/material

Type enter or yes for every question that showed up.

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

Next, register all required Angular Material CDK components or modules to `app.module.ts`. Open and edit that file then add these imports of DragDropModule and Scrolling Module (Angular CDK).

import { DragDropModule } from '@angular/cdk/drag-drop';
import { ScrollingModule } from '@angular/cdk/scrolling';

For Angular 8 Material, we will not import here but in each Ionic 4 Page Modules. Also, modify `FormsModule` import to add `ReactiveFormsModule`.

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

Register the above modules to `@NgModule` imports.

imports: [
  BrowserModule,
  FormsModule,
  ReactiveFormsModule,
  IonicModule.forRoot(),
  AppRoutingModule,
  BrowserAnimationsModule,
  DragDropModule,
  ScrollingModule
],


Use Dynamic Imports for Angular 8 Route Configuration

Before change the Angular 8 Route configuration, we have to add the required Ionic 4 Page Module first. Type these commands to create them.

ionic g page product-detail
ionic g page product-add
ionic g page product-edit

We just added detail, add, and edit pages because the Product list will display in the Home Page Module. Next, open `src/app/app-routing.module.ts` then you will see the route modified and includes the page navigation. Next, we will modify this to match the new Angular 8 feature. Replace all route constant with this constant.

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home', loadChildren: () => import('./home/home.module').then(m => m.HomePageModule)},
  { path: 'product-detail/:id', loadChildren: () => import('./product-detail/product-detail.module').then(m => m.ProductDetailPageModule)},
  { path: 'product-add', loadChildren: () => import('./product-add/product-add.module').then(m => m.ProductAddPageModule)},
  { path: 'product-edit/:id', loadChildren: () => import('./product-edit/product-edit.module').then(m => m.ProductEditPageModule)},
];

Next, modify `tsconfig.json` to change module and target.

{
  "compilerOptions": {
  …
  "module": "esnext",
  "moduleResolution": "node",
  …
  "target": "es2015",
  …
},


Create Ionic 4 Angular 8 REST API Service

To call REST API we will use Ionic 4 Angular Service using `HttpClientModule`. So, all CRUD call handle by Ionic 4 Angular 8 service that emitted the response by Observable and RXJS. Next, open and edit `src/app/app.module.ts` then add these imports to register `HttpClientModule` and `FormsModule`.

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: number;
  prod_name: string;
  prod_desc: string;
  prod_price: number;
  updated_at: Date;
}

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

ionic g service api

Next, open and edit `src/app/api.service.ts` then add these imports of RxJS Observable, of, throwError, catchError, tap, map, Angular HttpClient, HttpHeaders, HttpErrorResponse, and Product object model.

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 = 'http://localhost:3000/api/v1/products';

Inject `HttpClient` module to the constructor.

constructor(private http: HttpClient) { }

Add the error handler function that emits the error response using Observable.

private handleError<T>(operation = 'operation', result?: T) {
  return (error: any): Observable<T> => {
    console.error(error); // log to console instead
    return of(result as T);
  };
}

Add all CRUD (create, read, update, delete) functions of products data which return the response from the REST API and emits using Observable.

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

getProduct(id: any): 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=${prod._id}`)),
    catchError(this.handleError<Product>('addProduct'))
  );
}

updateProduct(id: any, product: any): 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'))
  );
}


View List of Data with Angular CDK DragDrop

As we mentioned at the beginning of this article, we will use existing Ionic 4 Home Page Module to display a list of data. Also, we will implement the Angular CDK DragDrop feature on this page. For that, open and edit `src/app/home/home.page.ts` then add/replace these imports of Ionic-Angular LoadingController, Angular ActivatedRoute, Router, ApiService, Product object model, Angular CdkDragDrop, and moveItemInArray.

import { Component, OnInit } from '@angular/core';
import { LoadingController } from '@ionic/angular';
import { ActivatedRoute, Router } from '@angular/router';
import { ApiService } from '../api.service';
import { Product } from '../product';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';

Next, add Angular 8 `OnInit` implementation to the HomePage Class name.

export class HomePage implements OnInit

Next, add the constructor then inject those modules to the constructor.

constructor(
  public api: ApiService,
  public loadingController: LoadingController,
  public router: Router,
  public route: ActivatedRoute) { }

Remove all default generated variable, function and constructor body if exists then add this variable before the constructor for hold classroom data that get from the service.

products: Product[] = [];

Add function for getting Product list from API.

async getProducts() {
  const loading = await this.loadingController.create({
    message: 'Loading...'
  });
  await loading.present();
  await this.api.getProducts()
    .subscribe(res => {
      this.products = res;
      console.log(this.products);
      loading.dismiss();
    }, err => {
      console.log(err);
      loading.dismiss();
    });
}

Add Angular 8 init function after the constructor for call above function.

ngOnInit() {
  this.getProducts();
}

Add function for the new Angular 8 CDK `Drag&Drop`.

drop(event: CdkDragDrop<string[]>) {
  moveItemInArray(this.products, event.previousIndex, event.currentIndex);
}

Next, because we will use the new Angular 8 CDK features. We should add modules for it to `src/app/home/home.module.ts` then add these imports of Angular CDK ScrollingModule and DragDropModule.

import { ScrollingModule } from '@angular/cdk/scrolling';
import { DragDropModule } from '@angular/cdk/drag-drop';

Register to `@NgModule` imports array.

imports: [
  IonicModule,
  CommonModule,
  FormsModule,
  ScrollingModule,
  DragDropModule,
  RouterModule.forChild([{ path: '', component: HomePage }])
],

Next, open and edit `src/app/home/home.page.html` then replace all HTML tags with this <ion-content> that implement Angular CDK Virtual Scroll.

<ion-header>
  <ion-toolbar>
    <ion-title>Home</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <cdk-virtual-scroll-viewport cdkDropList itemSize="20" class="example-viewport" (cdkDropListDropped)="drop($event)">
    <ion-item *cdkVirtualFor="let p of products" class="example-item" href="/tabs/(details:{{p._id}})" cdkDrag>
      <ion-icon name="desktop" slot="start"></ion-icon>
      {{p.prod_name}}
      <div class="item-note" slot="end">
        {{p.prod_price | currency}}
      </div>
    </ion-item>
  </cdk-virtual-scroll-viewport>
</ion-content>

Finally, give this page a style by open and edit `src/app/home/home.page.scss` then replace all SCSS codes with these.

.example-viewport {
  height: 100%;
  width: 100%;
  border: none;
}

.example-item {
  min-height: 50px;
}


View Data Details and Add Delete Function

Every time you click the list item in the list of data, you will be redirected to Details tab including the ID of the selected data. Open and edit `src/app/product-detail/product-detail.page.ts` then add/replace these imports of Ionic-Angular AlertController, ApiService, Angular ActivatedRoute, Router, and Product object model.

import { Component, OnInit } from '@angular/core';
import { AlertController } from '@ionic/angular';
import { ApiService } from '../api.service';
import { ActivatedRoute, Router } from '@angular/router';
import { Product } from '../product';

Inject above modules to the constructor.

constructor(
  public api: ApiService,
  public alertController: AlertController,
  public route: ActivatedRoute,
  public router: Router) {}

Add the variables before the constructor for hold Product data and Angular 8 Loading Spinner.

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

Add an asynchronous function to getting Product detail from API.

async getProduct() {
  if (this.route.snapshot.paramMap.get('id') === 'null') {
    this.presentAlertConfirm('You are not choosing an item from the list');
  } else {
    this.isLoadingResults = true;
    await this.api.getProduct(this.route.snapshot.paramMap.get('id'))
      .subscribe(res => {
        console.log(res);
        this.product = res;
        this.isLoadingResults = false;
      }, err => {
        console.log(err);
        this.isLoadingResults = false;
      });
  }
}

Add an asynchronous function for display an alert.

async presentAlertConfirm(msg: string) {
  const alert = await this.alertController.create({
    header: 'Warning!',
    message: msg,
    buttons: [
      {
        text: 'Okay',
        handler: () => {
          this.router.navigate(['']);
        }
      }
    ]
  });

  await alert.present();
}

Call get product function from Angular 8 init function.

ngOnInit() {
  this.getProduct();
}

Add the functions to delete the data.

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

Add a function to navigate to the Edit Product page.

editProduct(id: any) {
  this.router.navigate([ '/product-edit', id ]);
}

Next, open and edit `src/app/details/details.page.html` then replace all HTML tags with this.

<ion-header>
  <ion-toolbar color="primary">
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/home"></ion-back-button>
    </ion-buttons>
    <ion-title>Product Details</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <div class="example-container mat-elevation-z8">
    <div class="example-loading-shade"
          *ngIf="isLoadingResults">
      <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
    </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" (click)="editProduct(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>
</ion-content>

Finally, give this page a style by open and edit `src/app/product-detail/product-detail.page.scss` then replace all SCSS codes with these.

.example-container {
  position: relative;
  padding: 5px;
  height: 100%;
  background-color: aqua;
}

.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;
}


Create a Form to Add Data using Angular 8 Material

To create a form for adding a Product Data using Angular 8 Material, open and edit `src/app/product-add/product-add.page.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 in the `ngOnInit` function.

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();

Before modifying the HTML file, we have to register all Angular 8 Material files by open and edit `src/app/product-add/product-add.module.ts` then add these imports.

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

Declare that imported modules to then `@NgModule` imports array.

imports: [
  ...
  MatInputModule,
  MatPaginatorModule,
  MatProgressSpinnerModule,
  MatSortModule,
  MatTableModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule
],

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

<ion-header>
  <ion-toolbar color="primary">
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/home"></ion-back-button>
    </ion-buttons>
    <ion-title>Product Add</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <div class="example-container mat-elevation-z8">
    <div class="example-loading-shade"
         *ngIf="isLoadingResults">
      <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
    </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>
</ion-content>

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

.example-container {
  position: relative;
  padding: 5px;
  height: 100%;
  background-color: aqua;
}

.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;
}

.example-card {
  margin: 5px;
}


Create a Form to Edit Data using Angular 8 Material

To create a Form of Edit Product Data using Angular 8 Material, open and edit `src/app/product-edit/product-edit.page.ts` then add these lines of imports.

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

Add a new Class before the `@Component` that handles the error message in the HTML form.

/** 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));
  }
}

Next, add these lines of variables to the main Class before the constructor.

productForm: FormGroup;
_id = '';
prod_name = '';
prod_desc = '';
prod_price: number = null;
isLoadingResults = false;
matcher = new MyErrorStateMatcher();

Inject the constructor params with these modules.

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

Initialize the form group with the form controls of the product form and call the product detail data in the Angular 8 `ngOnInit` function.

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 new Angular 8 function to call the product data by ID.

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 new Angular 8 function that handles the form submission to save data to the REST API.

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 to navigate to the Product Detail page.

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

Before modifying the HTML file, we have to register all Angular 8 Material files by open and edit `src/app/product-edit/product-edit.module.ts` then add these imports.

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

Declare that imported modules to then `@NgModule` imports array.

imports: [
  ...
  MatInputModule,
  MatPaginatorModule,
  MatProgressSpinnerModule,
  MatSortModule,
  MatTableModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule
],

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

<ion-header>
  <ion-toolbar color="primary">
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/product-detail/{{_id}}"></ion-back-button>
    </ion-buttons>
    <ion-title>Product Edit</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <div class="example-container mat-elevation-z8">
    <div class="example-loading-shade"
         *ngIf="isLoadingResults">
      <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
    </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>
</ion-content>

Finally, add some styles for this page by open and edit `src/app/product-edit/product-edit.page.scss` then replace all SCSS codes with these.

.example-container {
  position: relative;
  padding: 5px;
  height: 100%;
  background-color: aqua;
}

.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;
}

.example-card {
  margin: 5px;
}


Run and Test the Whole Ionic 4 Angular 8 Mobile Apps

Before running Ionic 4 Angular 8 Mobile Apps we have to start the MongoDB server and Node/Express.js REST API server. Type these commands in the separate Terminal/CMD tabs.

mongod
nodemon

Now, we have to run the Ionic 4 Angular 8 Mobile Apps in the browser using this command.

ionic serve -l

And here we go, the full Ionic 4 Angular 8 Mobile Apps CRUD functions.

Ionic 4 Angular 8 List
Ionic 4 Angular 8 Add Form
Ionic 4 Angular 8 Details
Ionic 4 Angular 8 Edit Form

That it's, the comprehensive step by step tutorial of Ionic 4 Angular 8 CRUD Mobile Apps. You can find the full source code from our GitHub.

We know that building beautifully designed Ionic apps from scratch can be frustrating and very time-consuming. Check Ionic 4 - Full Starter App and save development and design time. Android, iOS, and PWA, 100+ Screens and Components, the most complete and advance Ionic Template.

That just the basic. If you need more deep learning about Ionic, Angular, and Typescript, you can take the following cheap course:

Thanks!

Loading…