Ionic 4, Angular 7 and Cordova Tutorial: Build CRUD Mobile Apps

by Didin J. on Nov 09, 2018 Ionic 4, Angular 7 and Cordova Tutorial: Build CRUD Mobile Apps

A comprehensive step by step tutorial on building CRUD (Create, Read, Update, Delete) Hybrid Mobile Apps using Ionic 4, Angular 7 and Cordova

A comprehensive step by step Ionic 4, Angular 7 tutorial on building CRUD (Create, Read, Update, Delete) Hybrid or Browser-based Mobile Apps that build to iOS or Android using Cordova. If you are new to Ionic Angular mobile apps development then you are ready to try this out from the scratch. But, if you have experience with previous Ionic version (3 and above) then you will have to face a different application structure that more Angular instead of Ionic Framework itself. As usual, we will learn Ionic 4 simply by practice instead of the full length of explanation or in other words, this is a walkthrough of building Ionic Angular mobile apps.


Shortcut to the steps:


This is updated Ionic 4 and Angular 7 tutorial using the latest Ionic Angular version

The Ionic 4 / Angular 7 CRUD application flow is very simple and standard. You can see the flow in this diagram.

Ionic 4, Angular 7 and Cordova Tutorial: Build CRUD Mobile Apps - App Flow Diagram

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

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

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 --version
5.0.2


Create a New Ionic 4 Application

The Ionic command-line interface (CLI) is your go-to tool for developing Ionic apps. To create a new Ionic 4 and Angular 7 application, type this command in your terminal.

ionic start ionic4-angular7-crud blank --type=angular

If you see this question, just type `N` for because we will installing or adding Cordova later.

Integrate your new app with Cordova to target native iOS and Android? (y/N) N

After installing `NPM` modules and dependencies, you will see this question, just type `N` because we are not using it yet.

Install the free Ionic Pro SDK and connect your app? (Y/n) N

Next, go to the newly created app folder.

cd ./ionic4-angular7-crud

As usual, 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 7 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 7 and Cordova Tutorial: Build CRUD Mobile Apps - Ionic 4 Labs


Install Angular 7 Material and CDK

Angular 7 Material is an implementation of Google's Material Design Specification (2014-2018). This project provides a set of reusable, well-tested, and accessible UI components for Angular developers. To install Angular 7 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: Indigo/Pink
   [ Preview: https://material.angular.io?theme=indigo-pink ]
? Set up HammerJS for gesture recognition? Yes
? Set up browser animations for Angular Material? Yes

Next, register all required Angular Material components or modules to `app.module.ts`. Open and edit that file then add this imports.

import {
  MatInputModule,
  MatPaginatorModule,
  MatProgressSpinnerModule,
  MatSortModule,
  MatTableModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule } from "@angular/material";
import { DragDropModule } from '@angular/cdk/drag-drop';
import { ScrollingModule } from '@angular/cdk/scrolling';

Register the above modules to `@NgModule` imports.

imports: [
  BrowserModule,
  IonicModule.forRoot(),
  AppRoutingModule,
  BrowserAnimationsModule,
  DragDropModule,
  ScrollingModule,
  MatInputModule,
  MatPaginatorModule,
  MatProgressSpinnerModule,
  MatSortModule,
  MatTableModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule
],


Create Service for Accessing REST API

Before creating a service for REST API access, first, we have to install or register `HttpClientModule`. Open and edit `src/app/app.module.ts` then add this import.

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

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

ng g service api

Next, open and edit `src/app/api.service.ts` then add these imports of RxJS Observable, of, throwError, Angular HttpClient, HttpHeaders, HttpErrorResponse, RxJS-Operators catchError, tap, map, 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 = "/api/v1/products";

Inject `HttpClient` module to the constructor.

constructor(private http: HttpClient) { }

Add the error handler function.

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 all CRUD (create, read, update, delete) functions of products data.

getProduct(id): 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): Observable<Product> {
  return this.http.post<Product>(apiUrl, product, httpOptions).pipe(
    tap((product: Product) => console.log(`added product w/ id=${product._id}`)),
    catchError(this.handleError<Product>('addProduct'))
  );
}

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


