Ionic 4 Form Validation Tutorial with Reactive Forms

Ionic 4 Form Validation Tutorial with Reactive Forms

In this Ionic 4 Forms validation tutorial, we'll learn Ionic 4 Form Validation tutorial with Reactive Forms. Learn the basic form validation with Reactive Forms in Ionic 4 Application .

In this step by step Ionic 4 Forms validation tutorial, we will learn to create and validate a form with Angular’s Reactive Forms method.

Setting up forms in an Ionic application is easy, Angular offers Template-driven and Reactive Forms methods to deal with the forms data. Template-driven approach is used for working with simple forms. In contrast, It is used for handling more complex data. Especially when you need to work with nested values.

We will create a basic form with following input fields name, email, dob and mobile number.

Table of Contents

  • Set Up Ionic Project
  • Define Form Structure with Ionic HTML
  • Import & Register ReactiveFormsModule
  • Creating Form in Ionic 4 with Reactive Forms
  • Validate Ionic Form
  • Conclusion
Set Up Ionic Project

To setup a form in an Ionic app, you need to run the below command.

ionic start ionic-form-validation blank --type=angular

Get inside the project directory.

cd ionic-form-validation

Install lab mode as a dev-dependency by running the following command.

npm i @ionic/lab --save-dev

Start the Ionic app.

ionic serve -l

Define Form Structure with Ionic HTML

To set up a form in Ionic use Ionic form UI components, below we are creating a form for school students and a student can fill information about himself using the form. We used Ionic’s checkboxes, radio buttons, buttons and other elements to build this form.

Open home.page.html file and add the following code inside of it.

<ion-content>
  <form>
    <ion-item lines="full">
      <ion-label position="floating">Name</ion-label>
      <ion-input type="text" required></ion-input>
    </ion-item>

    <ion-item lines="full">
      <ion-label position="floating">Email</ion-label>
      <ion-input type="email" required></ion-input>
    </ion-item>

    <ion-item lines="full">
      <ion-label position="floating">DOB</ion-label>
      <ion-input type="text" required></ion-input>
    </ion-item>

    <ion-item lines="full">
      <ion-label position="floating">Mobile</ion-label>
      <ion-input type="text" required></ion-input>
    </ion-item>

    <!-- Radio buttons -->
    <ion-radio-group lines="full">
      <ion-list-header>
        <ion-label>Gender</ion-label>
      </ion-list-header>

      <ion-item>
        <ion-label>Male</ion-label>
        <ion-radio slot="start" value="male" checked></ion-radio>
      </ion-item>

      <ion-item>
        <ion-label>Female</ion-label>
        <ion-radio slot="start" value="female"></ion-radio>
      </ion-item>
    </ion-radio-group>

    <!-- Checkboxes -->
    <ion-list lines="full">
      <ion-list-header>
        <ion-label>Subjects</ion-label>
      </ion-list-header>
      <ion-item>
        <ion-label>English</ion-label>
        <ion-checkbox></ion-checkbox>
      </ion-item>
      <ion-item>
        <ion-label>Maths</ion-label>
        <ion-checkbox></ion-checkbox>
      </ion-item>
      <ion-item>
        <ion-label>Science</ion-label>
        <ion-checkbox></ion-checkbox>
      </ion-item>
      <ion-item>
        <ion-label>History</ion-label>
        <ion-checkbox></ion-checkbox>
      </ion-item>
    </ion-list>

    <ion-row>
      <ion-col>
        <ion-button type="submit" color="danger" expand="block">Submit</ion-button>
      </ion-col>
    </ion-row>
  </form>
</ion-content>

Import & Register ReactiveFormsModule

To work with Reactive Forms we need to import and register ReactiveFormsModule. Now usually when we work with core Angular app so we declare ReactiveFormsModule globally in app.module.ts file. However, we have to declare ReactiveFormsModule in every page or component when we work with forms module.

Open home.module.ts and add the below code in it.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';

import { HomePage } from './home.page';

@NgModule({
  imports: [
    CommonModule,
    ReactiveFormsModule,
    FormsModule,
    IonicModule,
    RouterModule.forChild([
      {
        path: '',
        component: HomePage
      }
    ])
  ],
  declarations: [HomePage]
})

export class HomePageModule { }
Creating Form in Ionic 4 with Reactive Forms

Next, we will use FormControl, FormGroup, FormBuilder, and Validators service to validate the form data.

Let’s understand what these services are?

FormGroup: A FormGroup is a collection of single or multiple FormControls and declared on the HTML’s form tag. Basically, its a collection of various form controls.

<form [formGroup]="ionicForm" (ngSubmit)="submitForm()" novalidate>
</form>

FormControl: A FormControl represents a value which is entered by the user, FormGroup is a collection of various FormControls.

<ion-content>
  <form [formGroup]="ionicForm" (ngSubmit)="submitForm()" novalidate>

    <ion-item lines="full">
      <ion-label position="floating">Name</ion-label>
      <ion-input formControlName="name" type="text" required></ion-input>
    </ion-item>

    <ion-item lines="full">
      <ion-label position="floating">Email</ion-label>
      <ion-input formControlName="email" type="email" required></ion-input>
    </ion-item>

    <ion-item lines="full">
      <ion-label position="floating">DOB</ion-label>
      <ion-datetime (ionChange)="getDate($event)" formControlName="dob" [value]="defaultDate"></ion-datetime>
    </ion-item>

    <ion-item lines="full">
      <ion-label position="floating">Mobile</ion-label>
      <ion-input formControlName="mobile" type="text" required></ion-input>
    </ion-item>

    <ion-row>
      <ion-col>
        <ion-button type="submit" color="danger" expand="block">Submit</ion-button>
      </ion-col>
    </ion-row>
  </form>
</ion-content>

FormBuilder: The FormBuilder service refers to a form object and sets up a FormGroup. It holds the user entered values and validation state of a form input field.

Import the following services to initialize the form in home.page.ts.

import { FormGroup, FormBuilder, Validators } from "@angular/forms";

Define the form object by declaring the FormGroup with a variable.

ionicForm: FormGroup;

Inject the FormBuilder service in the constructor.

constructor(public formBuilder: FormBuilder) { }

Setting up Date-picker in Ionic 4

Date-picker is used to get the date from the user, Ionic offers its own custom date-picker to deal with the date related date. We declared the date with ion-datetime tag in the HTML template. Now we will get the date value from the user and set in the dob formControl in Angular’s Reactive form.

We defined the defaultDate variable to set the default date. The getDate(e) function takes date object as an argument and convert it to YYYY-MM-DD format and set as a FormControl in the FormGroup.

getDate(e) {
   let date = new Date(e.target.value).toISOString().substring(0, 10);
   this.ionicForm.get('dob').setValue(date, {
      onlyself: true
   })
}

The (ngSubmit)="submitForm()" directive triggers a form submission.

submitForm() {
  console.log(this.ionicForm.value)
}

Enter some values in the form field and click on submit button, you will see user entered values in the ionicForm in the browser’s console.

Validate Ionic 4 Form

In this section we will learn how to validate Ionic 4 form fields using Reactive Forms validators service.

Import Validators service to in the Ionic template, It provides built-in methods to validate form controls.

import { Validators } from "@angular/forms";

The Validators class offers following methods to deal with form validation in Ionic 4 / Angular 8.

class Validators {
  static min(min: number): ValidatorFn
  static max(max: number): ValidatorFn
  static required(control: AbstractControl): ValidationErrors | null
  static requiredTrue(control: AbstractControl): ValidationErrors | null
  static email(control: AbstractControl): ValidationErrors | null
  static minLength(minLength: number): ValidatorFn
  static maxLength(maxLength: number): ValidatorFn
  static pattern(pattern: string | RegExp): ValidatorFn
}

We will trigger the validation when user clicks on the submit button, so we need to define a boolean variable and set it to false initially.

isSubmitted = false;

Now, we will validate our name form control. In this value we will implement required and min-length validation.

this.ionicForm = this.formBuilder.group({
   name: ['', [Validators.required, Validators.minLength(2)]],
})

As you can see we injected the validation methods with the help of Validators class. Validators array allow us to implement multiple validation in a single form control.

Now, we will declare the getter method to access the form control. Add the following code to access the form control directly via the Ionic / Angular template.

get errorControl() {
  return this.ionicForm.controls;
}

Next, we need to add the alert messages in the Ionic’s HTML template for showing the validation messages. These, messages will appear when the form is invalid.

<form [formGroup]="ionicForm" (ngSubmit)="submitForm()" novalidate>
  <ion-item lines="full">
    <ion-label position="floating">Name</ion-label>
    <ion-input formControlName="name" type="text"></ion-input>
  </ion-item>

  <!-- Error messages -->
  <span class="error ion-padding" *ngIf="isSubmitted && errorControl.name.errors?.required">
    Name is required.
  </span>
  <span class="error ion-padding" *ngIf="isSubmitted && errorControl.name.errors?.minlength">
    Name should be min 2 chars long.
  </span>
</form>

Inside the *ngIf directive, we are checking if both the conditions are true. The *ngIf="" directive will only show the alert message to the users when the form is invalid.

Following method will trigger the form on submit.

submitForm() {
  this.isSubmitted = true;
  if (!this.ionicForm.valid) {
    console.log('Please provide all the required values!')
    return false;
  } else {
    console.log(this.ionicForm.value)
  }
}

Here are the final code of this Ionic 4/Angular 8 Form validation tutorial.

home.page.ts
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from "@angular/forms";

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})

export class HomePage implements OnInit {
  ionicForm: FormGroup;
  defaultDate = "1987-06-30";
  isSubmitted = false;

  constructor(public formBuilder: FormBuilder) { }

  ngOnInit() {
    this.ionicForm = this.formBuilder.group({
      name: ['', [Validators.required, Validators.minLength(2)]],
      email: ['', [Validators.required, Validators.pattern('[a-z0-9._%+-][email protected][a-z0-9.-]+\.[a-z]{2,3}$')]],
      dob: [this.defaultDate],
      mobile: ['', [Validators.required, Validators.pattern('^[0-9]+$')]]
    })
  }

  getDate(e) {
    let date = new Date(e.target.value).toISOString().substring(0, 10);
    this.ionicForm.get('dob').setValue(date, {
      onlyself: true
    })
  }

  get errorControl() {
    return this.ionicForm.controls;
  }

