Building CRUD Mobile App using Ionic 4, Angular 8

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. As usual, we will start this tutorial using Ionic CLI with the new version 5.

The Ionic 4 Angular 8 application flow will look like this. It just a regular CRUD (Create, Read, Update, Delete) function.

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 App development.

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

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

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.

Install Angular 8 Material and CDK

For UI, we will use Angular 8 Material and CDK. 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 this imports.

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 RESTful API Service

To call RESTful 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.

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.

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

Add all CRUD (create, read, update, delete) functions of products data.

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

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

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

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

deleteProduct(id: any): Observable {
  const url = `${apiUrl}/${id}`;

  return this.http.delete(url, httpOptions).pipe(
    tap(_ => console.log(`deleted product id=${id}`)),
    catchError(this.handleError('deleteProduct'))
  );
}

View List of Data

As we mention in the begining of this article, we will use existing Ionic 4 Home Page Module to display list of data. For that, open and edit src/app/home/home.page.ts then add/replace these imports.

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) {
  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.

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.


  
    Home
  



  
    
      
      {{p.prod_name}}
      
        {{p.prod_price | currency}}
      
    
  

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 this imports.

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.


  
    
      
    
    Product Details
  



  
    <div class="example-loading-shade"
          *ngIf="isLoadingResults">
      
    
    
      
        ## {{product.prod_name}}

        {{product.prod_desc}}
      
      
        
          Product Price:
          {{product.prod_price}}
          Updated At:
          {{product.updated_at | date}}
        
      
      
        edit
        delete
      
    
  

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.


  
    
      
    
    Product Add
  



  
    <div class="example-loading-shade"
         *ngIf="isLoadingResults">
      
    
    
      
        
          <input matInput placeholder="Product Name" formControlName="prod_name"
                 [errorStateMatcher]="matcher">
          
            Please enter Product Name
          
        
        
          <input matInput placeholder="Product Desc" formControlName="prod_desc"
                 [errorStateMatcher]="matcher">
          
            Please enter Product Description
          
        
        
          <input matInput placeholder="Product Price" formControlName="prod_price"
                 [errorStateMatcher]="matcher">
          
            Please enter Product Price
          
        
        
          save
        
      
    
  

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.


  
    
      
    
    Product Edit
  



  
    <div class="example-loading-shade"
         *ngIf="isLoadingResults">
      
    
    
      
        
          <input matInput placeholder="Product Name" formControlName="prod_name"
                 [errorStateMatcher]="matcher">
          
            Please enter Product Name
          
        
        
          <input matInput placeholder="Product Desc" formControlName="prod_desc"
                 [errorStateMatcher]="matcher">
          
            Please enter Product Description
          
        
        
          <input matInput placeholder="Product Price" formControlName="prod_price"
                 [errorStateMatcher]="matcher">
          
            Please enter Product Price
          
        
        
          save
        
      
    
  

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.

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.

#angular #ionic #mobile-apps #web-development

Building CRUD Mobile App using Ionic 4, Angular 8
221.60 GEEK