Modify Ionic Tabs Page

We will put every CRUD pages inside tabs that previously created as default Ionic 4 starter template. We will display a list of data inside the home page, so for edit, add and details we will modify the existing page or tab items. Rename contact page folder, files and it's filed contents as `add`. Rename about page folder, files and it's filed contents as `edit`. Type this command to generate a new details page.

ionic g page details

Next, open and edit `src/app/tabs/tabs.router.module.ts` then replace and add these imports.

import { HomePage } from '../home/home.page';
import { AddPage } from '../add/add.page';
import { EditPage } from '../edit/edit.page';
import { DetailsPage } from '../details/details.page';

Change the route constant as below.

const routes: Routes = [
  {
    path: 'tabs',
    component: TabsPage,
    children: [
      {
        path: '',
        redirectTo: '/tabs/(home:home)',
        pathMatch: 'full',
      },
      {
        path: 'home',
        outlet: 'home',
        component: HomePage
      },
      {
        path: 'add',
        outlet: 'add',
        component: AddPage
      },
      {
        path: ':id',
        outlet: 'edit',
        component: EditPage
      },
      {
        path: ':id',
        outlet: 'details',
        component: DetailsPage
      }
    ]
  },
  {
    path: '',
    redirectTo: '/tabs/(home:home)',
    pathMatch: 'full'
  }
];

Next, open and edit `src/app/tabs/tabs.module.ts` then add or replace these imports.

import { AddPageModule } from '../add/add.module';
import { EditPageModule } from '../edit/edit.module';
import { HomePageModule } from '../home/home.module';
import { DetailsPageModule } from '../details/details.module';

Add those imported modules to the `@NgModule` imports array.

imports: [
  IonicModule,
  CommonModule,
  FormsModule,
  TabsPageRoutingModule,
  HomePageModule,
  AddPageModule,
  EditPageModule,
  DetailsPageModule
],

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

<ion-tabs>

  <ion-tab tab="home">
    <ion-router-outlet name="home"></ion-router-outlet>
  </ion-tab>
  <ion-tab tab="details">
    <ion-router-outlet name="details"></ion-router-outlet>
  </ion-tab>
  <ion-tab tab="add">
    <ion-router-outlet name="add"></ion-router-outlet>
  </ion-tab>
  <ion-tab tab="edit">
    <ion-router-outlet name="edit"></ion-router-outlet>
  </ion-tab>

  <ion-tab-bar slot="bottom">

    <ion-tab-button tab="home" href="/tabs/(home:home)">
      <ion-icon name="home"></ion-icon>
      <ion-label>Home</ion-label>
    </ion-tab-button>

    <ion-tab-button tab="details" href="/tabs/(details:null)">
      <ion-icon name="eye"></ion-icon>
      <ion-label>Details</ion-label>
    </ion-tab-button>

    <ion-tab-button tab="add" href="/tabs/(add:add)">
      <ion-icon name="add"></ion-icon>
      <ion-label>Add</ion-label>
    </ion-tab-button>

    <ion-tab-button tab="edit" href="/tabs/(edit:null)">
      <ion-icon name="create"></ion-icon>
      <ion-label>Edit</ion-label>
    </ion-tab-button>

  </ion-tab-bar>

</ion-tabs>


View List of Data and Delete

To display a list of data, 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 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 7 init function after the constructor for call above function.

ngOnInit() {
  this.getProducts();
}

Add function for the new Angular 7 CDK Drag&Drop.

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

Next, because we will use the new Angular 7 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.

<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>