  submitForm() {
    this.isSubmitted = true;
    if (!this.ionicForm.valid) {
      console.log('Please provide all the required values!')
      return false;
    } else {
      console.log(this.ionicForm.value)
    }
  }
}
home.page.html
<ion-header>
  <ion-toolbar>
    <ion-title>Ionic Forms</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <form [formGroup]="ionicForm" (ngSubmit)="submitForm()" novalidate>

    <ion-item lines="full">
      <ion-label position="floating">Name</ion-label>
      <ion-input formControlName="name" type="text"></ion-input>
    </ion-item>
    <span class="error ion-padding" *ngIf="isSubmitted && errorControl.name.errors?.required">
      Name is required.
    </span>
    <span class="error ion-padding" *ngIf="isSubmitted && errorControl.name.errors?.minlength">
      Name should be min 2 chars long.
    </span>

    <ion-item lines="full">
      <ion-label position="floating">Email</ion-label>
      <ion-input formControlName="email" type="email"></ion-input>
    </ion-item>
    <span class="error ion-padding" *ngIf="isSubmitted && errorControl.email.errors?.required">
      Email is required.
    </span>
    <span class="error ion-padding" *ngIf="isSubmitted && errorControl.email.errors?.pattern">
      Please provide valid email id.
    </span>

    <ion-item lines="full">
      <ion-label position="floating">DOB</ion-label>
      <ion-datetime (ionChange)="getDate($event)" formControlName="dob" [value]="defaultDate"></ion-datetime>
    </ion-item>

    <ion-item lines="full">
      <ion-label position="floating">Mobile</ion-label>
      <ion-input maxlength="10" formControlName="mobile" type="text" required></ion-input>
    </ion-item>
    <span class="error ion-padding" *ngIf="isSubmitted && errorControl.mobile.errors?.required">
      Mobile number is required.
    </span>
    <span class="error ion-padding" *ngIf="isSubmitted && errorControl.mobile.errors?.pattern">
      Only numerical values allowed.
    </span>

    <ion-row>
      <ion-col>
        <ion-button type="submit" color="danger" expand="block">Submit</ion-button>
      </ion-col>
    </ion-row>
  </form>
</ion-content>

Conclusion

Finally, we have finished Ionic 4 Form Validation Tutorial with Reactive Forms. We just learned the basic form validation with Reactive Forms in Ionic 4 app, I hope you will surely liked it.

How to create a CRUD Mobile App with Ionic 4 and Angular 8?

How to create a CRUD Mobile App with Ionic 4 and Angular 8?

A step by step Ionic 4 Cordova tutorial, In this tutorial, we will learn how to create a CRUD (Create, Read, Update, Delete) Mobile app (Android/iOS) with Ionic 4 and Angular 8

A step by step Ionic 4 Cordova tutorial, In this tutorial, we will learn how to create a CRUD (Create, Read, Update, Delete) Mobile app (Android/iOS) with Ionic 4 and Angular 8

In this tutorial, we will learn how to create a hybrid mobile app using Ionic/Angular 8 with Cordova which will support both Android/iOS. Our music app will allow our users to add a song, edit or update a song or view a song list.

Before we begin, we should always remember that we must use the latest Ionic 4 Cordova and Angular 8 version to avoid compatibility problems.

Table of Contents

  1. Prerequisite
  2. Ionic 4 Cordova Environment Setup
  3. Create Ionic 4 Cordova Project with Angular 8
  4. Configure Angular 8 Routes in Ionic 4 App
  5. Create Node/Express Server
  6. Create Ionic 4 Angular 8 API Service
  7. Ionic 4/Angular 8 Display Data List & Delete Data
  8. Add Data in Ionic 4
  9. Ionic 4 Edit Data
  10. Conclusion
1. Prerequisite

You need to be familiar with the following tools and frameworks to understand this tutorial.

  • Node
  • Express
  • MongoDB
  • Ionic 4
  • Cordova
  • Angular 8
  • Angular 8 CLI
  • Text Editor

To set up Node.js on your system, you need to download the latest version of Node from here.

2. Ionic 4 Cordova Environment Setup

Run the following command to install Ionic 4 Cordova globally in your system.

sudo npm install -g cordova ionic

You can verify the Ionic CLI version by running the below command.

ionic -v

# 5.4.6

Use following command to update Ionic 4 and Cordova.

sudo npm update -g cordova ionic

Angular CLI can also be updated by using the below command.

sudo npm install -g @angular/cli
3. Create Ionic 4 Cordova Project with Angular 8

To create the Ionic 4 CRUD mobile app, we need to enter the following command in Ionic Angular CLI.

ionic start ionic-angular-crud-app --type=angular

Choose Ionic template from the Ionic ready-made template list.

? Starter template: 
  tabs         | A starting project with a simple tabbed interface 
  sidemenu     | A starting project with a side menu with navigation in the content area 
❯ blank        | A blank starter project 
  my-first-app | An example application that builds a camera with gallery 
  conference   | A kitchen-sink application that shows off all Ionic has to offer

Get inside the Ionic 4 Cordova mobile CRUD app’s project folder.

cd ionic-angular-crud-app

Next, start the Ionic 4 CRUD mobile app in both iOS and Android mode in the browser. First, Run the below command to install lab mode as a development dependency.

npm i @ionic/lab --save-dev

Then, run the command in the terminal. It will open the Ionic CRUD mobile app in the browser.

ionic serve -l

4. Configure Angular 8 Routes in Ionic 4 App

To configure routes in the Ionic 4 Cordova app, we need to generate pages, run the following command in the terminal.

ng generate page add-song

ng generate page edit-song

We created add-song and edit-song pages, in Ionic 4/Angular app’s home page we will render the songs list. Go to the src/app/app-routing.ts file and include the following code.

import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';

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

@NgModule({
  imports: [
    RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
  ],
  exports: [RouterModule]
})

export class AppRoutingModule { }
5. Create Node/Express Server

Now we will create a backend server to manage the data for our Ionic 4/Angular 8 CRUD mobile app. We will set up a backend server with the help of Node, Express and MongoDB. We will create REST APIs for Create, Read, Update and Delete songs data and store the data in the MongoDB database.

Create a folder in the root of the Ionic 4/Angular project and name it backend and then get inside this folder.

Run the following command from the root of your Ionic project.

mkdir backend && cd backend

Create the specific package.json for the node/express server.

npm init -y

Install following NPM packages.

npm install body-parser cors express mongoose --save

Install nodemon npm package as a development dependency to avoid re-starting the server every time we make the changes in the server files.

npm install nodemon --save-dev

Create database folder and also create db.js file in the backend folder’s root then paste the following code in it.

module.exports = {
  db: 'mongodb://localhost:27017/node_db'
};

Create model folder and also create Song.js file in the backend folder’s root then add the following code in it.

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

let Song = new Schema({
  song_name: {
    type: String
  },
  artist: {
    type: String
  }
}, {
  collection: 'songs'
})

module.exports = mongoose.model('Song', Song)

Next, create REST APIs for creating, reading, updating and deleting song data for our Ionic 4 CRUD mobile app. We will take help of express js middlewares, and later we will learn how to consume REST APIs in Ionic4/Angular app.

Create routes folder and also create song.route.js file in the backend folder’s root then add the following code in it.

const express = require('express');
const app = express();
const songRoute = express.Router();

let SongModel = require('../model/Song');

// Add Song
songRoute.route('/create-song').post((req, res, next) => {
  SongModel.create(req.body, (error, data) => {
    if (error) {
      return next(error)
    } else {
      res.json(data)
    }
  })
});

// Get all songs
songRoute.route('/').get((req, res) => {
  SongModel.find((error, data) => {
    if (error) {
      return next(error)
    } else {
      res.json(data)
    }
  })
})

// Get single song
songRoute.route('/get-song/:id').get((req, res) => {
  SongModel.findById(req.params.id, (error, data) => {
    if (error) {
      return next(error)
    } else {
      res.json(data)
    }
  })
})

// Update song
songRoute.route('/update-song/:id').put((req, res, next) => {
  SongModel.findByIdAndUpdate(req.params.id, {
    $set: req.body
  }, (error, data) => {
    if (error) {
      return next(error);
      console.log(error)
    } else {
      res.json(data)
      console.log('Song successfully updated!')
    }
  })
})

// Delete song
songRoute.route('/delete-song/:id').delete((req, res, next) => {
  SongModel.findByIdAndRemove(req.params.id, (error, data) => {
    if (error) {
      return next(error);
    } else {
      res.status(200).json({
        msg: data
      })
    }
  })
})

module.exports = songRoute;

In the next step we will configure node/express server, create app.js file in the backend folder’s root and add the given below code inside of it.

let express = require('express'),
  path = require('path'),
  mongoose = require('mongoose'),
  cors = require('cors'),
  bodyParser = require('body-parser'),
  dataBaseConfig = require('./database/db');

// Connecting mongoDB
mongoose.Promise = global.Promise;
mongoose.connect(dataBaseConfig.db, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  useFindAndModify: false
}).then(() => {
  console.log('Database connected sucessfully ')
},
  error => {
    console.log('Could not connected to database : ' + error)
  }
)

const songRoute = require('./routes/song.route')

const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: false
}));
app.use(cors());

// RESTful API root
app.use('/api', songRoute)

// PORT
const port = process.env.PORT || 3000;

app.listen(port, () => {
  console.log('PORT Connected on: ' + port)
})

// Find 404 and hand over to error handler
app.use((req, res, next) => {
  next(createError(404));
});

// Find 404 and hand over to error handler
app.use((req, res, next) => {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  console.error(err.message);
  if (!err.statusCode) err.statusCode = 500;
  res.status(err.statusCode).send(err.message);
});

Go to backend > package.json file and update the main: "index.js" name to main: "app.js" and you are ready to start the backend server. Run the below command while staying in the backend folder.

nodemon server

Start MongoDB server:
Open the terminal and run the below command in it to start the mongoDB server.

mongod

REST API URL – http://localhost:3000/api

6. Create Ionic 4 Angular 8 API Service

We will take the help of Ionic 4/Angular 8 Service to manage REST API in our CRUD mobile app. To make API calls, we need to import HttpClientModule service in the Ionic 4 Angular project.

Head over to src/app/app.module.ts then import and register HttpClientModule.

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

@NgModule({
  declarations: [...],
  entryComponents: [...],
  imports: [
    HttpClientModule
  ],
  providers: [...],
  bootstrap: [...]
})

export class AppModule { }

For type checking, we need to create a Song class. Generate a shared folder then create song.ts file in it. Include the given below code in it to define the song data type.

export class Song {
    _id: number;
    song_name: string;
    artist: string;
}

Run the following command to create Ionic 4/Angular 8 Service for handling REST API calls inside the shared folder:

ng generate service shared/song

Next, place the following code in shared/song.service.ts:

import { Injectable } from '@angular/core';
import { Song } from './song';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { HttpClient, HttpHeaders } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})

export class SongService {

  httpOptions = {
    headers: new HttpHeaders({ 'Content-Type': 'application/json' })
  };

  constructor(private http: HttpClient) { }

  addSong(song: Song): Observable<any> {
    return this.http.post<Song>('http://localhost:3000/api/create-song', song, this.httpOptions)
      .pipe(
        catchError(this.handleError<Song>('Add Song'))
      );
  }

  getSong(id): Observable<Song[]> {
    return this.http.get<Song[]>('http://localhost:3000/api/get-song/' + id)
      .pipe(
        tap(_ => console.log(`Song fetched: ${id}`)),
        catchError(this.handleError<Song[]>(`Get Song id=${id}`))
      );
  }

  getSongList(): Observable<Song[]> {
    return this.http.get<Song[]>('http://localhost:3000/api')
      .pipe(
        tap(songs => console.log('Songs fetched!')),
        catchError(this.handleError<Song[]>('Get Songs', []))
      );
  }

  updateSong(id, song: Song): Observable<any> {
    return this.http.put('http://localhost:3000/api/update-song/' + id, song, this.httpOptions)
      .pipe(
        tap(_ => console.log(`Song updated: ${id}`)),
        catchError(this.handleError<Song[]>('Update Song'))
      );
  }

  deleteSong(id): Observable<Song[]> {
    return this.http.delete<Song[]>('http://localhost:3000/api/delete-song/' + id, this.httpOptions)
      .pipe(
        tap(_ => console.log(`Song deleted: ${id}`)),
        catchError(this.handleError<Song[]>('Delete Song'))
      );
  }

  private handleError<T>(operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {
      console.error(error);
      console.log(`${operation} failed: ${error.message}`);
      return of(result as T);
    };
  }
}

To make the Http request we imported HttpClientModule in AppModule, now import RxJS operators to send the HTTP request across the server and get the response. Import observable, of, catchError and tap along with HttpClient and HttpHeaders.

7. Ionic 4/Angular 8 Display Data List & Delete Data

Now, we will display data list in Ionic 4 mobile app. Open home/home.page.ts file and add the following code in it.

import { Component, OnInit } from '@angular/core';
import { SongService } from './../shared/song.service';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})

export class HomePage implements OnInit {
  Songs: any = [];

  constructor(
    private songService: SongService
  ) {
  }

  ngOnInit() { }

  ionViewDidEnter() {
    this.songService.getSongList().subscribe((res) => {
      console.log(res)
      this.Songs = res;
    })
  }

  deleteSong(song, i) {
    if (window.confirm('Do you want to delete user?')) {
      this.songService.deleteSong(song._id)
        .subscribe(() => {
          this.Songs.splice(i, 1);
          console.log('Song deleted!')
        }
        )
    }
  }
}

Import the Angular 8 service and inject into the constructor it will allow us to consume the REST APIs and render the data into the Ionic 4 view. We use the ionViewDidEnter() page life cycle hook this hook will update the data in the Ionic view if the data is updated in the database. We also declared the deleteSong() function it helps in removing the song data from the Ionic 4 as well as the mongoDB database.

Next, open the home/home.page.html file and include the following code in it.

<ion-header>
  <ion-toolbar>
    <ion-title>
      Ionic Music App
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-list class="ios list-ios hydrated">
    <ion-list-header class="ios hydrated">
      Song List
    </ion-list-header>

    <ion-item *ngFor="let song of Songs" class="item-label item ios in-list ion-focusable hydrated">
      <ion-label class="sc-ion-label-ios-h sc-ion-label-ios-s ios hydrated">
        <h2>{{song.song_name}}</h2>
        <h3>{{song.artist}}</h3>
      </ion-label>

      <div class="item-note" item-end>
        <button ion-button clear [routerLink]="['/edit-song/', song._id]">
          <ion-icon name="create" style="zoom:2.0"></ion-icon>
        </button>
        <button ion-button clear (click)="deleteSong(song, i)">
          <ion-icon name="trash" style="zoom:2.0"></ion-icon>
        </button>
      </div>
    </ion-item>
  </ion-list>

  <!-- fab placed to the bottom start -->
  <ion-fab vertical="bottom" horizontal="end" slot="fixed" routerLink="/add-song">
    <ion-fab-button>
      <ion-icon name="add"></ion-icon>
    </ion-fab-button>
  </ion-fab>
</ion-content>

Here, we used the Ionic HTML to display the data list in the Ionic view we are showing the data with the help of Angular’s *ngFor directive. Declare the routerLink directive and pass the edit-song route to navigate to the edit page.

To know more about Ionic’s UI components check out here.

8. Add Data in Ionic 4

To add the data, we need to import and register FormsModule and ReactiveFormsModule in page’s module. Now, we need to remember that we have to import these Angular 8 form services in every page’s module rather than using it globally in the app.module.ts file.

Open add-song.module.ts file and add the following code.

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

@NgModule({
  imports: [
    FormsModule,
    ReactiveFormsModule
  ]
})

Open add-song.page.ts file and add the following code.

import { Component, OnInit, NgZone } from '@angular/core';
import { SongService } from './../shared/song.service';
import { Router } from '@angular/router';
import { FormGroup, FormBuilder } from "@angular/forms";

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

export class AddSongPage implements OnInit {
  songForm: FormGroup;

  constructor(
    private songAPI: SongService,
    private router: Router,
    public fb: FormBuilder,
    private zone: NgZone
  ) {
    this.songForm = this.fb.group({
      song_name: [''],
      artist: ['']
    })
  }

  ngOnInit() { }

  onFormSubmit() {
    if (!this.songForm.valid) {
      return false;
    } else {
      this.songAPI.addSong(this.songForm.value)
        .subscribe((res) => {
          this.zone.run(() => {
            console.log(res)
            this.songForm.reset();
            this.router.navigate(['/home']);
          })
        });
    }
  }
}

Open add-song.page.html file and add the following code.

<ion-header>
  <ion-toolbar class="ios hydrated">
    <ion-buttons slot="start">
      <ion-back-button defaultHref="home"></ion-back-button>
    </ion-buttons>
    <ion-title class="ios title-ios hydrated">Add Song</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-list lines="full" class="ion-no-margin ion-no-padding ios list-ios list-lines-full list-ios-lines-full hydrated">
    <form [formGroup]="songForm" (ngSubmit)="onFormSubmit()">
      <ion-item>
        <ion-label position="floating">Name</ion-label>
        <ion-input formControlName="song_name" type="text" required></ion-input>
      </ion-item>

      <ion-item>
        <ion-label position="floating">Artist</ion-label>
        <ion-input formControlName="artist" type="text" required>
        </ion-input>
      </ion-item>

      <ion-row>
        <ion-col>
          <ion-button type="submit" color="primary" shape="full" expand="block">Add Song</ion-button>
        </ion-col>
      </ion-row>
    </form>
  </ion-list>
</ion-content>
9. Ionic 4 Edit Data

To edit the data you need to open the edit-song.module.ts file and add the following code in it.

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

@NgModule({
  imports: [
    FormsModule,
    ReactiveFormsModule
  ]
})

Next, open the edit-song.page.ts file and add the following code inside of it.

import { Component, OnInit } from '@angular/core';
import { SongService } from './../shared/song.service';
import { ActivatedRoute, Router } from "@angular/router";
import { FormGroup, FormBuilder } from "@angular/forms";

@Component({
  selector: 'app-edit-song',
  templateUrl: './edit-song.page.html',
  styleUrls: ['./edit-song.page.scss'],
})
export class EditSongPage implements OnInit {

  updateSongForm: FormGroup;
  id: any;

  constructor(
    private songAPI: SongService,
    private actRoute: ActivatedRoute,
    private router: Router,
    public fb: FormBuilder
  ) {
    this.id = this.actRoute.snapshot.paramMap.get('id');
  }

  ngOnInit() {
    this.getSongData(this.id);
    this.updateSongForm = this.fb.group({
      song_name: [''],
      artist: ['']
    })
  }

  getSongData(id) {
    this.songAPI.getSong(id).subscribe(res => {
      this.updateSongForm.setValue({
        song_name: res['song_name'],
        artist: res['artist']
      });
    });
  }

  updateForm() {
    if (!this.updateSongForm.valid) {
      return false;
    } else {
      this.songAPI.updateSong(this.id, this.updateSongForm.value)
        .subscribe((res) => {
          console.log(res)
          this.updateSongForm.reset();
          this.router.navigate(['/home']);
        })
    }
  }

}

Next, open the edit-song.page.html file and add the following code inside of it.

<ion-header>
  <ion-toolbar class="ios hydrated">
    <ion-buttons slot="start">
      <ion-back-button defaultHref="home"></ion-back-button>
    </ion-buttons>
    <ion-title class="ios title-ios hydrated">Add Song</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-list lines="full" class="ion-no-margin ion-no-padding ios list-ios list-lines-full list-ios-lines-full hydrated">
    <form [formGroup]="updateSongForm" (ngSubmit)="updateForm()">
      <ion-item>
        <ion-label position="floating">Song name</ion-label>
        <ion-input formControlName="song_name" type="text" required></ion-input>
      </ion-item>

      <ion-item>
        <ion-label position="floating">Artist</ion-label>
        <ion-input formControlName="artist" type="text" required>
        </ion-input>
      </ion-item>

      <ion-row>
        <ion-col>
          <ion-button type="submit" color="primary" shape="full" expand="block">Updatec Song</ion-button>
        </ion-col>
      </ion-row>
    </form>
  </ion-list>
</ion-content>
10. Conclusion

Finally, we have completed Ionic 4/Cordova and Angular 8 tutorial. In this tutorial we learned how to create Ionic 4 Hybrid CRUD mobile app. We also learned how to create REST APIs with the help of Node/Express and save the data from the Ionic 4 frontend to MongoDB database. To compare your code with this tutorial, you can download the complete code from this Github repo.

How to Build Mobile Apps with Angular, Ionic 4, and Spring Boot

How to Build Mobile Apps with Angular, Ionic 4, and Spring Boot

Run Your Ionic App on Android. Make sure you're using Java 8. Run ionic cordova prepare android. Open platforms/android in Android Studio, upgrade Gradle if prompted. Set launchMode to singleTask in AndroidManifest.xml. Start your app using Android Studio...

In this brief tutorial, I’ll show you to use Ionic for JHipster v4 with Spring Boot and JHipster 6.

To complete this tutorial, you’ll need to have Java 8+, Node.js 10+, and Docker installed. You’ll also need to create an Okta developer account.

Create a Spring Boot + Angular App with JHipster

You can install JHipster via Homebrew (brew install jhipster) or with npm.

npm i -g [email protected]

Once you have JHipster installed, you have two choices. There’s the quick way to generate an app (which I recommend), and there’s the tedious way of picking all your options. I don’t care which one you use, but you must select OAuth 2.0 / OIDCauthentication to complete this tutorial successfully.

Here’s the easy way:

mkdir app && cd app

echo "application { config { baseName oauth2, authenticationType oauth2, \
  buildTool gradle, testFrameworks [protractor] }}" >> app.jh

jhipster import-jdl app.jh

The hard way is you run jhipster and answer a number of questions. There are so many choices when you run this option that you might question your sanity. At last count, I remember reading that JHipster allows 26K+ combinations!