Display Data Details

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/details/` then add/replace this imports.

import { Component, OnInit } from '@angular/core';
import { LoadingController, 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 loadingController: LoadingController,
  public alertController: AlertController,
  public route: ActivatedRoute,
  public router: Router) {}

Add a variable before the constructor for hold Product data.

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

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 {
    const loading = await this.loadingController.create({
      message: 'Loading...'
    });
    await loading.present();
    await this.api.getProduct(this.route.snapshot.paramMap.get('id'))
      .subscribe(res => {
        console.log(res);
        this.product = res;
        loading.dismiss();
      }, err => {
        console.log(err);
        loading.dismiss();
      });
  }
}

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 7 init function.

ngOnInit() {
  this.getProduct();
}

Add the functions to delete the data.

async delete(id) {
  const loading = await this.loadingController.create({
    message: 'Loading...'
  });
  await loading.present();
  await this.api.deleteProduct(id)
    .subscribe(res => {
      loading.dismiss();
      this.router.navigate([ '/tabs', { outlets: { home: 'home' } } ]);
    }, err => {
      console.log(err);
      loading.dismiss();
    });
}

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

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

<ion-content>
  <ion-card>
    <ion-card-header>
      <ion-card-title>{{product.prod_name}}</ion-card-title>
    </ion-card-header>

    <ion-card-content>
      <ion-item>
        <p>Price:</p>
        <p>{{product.prod_price | currency}}</p>
      </ion-item>
      <ion-item>
        <p>Description:</p>
        <p>{{product.prod_desc}}</p>
      </ion-item>
      <ion-button type="button" shape="round" color="primary" expand="block" href="/tabs/(edit:{{product._id}})">Edit</ion-button>
      <ion-button type="button" shape="round" color="danger" expand="block" (click)="delete(product._id)">Delete</ion-button>
    </ion-card-content>
  </ion-card>
</ion-content>


Create a Form to Edit and Update Data

We will use Angular 7 Reactive Forms for edit data. Open and edit `src/app/edit/edit.module.ts` then add/replace these imports.

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

Register the Reactive Form Module to the `@NgModule` imports array.

imports: [
  IonicModule,
  CommonModule,
  FormsModule,
  ReactiveFormsModule,
  RouterModule.forChild([{ path: '', component: EditPage }])
],

Next, open and edit `src/app/edit/edit.page.ts` then add/replace these imports.

import { Component, OnInit } from '@angular/core';
import { LoadingController, AlertController } from '@ionic/angular';
import { ApiService } from '../api.service';
import { ActivatedRoute, Router } from '@angular/router';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { Product } from '../product';

Add this constructor and inject above modules.

constructor(public api: ApiService,
  public loadingController: LoadingController,
  public alertController: AlertController,
  public route: ActivatedRoute,
  public router: Router,
  private formBuilder: FormBuilder) {
}

Add the variables for hold current data, Form Group, and Reactive Form controls.

productForm: FormGroup;
_id:any='';
prod_name:string='';
prod_desc:string='';
prod_price:number=null;

Add asynchronous function to get Product Detail.

async getProduct(id) {
  if(this.route.snapshot.paramMap.get('id') == 'null') {
    this.presentAlertConfirm('You are not choosing an item from the list');
  } else {
    const loading = await this.loadingController.create({
      message: 'Loading...'
    });
    await loading.present();
    await this.api.getProduct(id)
      .subscribe(data => {
        this._id = data._id;
        this.productForm.setValue({
          prod_name: data.prod_name,
          prod_desc: data.prod_desc,
          prod_price: data.prod_price
        });
        loading.dismiss();
      }, err => {
        console.log(err);
        loading.dismiss();
      });
  }
}

Call that function inside Angular 7 init.

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]
  });
}

Add asynchronous function for handle form submit.

async onFormSubmit(form:NgForm) {
  await this.api.updateProduct(this._id, form)
    .subscribe(res => {
        let id = res['_id'];
        this.router.navigate([ '/tabs', { outlets: { details: id }} ]);
      }, (err) => {
        console.log(err);
      }
    );
}

Add asynchronous function to display an alert that calls on the first function.

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

  await alert.present();
}

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

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

<ion-content padding>
  <form [formGroup]="productForm" (ngSubmit)="onFormSubmit(productForm.value)">
    <ion-item>
      <ion-label position="floating">Product Name</ion-label>
      <ion-input type="text" formControlName="prod_name"></ion-input>
    </ion-item>
    <ion-item>
      <ion-label position="floating">Product Price</ion-label>
      <ion-input type="number" formControlName="prod_price"></ion-input>
    </ion-item>
    <ion-item>
      <ion-label position="stacked">Product Description</ion-label>
      <ion-textarea formControlName="prod_desc"></ion-textarea>
    </ion-item>
    <ion-button type="submit" shape="round" color="primary" expand="block" [disabled]="!productForm.valid">Submit</ion-button>
  </form>
</ion-content>


Create a Form to Add Data

Almost the same with edit data, we will create a form to add new data. Open and edit `src/app/add/add.module.ts` then add/replace these imports.

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

Register the Reactive Form Module to the `@NgModule` imports array.

imports: [
  IonicModule,
  CommonModule,
  FormsModule,
  ReactiveFormsModule,
  RouterModule.forChild([{ path: '', component: AddPage }])
],

Next, open and edit `src/app/edit/edit.page.ts` then add/replace these imports.

import { Component, OnInit } from '@angular/core';
import { LoadingController, AlertController } from '@ionic/angular';
import { ApiService } from '../api.service';
import { ActivatedRoute, Router } from '@angular/router';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { Product } from '../product';

Add this constructor and inject above modules.

constructor(public api: ApiService,
  public loadingController: LoadingController,
  public alertController: AlertController,
  public route: ActivatedRoute,
  public router: Router,
  private formBuilder: FormBuilder) {
}

Add the variables for hold current data, Form Group, and Reactive Form controls.

productForm: FormGroup;
prod_name:string='';
prod_desc:string='';
prod_price:number=null;

Add Angular 7 init.

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

Add asynchronous function for handle form submit.

async onFormSubmit(form:NgForm) {
  const loading = await this.loadingController.create({
    message: 'Loading...'
  });
  await loading.present();
  await this.api.addProduct(form)
    .subscribe(res => {
        let id = res['_id'];
        loading.dismiss();
        console.log(this.router);
        this.router.navigate([ { outlets: { details: id } } ], { relativeTo: this.route.parent });
      }, (err) => {
        console.log(err);
        loading.dismiss();
      });
}

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

<ion-header>
  <ion-toolbar>
    <ion-title>
      Add Product
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding>
  <form [formGroup]="productForm" (ngSubmit)="onFormSubmit(productForm.value)">
    <ion-item>
      <ion-label position="floating">Product Name</ion-label>
      <ion-input type="text" formControlName="prod_name"></ion-input>
    </ion-item>
    <ion-item>
      <ion-label position="floating">Product Price</ion-label>
      <ion-input type="number" formControlName="prod_price"></ion-input>
    </ion-item>
    <ion-item>
      <ion-label position="floating">Product Description</ion-label>
      <ion-textarea formControlName="prod_desc"></ion-textarea>
    </ion-item>
    <ion-button type="submit" shape="round" color="primary" expand="block" [disabled]="!productForm.valid">Submit</ion-button>
  </form>
</ion-content>


Run and Test The Ionic, Angular, and Cordova App

Before running the Ionic 4 and Angular 7 application, first, you have run the Node.js, Express.js and MongoDB RESTful API that previously downloaded. Type this commands in another terminal tab. We assume, you already running the MongoDB server.

npm install
npm start

Now, we have to run the whole Ionic 4 and Angular 7 application to the browser first by type this command.

ionic serve -l

You can navigate the whole application, so it will similar to this.

Ionic 4, Angular 7 and Cordova Tutorial: Build CRUD Mobile Apps - Test and Run

To run on the devices, type this commands.

ionic cordova platform rm android
ionic cordova platform add android
ionic cordova run android
ionic cordova platform rm ios
ionic cordova platform add ios
ionic cordova run ios

That it's, the results of our experiment of using Angular 7 with Ionic 4. To get the full working source code, you can get 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…