The project generation process will take a couple of minutes to complete if you’re on fast internet and have a bad-ass laptop. When it’s finished, you should see output like the following.

OIDC with Keycloak and Spring Security

JHipster has several authentication options: JWT, OAuth 2.0 / OIDC, and UAA. With JWT (the default), you store the access token on the client (in local storage). This works but isn’t the most secure. UAA involves using your own OAuth 2.0 authorization server (powered by Spring Security), and OAuth 2.0 / OIDC allows you to use Keycloak or Okta.

Spring Security makes Keycloak and Okta integration so incredibly easy it’s silly. Keycloak and Okta are called "identity providers" and if you have a similar solution that is OIDC-compliant, I’m confident it’ll work with Spring Security and JHipster.

Having Keycloak set by default is nice because you can use it without having an internet connection.

To log into the JHipster app you just created, you’ll need to have Keycloak up and running. When you create a JHipster project with OIDC for authentication, it creates a Docker container definition that has the default users and roles. Start Keycloak using the following command.

docker-compose -f src/main/docker/keycloak.yml up -d

Start your application with ./gradlew (or ./mvnw if you chose Maven) and you should be able to log in using "admin/admin" for your credentials.

Open another terminal and prove all the end-to-end tests pass:

npm run e2e

If your environment is setup correctly, you’ll see output like the following:

> [email protected] e2e /Users/mraible/app
> protractor src/test/javascript/protractor.conf.js

[16:02:18] W/configParser - pattern ./e2e/entities/**/*.spec.ts did not match any files.
[16:02:18] I/launcher - Running 1 instances of WebDriver
[16:02:18] I/direct - Using ChromeDriver directly...


  account
    ✓ should fail to login with bad password
    ✓ should login successfully with admin account (1754ms)

  administration
    ✓ should load metrics
    ✓ should load health
    ✓ should load configuration
    ✓ should load audits
    ✓ should load logs


  7 passing (15s)

[16:02:36] I/launcher - 0 instance(s) of WebDriver still running
[16:02:36] I/launcher - chrome #01 passed
Execution time: 19 s.

OIDC with Okta and Spring Security

To switch to Okta, you’ll first need to create an OIDC app. If you don’t have an Okta Developer account, now is the time!

Log in to your Okta Developer account.

  • In the top menu, click on Applications
  • Click on Add Application
  • Select Web and click Next
  • Enter JHipster FTW! for the Name (this value doesn’t matter, so feel free to change it)
  • Change the Login redirect URI to be <a href="http://localhost:8080/login/oauth2/code/oidc" target="_blank">http://localhost:8080/login/oauth2/code/oidc</a>
  • Click Done, then Edit and add <a href="http://localhost:8080" target="_blank">http://localhost:8080</a> as a Logout redirect URI
  • Click Save

These are the steps you’ll need to complete for JHipster. Start your JHipster app using a command like the following:

SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI=https://{yourOktaDomain}/oauth2/default \
  SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_ID=$clientId \
  SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_SECRET=$clientSecret ./gradlew

Create a Native App for Ionic

You’ll also need to create a Native app for Ionic. The reason for this is because Ionic for JHipster is configured to use PKCE(Proof Key for Code Exchange). The current Spring Security OIDC support in JHipster still requires a client secret. PKCE does not.

Go back to the Okta developer console and follow the steps below:

  • In the top menu, click on Applications
  • Click on Add Application
  • Select Native and click Next
  • Enter Ionic FTW! for the Name
  • Add Login redirect URIs: <a href="http://localhost:8100/implicit/callback" target="_blank">http://localhost:8100/implicit/callback</a> and dev.localhost.ionic:/callback
  • Click Done, then Edit and add Logout redirect URIs: <a href="http://localhost:8100/implicit/logout" target="_blank">http://localhost:8100/implicit/logout</a> and dev.localhost.ionic:/logout
  • Click Save

You’ll need the client ID from your Native app, so keep your browser tab open or copy/paste it somewhere.

Create Groups and Add Them as Claims to the ID Token

In order to login to your JHipster app, you’ll need to adjust your Okta authorization server to include a groups claim.

On Okta, navigate to Users > Groups. Create ROLE_ADMIN and ROLE_USER groups and add your account to them.

Navigate to API > Authorization Servers, click the Authorization Servers tab and edit the default one. Click the Claims tab and Add Claim. Name it "groups" or "roles" and include it in the ID Token. Set the value type to "Groups" and set the filter to be a Regex of .*. Click Create.

Navigate to <a href="http://localhost:8080" target="_blank">http://localhost:8080</a>, click sign in and you’ll be redirected to Okta to log in.

Enter the credentials you used to signup for your account, and you should be redirected back to your JHipster app.

Generate Entities for a Photo Gallery

Let’s enhance this example a bit and create a photo gallery that you can upload pictures to. Kinda like Flickr, but waaayyyy more primitive.

JHipster has a JDL (JHipster Domain Language) feature that allows you to model the data in your app, and generate entities from it. You can use its JDL Studio feature to do this online and save it locally once you’ve finished.

I created a data model for this app that has an Album, Photo, and Tag entities and set up relationships between them. Below is a screenshot of what it looks like in JDL Studio.

Copy the JDL below and save it in a photos.jdl file in the root directory of your project.

entity Album {
  title String required,
  description TextBlob,
  created Instant
}

entity Photo {
  title String required,
  description TextBlob,
  image ImageBlob required,
  taken Instant
}

entity Tag {
  name String required minlength(2)
}

relationship ManyToOne {
  Album{user(login)} to User,
  Photo{album(title)} to Album
}

relationship ManyToMany {
  Photo{tag(name)} to Tag{photo}
}

paginate Album with pagination
paginate Photo, Tag with infinite-scroll

You can generate entities and CRUD code (Java for Spring Boot; TypeScript and HTML for Angular) using the following command:

jhipster import-jdl photos.jdl

When prompted, type a to update existing files.

This process will create Liquibase changelog files (to create your database tables), entities, repositories, Spring MVC controllers, and all the Angular code that’s necessary to create, read, update, and delete your data objects. It’ll even generate Jest unit tests and Protractor end-to-end tests!

When the process completes, restart your app, and confirm that all your entities exist (and work) under the Entities menu.

You might notice that the entity list screen is pre-loaded with data. This is done by faker.js. To turn it off, edit src/main/resources/config/application-dev.yml, search for liquibase and set its contexts value to dev. I made this change in this example’s code and ran ./gradlew clean to clear the database.

liquibase:
  # Add 'faker' if you want the sample data to be loaded automatically
  contexts: dev

Develop a Mobile App with Ionic and Angular

Getting started with Ionic for JHipster is similar to JHipster. You simply have to install the Ionic CLI, Yeoman, the module itself, and run a command to create the app.

npm i -g [email protected] [email protected] yo
yo jhipster-ionic

If you have your app application at ~/app, you should run this command from your home directory (~). Ionic for JHipster will prompt you for the location of your backend application. Use mobile for your app’s name and app for the JHipster app’s location.

Type a when prompted to overwrite mobile/src/app/app.component.ts.

Open mobile/src/app/auth/auth.service.ts in an editor, search for data.clientId and replace it with the client ID from your Native app on Okta.

// try to get the oauth settings from the server
this.requestor.xhr({method: 'GET', url: AUTH_CONFIG_URI}).then(async (data: any) => {
  this.authConfig = {
    identity_client: '{yourClientId}',
    identity_server: data.issuer,
    redirect_url: redirectUri,
    end_session_redirect_url: logoutRedirectUri,
    scopes,
    usePkce: true
  };
  ...
}

When using Keycloak, this change is not necessary.### Add Claims to Access Token

In order to authentication successfully with your Ionic app, you have to do a bit more configuration in Okta. Since the Ionic client will only send an access token to JHipster, you need to 1) add a groups claim to the access token and 2) add a couple more claims so the user’s name will be available in JHipster.

Navigate to API > Authorization Servers, click the Authorization Servers tab and edit the default one. Click the Claims tab and Add Claim. Name it "groups" and include it in the Access Token. Set the value type to "Groups" and set the filter to be a Regex of .*. Click Create.

Add another claim, name it given_name, include it in the access token, use Expression in the value type, and set the value to user.firstName. Optionally, include it in the profile scope. Perform the same actions to create a family_name claim and use expression user.lastName.

When you are finished, your claims should look as follows.

Run the following commands to start your Ionic app.

cd mobile
ionic serve

You’ll see a screen with a sign-in button. Click on it, and you’ll be redirected to Okta to authenticate.

Now that you having log in working, you can use the entity generator to generate Ionic pages for your data model. Run the following commands (in your ~/mobile directory) to generate screens for your entities.

yo jhipster-ionic:entity album

When prompted to generate this entity from an existing one, type Y. Enter ../app as the path to your existing application. When prompted to regenerate entities and overwrite files, type Y. Enter a when asked about conflicting files.

Go back to your browser where your Ionic app is running (or restart it if you stopped it). Click on Entities on the bottom, then Albums. Click the blue + icon in the bottom corner, and add a new album.

Click the ✔️ in the top right corner to save your album. You’ll see a success message and it listed on the next screen.

Refresh your JHipster app’s album list and you’ll see it there too!

Generate code for the other entities using the following commands and the same answers as above.

yo jhipster-ionic:entity photo
yo jhipster-ionic:entity tag

Run Your Ionic App on iOS

To generate an iOS project for your Ionic application, run the following command:

ionic cordova prepare ios

When prompted to install the ios platform, type Y. When the process completes, open your project in Xcode:

open platforms/ios/MyApp.xcworkspace

You’ll need to configure code signing in the General tab, then you should be able to run your app in Simulator.

Log in to your Ionic app, tap Entities and view the list of photos.

Add a photo in the JHipster app at <a href="http://localhost:8080" target="_blank">http://localhost:8080</a>.

To see this new album in your Ionic app, pull down with your mouse to simulate the pull-to-refresh gesture on a phone. Looky there - it works!

There are some gestures you should know about on this screen. Clicking on the row will take you to a view screen where you can see the photo’s details. You can also swipe left to expose edit and delete buttons.

Run Your Ionic App on Android

Deploying your app on Android is very similar to iOS. In short:

  1. Make sure you’re using Java 8
  2. Run ionic cordova prepare android
  3. Open platforms/android in Android Studio, upgrade Gradle if prompted
  4. Set launchMode to singleTask in AndroidManifest.xml
  5. Start your app using Android Studio
  6. While your app is starting, run adb reverse tcp:8080 tcp:8080 so the emulator can talk to JHipster
Learn More About Ionic 4 and JHipster 6

Ionic is a nice way to leverage your web development skills to build mobile apps. You can do most of your development in the browser, and deploy to your device when you’re ready to test it. You can also just deploy your app as a PWA and not both to deploy it to an app store.

JHipster supports PWAs too, but I think Ionic apps look like native apps, which is a nice effect. There’s a lot more I could cover about JHipster and Ionic, but this should be enough to get you started.

You can find the source code for the application developed in this post on GitHub at @oktadeveloper/okta-ionic4-jhipster-example.

Thank you for reading!

Ionic 4 tutorial: Learn to build a CRUD To-do App using Ionic 4

Ionic 4 tutorial: Learn to build a CRUD To-do App using Ionic 4

In this Ionic 4 tutorial, you would learn how to create a simple CRUD (Create, Read, Update and Delete) to-do list app using Ionic 4, which is also connected to Firebase.

Hey all! This is a post on an up and coming tech topic — Ionic! By the end of this post you would learn how to create a simple CRUD (Create, Read, Update and Delete) to-do list app, which is also connected to Firebase.

Hybrid Mobile Applications — What are they?

Simply put, they are mobile apps created by the easier to learn languages; HTML, CSS, and JavaScript. The beauty of developing a hybrid mobile app is the fact that they can be compiled to work with any platform. If you are lazy, like yours truly, you’ll find it easier to use one code to build many apps, instead of developing separate apps for each platform.

Ionic is one of the popular frameworks to make your own hybrid mobile app. It can be built into an Android, iOS, Windows phone, Progressive Web or Desktop application. And testing the app is so much easier since it can be live reloaded right onto your browser.

Step 1 — Setting it all up

Initially, sign up for an Ionic Pro account, here. That will make building and shipping the app easier. You might have to sign in sometime during the process of project creation.

To start coding your first Ionic App, there are a bunch of things you need;

  1. Node.js — This is pretty easy. Simply go to the Node.js website and download the ideal installer for you. We need the Node Package Manager, aptly named npm, to install all the dependencies for the many modules you would want to use in your app. If you develop on a Mac and have Homebrew installed, simply type in the command brew install npm on the console.
  2. TypeScript — TypeScript, a superset of JavaScript, is used instead of JS for the majority of the code. After installing Node.js, on the console, type in npm install -g typescript .
  3. Cordova — Cordova is a framework that builds the HTML, CSS and JS/TS code into an app. To install, type npm install -g cordova
  4. And finally, Ionic — Type in npm install -g ionic .

Bonus — You can download all three in one go with this command too! npm install -g typescript cordova ionic.

Now that you have set up the environment, let's get this party started!! ??

Creating your first app

From within the console, move to the folder in which you want to store the app. My personal preference is to have a dedicated folder for all my Ionic projects in my Documents.

Then, type in ionic start . The console then prompts you for a name for the project, like so, Project name: Tasks.

It then prompts you to specify the type of application.

? Starter template: (Use arrow keys)
  tabs     | A starting project with a simple tabbed interface
> blank    | A blank starter project
  sidemenu | A starting project with a side menu with navigation in the content area
  super    | A starting project complete with pre-built pages, providers and best practices for Ionic development.
  tutorial | A tutorial based project that goes along with the Ionic documentation
  aws      | AWS Mobile Hub Starter

For now, let's make it a blank project, a to-do list with all CRUD functions in one page. It will then prompt you for permission to add the Android and iOS platforms.

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

It will proceed to download extra dependencies that will allow you to live reload the app in emulators and devices. Once the native SDK’s are downloaded you are prompted to add the Ionic Pro SDK, if you wish to do so.

? Install the free Ionic Pro SDK and connect your app? y

If you do pick yes, the console will then prompt you for your Ionic Pro email and password, set up at the beginning of this post.

? Email: 
? Password:

Thereafter, you have the option to either link this app to an existing one, to create a new one entirely.

? What would you like to do? (Use arrow keys)
  Link an existing app on Ionic Pro
> Create a new app on Ionic Pro

The console then proceeds to ask your preferred git host, to store your repository. I prefer GitHub, as it’s something I’m more familiar with.

? Which git host would you like to use? (Use arrow keys)
> GitHub
  Ionic Pro

Depending on your choice above, if you picked GitHub as I have, you may require to open your browser to give your credentials and sign in. Once done, return back to the console. You then need to link this app to the repository or create a new one. If you don’t have a repository, go back to GitHub and create one now. Once the new repository is created, come back to the console and type y .

? Does the repository exist on GitHub? y

Afterward, pick the correct repository from the list displayed on the console. I will be using only the master branch for now and will go with the former option.

? Which would you like to do? (Use arrow keys)
> Link to master branch only
  Link to specific branches

And FINALLY, we’re done creating the app!! ??

But, If you picked Ionic Pro as a git host, pick the option to generate an SSH key pair.

? How would you like to connect to Ionic Pro? (Use arrow keys)
> Automatically setup new a SSH key pair for Ionic Pro
  Use an existing SSH key pair
  Skip for now
  Ignore this prompt forever

And we’re done here too! Now to have a look at the app

There are two different commands to view the app on the browser.

  1. ionic serve
  2. ionic serve -l

ionic serve displays the app in the view of a web application.

ionic serve -l displays the app in the many mobile device platforms. You will need to download it from within the console, when prompted, to get this view.

And that’s a wrap for today! We successfully created and linked an Ionic 4 app to a version control host.

The Project Structure

  1. app.module.ts — The entry point of the app. Any and all components, pages, modules, and providers need to be added to this file, as it keeps track and controls the many resources used by the app.
  2. app.components.ts — The first page that is loaded as the app starts running, with all the code you wish to execute first. Pages that you might wish the user to view first, like the login screen, are put in this component.
  3. app.html — The template of the app, where the other UI pages will mount onto.
  4. app.scss — The page that holds all the Sass variables and styles to be used globally within the app.

Let’s head on over to the main component that we will be amending for this application, home.

As seen above, the home component has three pages;

  1. home.html — The view/UI of the page is coded here, using HTML.
  2. home.scss — Any page-specific styling is to be added here, along with Sass variables to be used within the page.
  3. home.ts — The operational logic, in our case adding new tasks to the list, is coded in TypeScript here.

Step 2 - Implementing the CRUD operations

What I hope to implement as seen above, is a very simple design; a text input to type the tasks, a button to add it to the list, a list view to view the items and finally a delete button to remove the items from the list. I might change up the design later.

Go ahead and open your editor. Let's take a quick run through all the pages and components found in the current directory.

Creating the UI for C and R

To begin, let’s tackle the UI first. When you open up home.html, this is the current code in the page.

<ion-header>
	<ion-navbar>
		<ion-title>Ionic Blank</ion-title>
	</ion-navbar>
</ion-header>
<ion-content padding>
	The world is your oyster.
	<p>If you get lost, the
		<a href="http://ionicframework.com/docs/v2">docs</a>
	will be your guide.
	</p>
</ion-content>

You can then remove everything within the <ion-content> tags. This is the body of the page and elements within those tags will be seen.

Now add an input tag in the body, so we can enter in the task, followed by a button, to call a method to add the task to the list.

<ion-content padding>
	<input type="text" placeholder="Enter task">
	<button>Add Task</button>
</ion-content>

Not pretty, right? Let’s add some styling now!

Ionic has a special input tag <ion-input> , that comes with some styling coded within it, so go ahead and switch boring old <input> to <ion-input> !

Ionic also comes with certain special classes which have styling, like the ion-button. I also want to have the button to the end of the input, and not right below. The final changes look like this;

<ion-content padding>
	<ion-item>
		<ion-input type="text" placeholder="Enter task" [(ngModel)]="taskName"/>
		<div class="item-note" item-end>
			<button ion-button>Add Task</button>
		</div>
	</ion-item>
</ion-content>

So much better, right!? And all this without writing any CSS! Let’s have another look at the code above.

<ion-item> tag is normally used with the <ion-list> element. But, using this here, with the input within this element, gives it an added style on focus or use. Using the class item-note for a div element allows the button to be in line with the input tag. Doing so, gives a more seamless and sleek design, compared to the first one. Since Angular is also integrated into Ionic, we can use ngModel to easily link values in the views to that in the TypeScript files.

Ionic also comes with a built-in pack of icons, Ionicons. Its very simple to use, and a quick example would be substituting the Add task text with <ion-icon name="add"></ion-icon> . Find more on Ionicons, here.

The final result! I’m quite happy with what it looks like now, but feel free to play around more with colors and styling.

Implementing create and read functionality

Now that the UI has been done, let's move on to giving this a function. It’s time to look at home.ts. You start off with code that looks like this;

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';

@Component({
    selector: 'page-home',
    templateUrl: 'home.html'
})

export class HomePage {
    constructor(public navCtrl: NavController) {  }
}

Let’s get a quick look at what we have here. You import any components or external modules, that you may need to use in this page at the very top. The next few lines describe the template to which the many functions you may write belong to and manipulate. And lastly, all the logic you may code. Any code you wish to execute before viewing or interacting with the page must be written within the constructor.

Since we will be adding new to-dos each time, we need a place to store it. The simplest way to do this is to initialize an array. If you have had experience with JavaScript previously, coding with TypeScript will be a piece of cake!

Let’s call our list taskList, but since we need the list to be accessed from more than one method of the code, we need to initialize it outside the constructor taskList = [];. Now to write code to handle the Add Task button click, let's call it addTask. All we need to do is capture the text in the input, and push it onto the array. Since we have used ngModel for the input tag, we can easily get the value inside it by using this.taskName. And adding values to an array is as easy as taskList.push(task). We also need to ensure that no empty string is being added to the list, so wrap the above statement in an if condition, checking if the taskName truly exists. The final home.ts code;

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';

@Component({
    selector: 'page-home',
    templateUrl: 'home.html'
})

export class HomePage {
    taskList = [];

    constructor(public navCtrl: NavController) {}

    addTask() {
        if (this.taskName.length > 0) {
            let task = this.taskName;
            this.taskList.push(task);
            this.taskName = "";
        }
    }
}

Note: Using the keyword let in TypeScript is the equivalent of using var, for variable declaration.

Now we can begin adding new tasks!

But how do we know something is being added???

Easy Peasy, ?Squeezy! That’s what the R in CRUD is there for!

Run the code and have a look

Time to C(reate) a way for us to R(ead) what we type! (See what I did there?)?

Let’s roll back to the home.html. So far, we have put an input tag and a button to add tasks; now to put a list to view it. We now need to link the method addTask() to the button in the (click) property, so that a list item is added to the array with each click.

<ion-list> is a special Ionic element for list views. The <ion-item> tag is used within it to generate each item in said list. *ngFor is an easy method of showing all elements within a list, by setting a standard view for each list item.

The final home.html code;

<ion-header>
	<ion-navbar>
		<ion-title>To-do List</ion-title>
	</ion-navbar>
</ion-header>
<ion-content padding>
	<ion-item>
		<ion-input type="text" [(ngModel)]="taskName" placeholder="Enter task"/>
		<div class="item-note" item-end>
			<button ion-button (click)="addTask()"><ion-icon name="add"></ion-icon></button>
		</div>
	</ion-item>
	<div padding>
		<ion-list>
			<ion-item *ngFor="let todo of taskList">
				{{todo}}
			</ion-item>
		</ion-list>
	</div>
</ion-content>

The variable todo is a temporary store for the element in the current index of the for loop (ngFor) within the list taskList, as declared in the home.ts.

Ready to see our app so far?

We did it!! It works!!????

But that was just Create and Read. Will still have to implement Update and Delete.

We will first begin with changing the GUI so that it can fit both the update and delete features. Thereafter move onto the TypeScript code, to show its functionality.

Change that look to include handlers for U and D

Whoops! Little forgetful ole me! I didn’t change the app name on the home page… ???

Go ahead and call it whatever you wish (I’ll be basic with ‘To-do List’).

The first step, to be done in the home.html, is to add the delete button to the left side of each list item. That’s easy! Reuse the very same code I used to include the addTask button next to the input in the <ion-item>, nesting this button within the div with the class item-note, but change that + into an ?️icon (don’t want to get confused now, do we?). Since this is a button, give the event handler the name deleteTask(). The button will also have another style class clear, which gives it a clear background. Since this button will be within the <ion-item> that is in the <ion-list>, it will be generated for all items in the list.

We need to add another button to the list to edit each task. Luckily more copying of code! Copy the whole button tag, but replace the icon ?️ to a ✏️ and the click handler to updateTask().

The code for each <ion-item> tag now looks like this

<ion-item *ngFor="let todo of taskList; let i = index">
	{{todo}}
	<div class="item-note" item-end>
		<button ion-button clear (click)="updateTask(i)">
			<ion-icon name="create"></ion-icon>
		</button>
		<button ion-button clear (click)="deleteTask(i)">
			<ion-icon name="trash"></ion-icon>
		</button>
	</div>
</ion-item>

The statement let i = index takes the index of the specific element in the list, so that we can pass it over to the method, so only the element to be deleted would be affected.

Pretty neat, huh??

I quite like it and it looks so much better than the wireframe I originally designed.

Implementing update and delete functionality

Now to add functionality to our ?️ and ✏️.

We need to create a new method within the home.ts called deleteTask(), as specified in home.html above. We pass the index of the array from within the ngFor loop, so we know the exact position of the task to be deleted. Hop on over to home.html, and pass the parameter i, which is the index of the element in the array, within the deleteTask method, like so deleteTask(i). As you have passed the index through to the home.ts, you simply need to use the splice() method on the array to remove the desired task, passing the index of the item to be removed as a parameter, like so this.taskList.splice(index, 1);.

The code for the deleteTask method is;

deleteTask(index){
    this.taskList.splice(index, 1);
}

Short and sweet! ? That’s all the coding we need to delete tasks!

Now to update, it will take a bit more typing (bear with me)!

My plan is to open up an alert asking the user to enter the update text of the task. To do that, we need to import the AlertController, a module found in ionic-angular. You import it using this line of code.

import { NavController, AlertController } from 'ionic-angular';

You then need to initialize it in the constructor, like so;

constructor(public navCtrl: NavController, public alertCtrl: AlertController)

You will then need to create an alert in the updateTask method to capture the new task name. To do so, you will need to pass the following into the create method of the AlertController;

  1. title — The title of the message.
  2. message — A longer message (if required).
  3. inputs — Input field with their name and placeholder (if any).
  4. buttons — Buttons along with their role or handler (if any).

The alert can be displayed afterward with the simple alert.present()command. I will be having two buttons, one is a cancel button, the second is to edit and the handler code will simply take the entered task and switch it with the previous value in the array. The code for the updateTask() method;

updateTask(index) {
    let alert = this.alertCtrl.create({
        title: 'Update Task?',
        message: 'Type in your new task to update.',
        inputs: [{ name: 'editTask', placeholder: 'Task' }],
        buttons: [{ text: 'Cancel', role: 'cancel' },
                  { text: 'Update', handler: data => {
                      this.taskList[index] = data.editTask; }
                  }
                 ]
    });
    alert.present();
}

It should all work perfectly now!

Want to see the final CRUD app?

And there you have it! ??

A fully operational CRUD to-do list, with minimal coding! That’s how easy Ionic can be.

I still believe we can make it a bit more user-friendly. Scroll down for more add-on functionality.

Bonus!! — Auto-focus

Do you know what I find annoying? I need to click on the input each time I want to add a new task, even at the beginning. Why not auto-focus the input after clicking the button?

That’s exactly what we will do!

Auto-focus on Ionic is not as easy as it is in classic HTML/JavaScript interactions. You need to import an extra component called ViewChild. You can then easily connect the input from the view (home.html) to the controller (home.ts), and manipulate it as well. You import it, like so;

import { Component, ViewChild } from '@angular/core';

You can then connect the input tag to the component, outside the constructor, using this line of code,

@ViewChild('taskInput') input;

taskInput is the id of the input tag on the home.html page. Go ahead and add#taskInput to the input tag. The input tag can now directly be handled from within the TypeScript file.

Ionic comes with a few methods that can be invoked on certain app events, such as when a page loads onto the view, unloads and so on. These are called lifecycle events, and more about then can be found here. We can cause the app to auto-focus on the input from within the ionViewDidLoad(), by setting a timeout. The code would be;

ionViewDidLoad(){
    setTimeout(() => {
        this.input.setFocus();
    },350);
}

For the auto-focus to work after you add the line this.input.setFocus(); as the last statement in the addTask() handler. Lets head on out to see the changes we made!

Now that’s what you call seamless…?

Step 3 - Integrating Firebase Authentication

Firebase has everything, from authorization to a database to file storage, one of the many reasons it’s a good choice to add to mobile apps. In this post, we will explore Firebase, create a project and make a handler component for Firebase in the app.

Setting up the Firebase console

But first step’s first, you need to create a project on the Firebase console. All you need is a Google account to access Firebase. So head on over here to get started. Add a new project and give it a name (I just called mine ‘Tasks’), agree to everything they ask and hit Create Project.

Now to set up the project to fit our needs.

All the areas of Firebase that we will be accessing will be found under Develop.

Namely;

  1. Authentication
  2. And Database.

Let’s have a look at Authentication.

As you can see, all methods of authentication have been disabled. For now enable the very basic of types, Email/Password, so we can begin using it to register an account.

Under templates, the many email templates for verification of email address to forget password are can be found. If you wish, you can change a few of the details, like the project name to be displayed and the name of the sender.

Now, onward to the Database section. Firebase has two types of databases;

  1. Realtime Database — a NoSQL database, that looks like one big JSON Object.
  2. Cloud Firestore — A collection of documents, which are essentially JSON Objects.

Firestore is the better option as it has a better structure compared to the normal Realtime Database. In the Realtime Database, anybody can write data anywhere, if they have the reference to the database, greatly affecting all the data stored. And for that reason, I picked Firestore and created the database in test mode, so we can assess the database.

Firestore in test mode does allow anyone to read and write into it, so let’s make it that only users who have registered to the app have access to the database. To do so, switch allow read, write: if false; for allow read, write:if request.auth.uid!=null;. Only registered users have a unique uid, with which to distinguish them. Most often, the uid is used as the ID to the users' object. I will be implementing the same for this project.

Once the rules are changed, we need to create a collection, so all our user documents can be put into it. Since we cannot have a collection without at least one document, make a fake user. You can delete it from the dashboard later.

As we have set up the Firebase dashboard, let’s move on the integrating Firebase into the app.

Linking Firebase to the app

There is a special module AngularFire you can download using npm to incorporate Firebase into the Ionic app. To download, type npm install firebase angularfire2 --save.

To use this module, you need to import it into the app.module.ts page, like so

import { AngularFireModule } from 'angularfire2';
import { AngularFireAuthModule } from 'angularfire2/auth';
import { AngularFirestoreModule } from 'angularfire2/firestore';

We also need to add the necessary config data for the app to access and use the correct database. This can be found in the Project Overview section, ‘Add Firebase to your web app’. You are required to call the JSON object firebaseConfig and initialize it after the imports.

export const firebaseConfig = {
    apiKey: "#######################################",
    authDomain: "###########.firebaseapp.com",
    databaseURL: "https://###########.firebaseio.com",
    projectId: "###########",
    storageBucket: "###########.appspot.com",
    messagingSenderId: "############"
};

One last step! You need to include the imported modules above, into the import array of @NgModule that contains all the components used in the app, initializing the AngularFireModule as well with the config object above.

@NgModule({
    ...
    imports: [
        ...
        AngularFireModule.initializeApp(firebaseConfig), 
        AngularFireAuthModule, 
        AngularFirestoreModule
    ]
})

AngularFireAuthModule comes with many methods pertaining to authorization, like signup, sign in, forgot password, etc. All the methods we will be using will be found in the auth property of AngularFireAuth. The methods being used are;

  1. signInWithEmailAndPassword() — Login
  2. createUserWithEmailAndPassword() — Register
  3. sendPasswordResetEmail() — Reset Password
  4. signOut() — Logout

Implementing all the authentication logic

We need to add a listener, to check if the user has logged in or not, and to display the correct response for either. We need to add the listener in the app.component.ts, as it’s the first page of the app that is loaded.

const authObserver = afAuth.authState.subscribe(user => {
    if (user) {
        this.rootPage = HomePage;
        authObserver.unsubscribe();
    } else {
        this.rootPage = LoginPage;
        authObserver.unsubscribe();
    }
});

Import the necessary other modules, like the HomePage, LoginPage, and AngularFireAuth.

Let’s start coding the Register page first.

First, to add a new page to the app. There are two ways to do this;

  1. Create a new folder within the pages folder inside src and create separate .scss, .ts and .html files.
  2. Or, be lazy (like me ?) and just type ionic g page <name of page> in the console. All three files will be auto-generated!

Since we need to conduct many validations on the data entered in the login, register and forgot password pages, we need to utilize a form group to have a track of all the fields in the form and to add any and all validation to each field, such as checking if the email looks like an actual email, password lengths, the works. We’ll first design the view of the page. In register.html, the form tag looks like so;

<form [formGroup]="signupForm" (submit)="signupUser()" novalidate>

novalidate is used as the actual validation is being added in the .ts file to the form group signupForm.

Then copy the exact item tag that we have been using to add task names in the home page (but remove that button, id and [(ngModule)] this time!). Add a tag for the users’ full name, email, password and confirm password. The type of input tag for the latter two is password and email for the email tag. You will also need to add a formControlName to each input tag. Add in a button as well of the type submit, to submit the form. The body of your register page must now look like this;

<form [formGroup]="signupForm" (submit)="signupUser()" novalidate>
  <ion-item>
    <ion-input formControlName="firstName" type="text" placeholder="First Name"></ion-input>
  </ion-item>
  <ion-item>
    <ion-input formControlName="lastName" type="text" placeholder="Last Name"></ion-input>
  </ion-item>  
  <ion-item>
    <ion-input formControlName="email" type="email" placeholder="Email"></ion-input>
  </ion-item>
  <ion-item>
    <ion-input formControlName="password" type="password" placeholder="Password"></ion-input>
  </ion-item>
  <ion-item>
    <ion-input formControlName="retype" type="password" placeholder="Confirm Password"></ion-input>
  </ion-item>
  <ion-grid>
    <ion-row>
      <ion-col style="text-align: center">
        <button ion-button center-all type="submit" [disabled]="!signupForm.valid">Create an Account</button>
      </ion-col>
    </ion-row>
  </ion-grid>
<form>

The Register button is disabled until the Lets now add validators to each input, in the register.ts page. We will need to import the following modules to the top of the page,

import { FormBuilder, FormGroup, Validators } from '@angular/forms';

initialize the form group outside of the constructor, so it can be accessed from anywhere in the component; public signupForm: FormGroup and initialize the form builder inside the parameters passed to the constructor, like so;

constructor(public navCtrl: NavController, public navParams: NavParams, public formBuilder: FormBuilder){}

Validators will be added to the form within the constructor like so;

this.signupForm = formBuilder.group({
  email: ['', Validators.compose([Validators.required])],
  password: ['', Validators.compose([Validators.minLength(6), Validators.required])],
  retype: ['', Validators.compose([Validators.minLength(6), Validators.required])],
  firstName: ['', Validators.compose([Validators.maxLength(30), Validators.pattern('[a-zA-Z ]*'), Validators.required])],
  lastName: ['', Validators.compose([Validators.maxLength(30), Validators.pattern('[a-zA-Z ]*'), Validators.required])]
});

Validators.compose creates a validation check for the value, according to the validations passed in its parameters. Most of these Validators are self-explanatory. The pattern Validator checks if the value fits a specific regex. But one question remains, how to validate if an email looks like an email? Apparently, we need to make one….

But don’t worry! It’s quite simple and the only logic to it is to see if it fits a certain regex.

We need to make a new folder ‘validators’ in the src folder and a file ‘email.ts’ within it. We will be declaring a static method to check the email. When validating the email, we send the formControl to the Validator, so in that case, we will need to import FormControl. Once the email is tested against the regex, we need to return a value to convey if the email is valid or not. The final code for the email validator is;

import { FormControl } from '@angular/forms';

export class EmailValidator {  
  static isValid(control: FormControl) {
    const re = /^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/.test(control.value);
    if (re) {
      return null;
    }
    return {
      "invalidEmail": true
    };
  }
}

Now import the EmailValidator into the register.ts and add it to the array within the Validators.compose method for the email input.

this.signupForm = formBuilder.group({
    email: ['', Validators.compose([Validators.required, EmailValidator.isValid])],
    ...
});

That’s it on the validation side.

Another added feature you can do is show an error message right below the input, or even have the input tag turn red if the validation returns a false. The code for the error message;

<ion-item class="error-message" *ngIf="!signupForm.controls.email.valid  && signupForm.controls.email.dirty">
    <p>Please enter a valid email.</p>
</ion-item>

*ngIf allows you to display the error only if the validation is false. The errors should be put right below each tag, altering the message and input name (in the above example ‘email’) accordingly.

The code for a red input on validation error;

[class.invalid]="!signupForm.controls.email.valid && signupForm.controls.email.dirty"

Add this inside each input, again changing the inputs’ name accordingly.

Now to handle the button click!

Create the method signupUser(). We will be using the AngularFireAuth modules’ method createUserWithEmailAndPassword(). This returns a promise, that we need to capture and according to the result, handle either the sign in of the user or display an error message. To make it more user-friendly, also show a loading carousel to the user as signup takes place.

As the button is only enabled when the whole form is valid, we do not need to recheck on that fact. We will first check if the password and the retyped password are the same, and if they are, create the new user and add their information to the Firestore. If the two are different, display an error message in the alert, stating that they are different.

signupUser() {
  if (this.signupForm.value.password == this.signupForm.value.retype) {
    this.afAuth.auth.createUserWithEmailAndPassword(this.signupForm.value.email, this.signupForm.value.password)
      .then(() => {
        let userId = this.afAuth.auth.currentUser.uid;
        let userDoc = this.firestore.doc<any>('users/' + userId);
        userDoc.set({
          firstName: this.signupForm.value.firstName,
          lastName: this.signupForm.value.lastName,
          email: this.signupForm.value.email
        });
        this.navCtrl.setRoot(HomePage);
      }, (error) => {
        this.loading.dismiss().then(() => {
          let alert = this.alertCtrl.create({
            message: error.message,
            buttons: [{ text: "Ok", role: 'cancel' }]
          });
          alert.present();
        });
      });

    this.loading = this.loadingCtrl.create({
      dismissOnPageChange: true,
      content: "Signing up.."
    });
    this.loading.present();
  } else {
    let alert = this.alertCtrl.create({
      message: "The passwords do not match.",
      buttons: [{ text: "Ok", role: 'cancel' }]
    });
    alert.present();
  }
}

You will need to additionally import AlertController, Loading, LoadingController, AngularFirestore and HomePage.

loading needs to be declared outside the constructor, so that it can be accessed by all the methods. AlertController, LoadingController and AngularFirestore needs to be initialized in the constructor parameters.

And (finally) the register page is done!

Whew! ?? This is the longest post I’ve ever written. And there’s still more to go…..

But don’t worry! The rest is all just copy + paste.

The next page to tackle is the Login page. Copy the entire Register page form to login.html, coz its time to make some changes for it to fit Login. Remove the first name, last name and retyped passwords’ input tags and error messages. On the form tag, change all instances of signupForm to loginForm.

Change the submit buttons’ text to ‘Login’ and the onSubmit method to loginUser(). Add two buttons as well, outside the form, to navigate to the register and reset password pages. The final body of login.html;

<form [formGroup]="loginForm" (submit)="loginUser()" novalidate>
  <ion-item>
    <ion-input formControlName="email" type="email" placeholder="Email" [class.invalid]="!loginForm.controls.email.valid && loginForm.controls.email.dirty"></ion-input>
  </ion-item>
  <ion-item class="error-message" *ngIf="!loginForm.controls.email.valid  && loginForm.controls.email.dirty">
    <p>Please enter a valid email.</p>
  </ion-item>
  <ion-item>
    <ion-input formControlName="password" type="password" placeholder="Password" [class.invalid]="!loginForm.controls.password.valid && loginForm.controls.password.dirty"></ion-input>
  </ion-item>
  <ion-item class="error-message" *ngIf="!loginForm.controls.password.valid  && loginForm.controls.password.dirty">
    <p>Your password must be more than 6 characters long</p>
  </ion-item>
  <ion-grid>
    <ion-row>
      <ion-col style="text-align: center">
        <button ion-button center-all type="submit" [disabled]="!loginForm.valid">Login</button>
      </ion-col>
    </ion-row>
  </ion-grid>
</form>
<button ion-button block clear color="danger" (click)="resetPwd()">
  I forgot my password
</button>
<button ion-button block clear (click)="createAccount()">
  Create a new account
</button>

There you have it! The UI is done.

The loginForm has the same Validators for the email and password fields. So, proceed to copy the same formBuilder, omitting the first name, last name and retyped password fields.

this.loginForm = formBuilder.group({
    email: ['', Validators.compose([Validators.required, EmailValidator.isValid])],
    password: ['', Validators.compose([Validators.minLength(6), Validators.required])]
});

The loginUser() method has similar code to that of the signupUser method. So copy that on to the login.ts as well. The change to be made, is to remove the password comparison and accessing the database.

loginUser() {
 this.afAuth.auth.signInWithEmailAndPassword(this.loginForm.value.email, this.loginForm.value.password).then(() => {
   this.navCtrl.setRoot(HomePage);
 }, (error) => {
   this.loading.dismiss().then(() => {
     let alert = this.alertCtrl.create({
       message: error.message,
       buttons: [{ text: "Ok", role: 'cancel' }]
     });
     alert.present();
   });
 });
 this.loading = this.loadingCtrl.create({
   dismissOnPageChange: true,
   content: "Logging in.."
 });
 this.loading.present();
}

You will need to import the exact extra modules to the login.ts as well, with the exception of the AngularFirestore, as you will not be accessing the database now.

Now to handle the buttons to the reset password and the registration page;

resetPwd() {
    this.navCtrl.push(ResetPasswordPage);
}

createAccount() {
    this.navCtrl.push(RegisterPage);
}

The pages work like a stack; you push the next page to the top of the stack and pop from the top as well.

Bear with me, we have one more page to go. Yay! More copy+paste!

For the reset password, we only require the email field, but still, need a form to validate the email entered. Much like for the Login page, copy the entire login.html form, remove all fields except the email input tag and error message, change all instances of loginForm to resetPwdForm. You are left with;

<form [formGroup]="resetPwdForm" (submit)="resetUserPwd()" novalidate>
  <ion-item>
    <ion-input formControlName="email" type="email" placeholder="Email" [class.invalid]="!resetPwdForm.controls.email.valid && resetPwdForm.controls.email.dirty"></ion-input>
  </ion-item>
  <ion-item class="error-message" *ngIf="!resetPwdForm.controls.email.valid  && resetPwdForm.controls.email.dirty">
    <p>Please enter a valid email.</p>
  </ion-item>
  <ion-grid>
    <ion-row>
      <ion-col style="text-align: center">
        <button ion-button center-all type="submit" color="danger" [disabled]="!resetPwdForm.valid">Reset Password</button>
      </ion-col>
    </ion-row>
  </ion-grid>
</form>

The same is to be done for the reset-password.ts file. The form builder looks like this;

this.resetPwdForm = formBuilder.group({
    email: ['', Validators.compose([Validators.required, EmailValidator.isValid])]
});

while the resetUserPwd() method looks like so;

resetUserPwd() {
 this.afAuth.auth.sendPasswordResetEmail(this.resetPwdForm.value.email).then((user) => {
   let alert = this.alertCtrl.create({
     message: "We just sent a link to reset your password to your email.",
     buttons: [{ text: "Ok", role: 'cancel',
       handler: () => {
         this.navCtrl.pop();
       }}]
   });
   alert.present();
 }, (error) => {
   let errorAlert = this.alertCtrl.create({
     message: error.message,
     buttons: [{ text: "Ok", role: 'cancel' }]
   });
   errorAlert.present();
 });
}

The handler code above pops the reset password page to show the login page once the request for the link is sent.

One last part (I’m so sorry! I’m tired too)…??

The logout button, the easiest and smallest code!

You need to put a button at the end of the header on the home page as shown below;

<ion-header>
	<ion-navbar>
		<ion-title>To-do List</ion-title>
		<ion-buttons end>
			<button ion-button (click)="logout()">Logout</button>
		</ion-buttons>
	</ion-navbar>
</ion-header>

The code to handle the logout in home.ts;

logout() {
    return this.afAuth.auth.signOut().then(authData => {
        this.app.getRootNav().setRoot(LoginPage);
    });
}

The code after the ‘then’ takes the user back to the login page.

And that’s it! Finally! ??

To allow the app to use these pages, you need to include them in the app.module.ts page, in both the declarations and entryComponents arrays, like so;

@NgModule({
    ...
    declarations: [
        ...
        LoginPage, 
        RegisterPage, 
        ResetPasswordPage
    ],
    ...
    entryComponents: [
        ...
        LoginPage, 
        RegisterPage, 
        ResetPasswordPage
    ]
})

Let’s have a look at all we have achieved so far.

And there you have it! ?? It’s not so easy on the eyes, but it is definitely functional.

As you can see, when a particular fields’ validation returns false, the input turns red, and the error message shows as well. The buttons stay disabled until all fields of the form are valid!

Below, the user object has also been stored in Firestore, with the current users’ uid as the key to the document. It all works!

Now that authentication and by extension user objects has been implemented, we now go on to syncing up the CRUD operations with Firebase Cloud Firestore.

Step 4 - Syncing CRUD actions with Cloud Firestore

The coding will be quite simple, as we have already integrated AngularFire into our app. The major changes will be made only to the back-end logic in the home.ts file, and one simple addition to the home.html to handle lists we get from Firestore.

The C in CRUD to Firestore

We’ll first start with adding functionality to the addTask() method. But first we need to import AngularFirestore to the home.ts and initialize it in the constructor, like so;

constructor(...public firestore: AngularFirestore) {}

As mentioned in the previous post, Firestore is not like its predecessor, it is not one big JSON structure. Instead, it works with something called documents. Each document is one uniquely JSON object that holds only one type of data, for example, the user object will only hold user data, such as their name, date of birth and other personal information, but not any other data.

Many documents of the same type make up a collection. And sometimes an object can have a collection of different objects inside it, and that’s what we are doing today; making a collection of task objects for each user.

If you can remember, in the previous post, we took the user’s uid, a unique ID that Firebase assigns all its users that sign up as the ID for the users’ JSON object. We will be requiring it heavily today as well, so the first thing to do is capture the uid from AngularFireAuth. As many methods will be using this value, it will be best to declare this variable outside the constructor, then initializing it inside ionViewDidLoad.

We put it in ionViewdidLoad(), because sometimes the user details from AngularFireAuth is not ready by the constructor. And since we will be accessing only that collection within the users' object, go ahead and grab that as well, similar to the register page. All this is added within the call to get the userId.

this.afAuth.authState.subscribe(user => {
    if (user) {
        this.userId = user.uid;
        this.fireStoreTaskList = this.firestore.doc<any>('users/' + this.userId).collection('tasks').valueChanges();
        this.fireStoreList = this.firestore.doc<any>('users/' + this.userId).collection('tasks');
    }
});

The reason why we have two lists is the fireStoreTaskList holds the list that we view, while the fireStoreList is the reference to the collection where we directly add the new tasks. The method valueChanges() returns an Observable List, which we can display in the view.

We can now use this reference anywhere in the page. Using it to add a task in the addTask method is very simple. There is a need to have a specific ID for each task, as we will require it when attempting to update the taskName, so we need to generate the ID and use the set() method of the firestore collection, to create a new task object, inside the if condition, replacing the previous code that pushes the task name into taskList.

let id = this.firestore.createId();
this.fireStoreList.doc(id).set({
    id: id,
    taskName: task
});

The R in CRUD in the App

Now to set up viewing the firestore list. The main part, getting the collection was done above. So the changes now need to be made to the home.html to view the fireStoreTaskList.

The first change is to be in the *ngFor, the name of the list. Since the list will be a response back by firebase, its asynchronous. The normal *ngFor, will cause errors. We need to add an async pipe as well, like so;

<ion-item *ngFor="let todo of fireStoreTaskList | async">

We no longer need to keep track of the index, as we will be using the task ID to either delete or update its value. And the second change is the value that we will view since todo will now be an object, we need to display todo.taskName, as that’s what we have named the task variable in the task object.

{{todo.taskName}}

And that’s it! Lets now have a look at both the app and Firestore, to see if it gets saved.

It’s got saved!

There’s nothing much to it for the C and R in CRUD. Now to update then delete.

The U in CRUD to Firestore

Luckily, AngularFirestore has its own update function, which, given the documents’ ID as well as the values to be updated, can be done in one single line. But first, a small change in the home.html file, to allow this to happen. As said earlier, you don’t need the index of the task in the list to update or delete, but instead the document ID, which we have simply stored in the variable id of a task object.

Our first order of business is to send the tasks’ id to the method from the button, like so;

<button ion-button clear (click)="updateTask(todo.id)">

Move over to home.ts and replace the code in the handler of the alert to;

this.fireStoreList.doc(index).update({ taskName: data.editTask });

We first create a reference to the specific object that the user wishes to update using the doc() method, then sending the relevant data we wish to update into the update() method.

Now to see this functionality in action!

This one works too!

Now onto the last change, delete.

The D in CRUD to Firestore

Deleting is just as easy (or easier, really) than updating.

You will again, need to pass the tasks’ ID onto the delete button;

<button ion-button clear (click)=”deleteTask(todo.id)”>

Again like for update, AngularFirestore has a function delete(), that is run on the reference of the document to be deleted, like so;

this.fireStoreList.doc(index).delete();

And now to watch the last functionality….

This one is functional too!

As you can see, the ‘Fold Clothes’ task with an ID of ‘NSskIVHEg4gKsT3U0xAV’ is no longer there, as it has been successfully deleted

There you have it! Firebase integrated into all the CRUD operations.

Step 5 - Bonus content styling

This is a short checklist of basic things that weren’t covered in the previous posts;

  1. Custom styles ?
  2. Images ?️
  3. Custom fonts ?

Prettify the UI

Going through my app, I was able to see a few things I wanted to change.

Remember those little messages below the input fields in the login, register and reset password pages?

I just realized that, since they are essentially <ion-item>, they have a line at the bottom. Not that great.

Thankfully, it’s a simple fix! There’s a global property called no-lines, that you need to add to the <ion-item> like so;

<ion-item ... no-lines>

So go ahead and add this to all the error message <ion-item> tags.

Your error message now looks like this.

Let’s move on to colors!

If you snooped around the project structure, you would have seen a folder called theme. The variables.scss file within has a color variable with 5 set colors. Keep the colors light and dark as they are, as well as danger, as we are using it for the reset password button and page. I will be only changing the primary and secondary color. I normally use coolors.co to find complementary colors for all the projects I’ve ever done.

Disclaimer: Do not add more than those 5 colors to the object, as this causes multiple copies of components to be made for each of these colors. It will eventually add unwanted bulk to the project, as not all components with all colors are used. If you need to use more colors, add a new variable to hold only that color literal.

The colors I will be using are;

$colors: (
	primary:    #32B596,
	secondary:  #fff350,
	danger:     #f53d3d,
	light:      #f4f4f4,
	dark:       #222
);

The first place to splash some color is the top navbar.

Looked so bland right??

Not any more.??

All you need to do is add the color primary to the ion-navbar tag, like so;

<ion-navbar color='primary'>

You can add the color property similarly to other components. Such as, give the delete icon the color stored in danger, or the add and logout button the color in secondary;

I still hate the way that the logout button looks… To make it a proper button, simply add the property solid to the tag, like so;

<button ion-button solid color='secondary' (click)="logout()">Logout</button>

Another cool UI design I saw previously, had icons before each input tag on the login, register and reset password pages, so I decided to give that a try as well! It’s a pretty simple code, that you need to add within the <ion-item> tag but before the <ion-input> tag, like so;

<ion-item>
	<div class="item-note" item-start>
		<ion-icon name="at" color='primary'></ion-icon>
	</div>
	<ion-input formControlName="email" ...></ion-input>
</ion-item>

There is no icon that screams password, so I decided to use ? just like in the UI design I had a look at; and ? for the users’ names

Adding images

A picture says a thousand words… But we have no need for such pictures…. ?No matter!

Adding pictures are not necessarily tough, but the path may get a bit confusing sometimes. You would assume that you need to add the actual path from the page to the image folder, which is ../../assets/imgs/imagename.png. The path you really need to add is the path from the app.html to the image in the image folder, and that path looks like assets/imgs/imagename.png.

Any and all images you wish to use needs to be added to the folder src/assets/imgs. You can then use the image as if this was HTML;

<img src="assets/imgs/imagename.png"/>

I want to add an image, kinda like a logo, to the login, register and reset password pages.

So that the image doesn’t exceed the page, we will also need to code some styling, and as this image will be in more than one page, we are required to write the styling in the app.scss page like so;

.imageTop {
    height: 200px;
    padding: 20px;
    margin: auto;
    display: block;
}

All you need to do now is simply add the class to the img tag, class='imageTop'.

Another image (or two) that you might want to change, is the splash page and app icon. You will first need to add either (or both) Android and iOS platforms, to use this feature. The command to add a platform is

ionic cordova platform add android

Or ios, if that’s your cup of ☕.

Ionic can easily generate different sized splash pages and icons according to different phones when you run the command ionic cordova resources in the terminal. You will need internet for this, as ionic uploads both images to be analyzed to generate the other splash pages and icons.

Before that you need to add both the images, named splash.png and icon.png to the resources folder. The sizes of both images should be 27322732 and 10241024 respectively, for the many splash pages and app icons to be generated.

That’s all for images!

Typography Rox(cks)!

First, find a font that speaks to you. The latest trends stick to sans serif fonts that are pretty easy to read. As pretty as many handwritten flowy fonts are, they are just a fail waiting to happen, like this one….

Or this one,

Unpack all the fonts to the folder assets/fonts.

All you need to do now is add the code below to the variables.scss found in the src/theme folder.

@font-face {
	font-family: 'Alegreya Sans Regular';
	src: url("../assets/fonts/AlegreyaSans-Regular.otf");
}
$font-family-base: 'Alegreya Sans Regular';
$font-family-ios-base: 'Alegreya Sans Regular';
$font-family-md-base: 'Alegreya Sans Regular';
$font-family-wp-base: 'Alegreya Sans Regular';

The @font-face imports your font and gives it a name, so it can be used throughout the application.

The variable $font-family-base assigns the default font.

The app now looks like this;

As you can only view the splash page and icon on a real device, I have brought in my trusty phone into the mix (Sadly it ain’t an Apple to fit with the rest of the gifs/pics).

And that’s it for this series!!!!!??

Find the repo for this post, here.

I hope you all had fun and learned a lot on this journey with me!

Thank you for the read, and see you soon!??