Ionic 4, Angular 7 and Cordova Crop and Upload Image

Ionic 4, Angular 7 and Cordova Crop and Upload Image

The comprehensive step by step tutorial on crop, and upload Image using Ionic 4, Angular 7 and Cordova

The comprehensive step by step tutorial on crop, and upload Image using Ionic 4, Angular 7 and Cordova. We will use Native Ionic Cordova Crop, File Transfer plugins and it’s dependencies. In this tutorial, we will use our existing uploader API that you can find on our GitHub which it uses Node.js, Express.js, and Multer. Or, you can use your own backend or API that using HTML Form Encoding multipart/form-data. The scenario is in the Ionic 4 App, click the Camera button inside the preview image then it will open image picker. After image picked then it will go to cropping popup that you can crop anyway you like then upload to the API. After the successful upload, the Ionic 4 app will preview the Image by URL that saved to the API server.

Table of Contents:

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

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

sudo npm install -g ionic

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

ionic --version
4.12.0

1. Create a New Ionic 4, Angular 7 and Cordova App

To create a new Ionic 4 App, type this command in your terminal.

ionic start ionic4-crop blank --type=angular

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

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

Next, go to the newly created app folder.

cd ./ionic4-crop

As usual, run the Ionic 4 App for the first time, but before run as lab mode, type this command to install @ionic/lab.

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

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

2. Install and Configure Image Crop, File Transfer Plugins, and Dependencies

We will install all required plugins for this tutorial. First, we have to install Native Cordova plugins and Ionic 4 Angular 7 Modules by running these commands.

ionic cordova plugin add cordova-plugin-crop
npm install @ionic-native/crop
ionic cordova plugin add cordova-plugin-camera
npm install @ionic-native/camera
ionic cordova plugin add cordova-plugin-file-transfer
npm install @ionic-native/file-transfer
ionic cordova plugin add cordova-plugin-file
npm install @ionic-native/file

Next, open and edit src/app/app.module.ts then add these imports.

import { ImagePicker } from '@ionic-native/image-picker/ngx';

Add that import to @NgModule Providers.

providers: [
  StatusBar,
  SplashScreen,
  { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
  ImagePicker
],

3. Implementing Image Crop and File Upload/Transfer

We will be using the existing Home component or page to implementing Image Preview, Picker, Crop and Upload. For that, open and edit src/app/home/home.page.html then replace all HTML tags with these.

<ion-header>
&nbsp; <ion-toolbar>
&nbsp; &nbsp; <ion-title>
&nbsp; &nbsp; &nbsp; Ionic 4 Crop Upload
&nbsp; &nbsp; </ion-title>
&nbsp; </ion-toolbar>
</ion-header>

<ion-content padding>
&nbsp; <ion-card>
&nbsp; &nbsp; <img *ngIf="!fileUrl" src="assets/no-image.jpeg"/>
&nbsp; &nbsp; <img *ngIf="fileUrl" src="{{fileUrl}}"/>
&nbsp; &nbsp; <ion-card-content>
&nbsp; &nbsp; &nbsp; <ion-button color="medium" size="large" (click)="cropUpload()">
&nbsp; &nbsp; &nbsp; &nbsp; <ion-icon slot="icon-only" name="camera"></ion-icon>
&nbsp; &nbsp; &nbsp; </ion-button>
&nbsp; &nbsp; </ion-card-content>
&nbsp; </ion-card>
</ion-content>

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

import { Crop } from '@ionic-native/crop/ngx';
import { ImagePicker } from '@ionic-native/image-picker/ngx';
import { FileTransfer, FileUploadOptions, FileTransferObject } from '@ionic-native/file-transfer/ngx';

Inject those imports to the constructor.

constructor(private imagePicker: ImagePicker,
&nbsp; private crop: Crop,
&nbsp; private transfer: FileTransfer) { }

Add the variables for hold image URL and response data.

fileUrl: any = null;
respData: any;

Create a function for crop and upload an image file to the API server.

cropUpload() {
&nbsp; this.imagePicker.getPictures({ maximumImagesCount: 1, outputType: 0 }).then((results) => {
&nbsp; &nbsp; for (let i = 0; i < results.length; i++) {
&nbsp; &nbsp; &nbsp; &nbsp; console.log('Image URI: ' + results[i]);
&nbsp; &nbsp; &nbsp; &nbsp; this.crop.crop(results[i], { quality: 100 })
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .then(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; newImage => {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log('new image path is: ' + newImage);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const fileTransfer: FileTransferObject = this.transfer.create();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const uploadOpts: FileUploadOptions = {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;fileKey: 'file',
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;fileName: newImage.substr(newImage.lastIndexOf('/') + 1)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; };

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fileTransfer.upload(newImage, 'http://192.168.0.7:3000/api/upload', uploadOpts)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;.then((data) => {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;console.log(data);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;this.respData = JSON.parse(data.response);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;console.log(this.respData);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;this.fileUrl = this.respData.fileUrl;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}, (err) => {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;console.log(err);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;});
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; error => console.error('Error cropping image', error)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; );
&nbsp; &nbsp; }
&nbsp; }, (err) => { console.log(err); });
}

As you can see, we use the IP address to access Express.js API from the device. The uploaded image file accessible from the device through [http://192.168.0.7:3000/images/filename](http://192.168.0.7:3000/images/filename "http://192.168.0.7:3000/images/filename") URL.

4. Run and Test Ionic 4, Angular 7 and Cordova App on iOS/Android Devices

We assume that you have cloned the Node.js, Express.js and Multer image uploader here https://github.com/didinj/node-express-image-uploader.git. Next, open a new Terminal or cmd-tab then go to the cloned Express image uploader.

npm install
nodemon

Next, to run on Android devices type this command while the device connected.

ionic cordova platform add android
ionic cordova run android

To run on iOS simulator or device, we have to build it first.

ionic cordova platform add ios
ionic cordova build ios

Then open and run the iOS app from the XCode. You will this view from your Android device or iOS simulator.

That it’s, the Ionic 4, Angular 7 and Cordova Crop and Upload Image tutorial. You can get the full source code from out GitHub.

How to Create Mobile Apps using Ionic 4, Angular 8 and Cordova?

How to Create Mobile Apps using Ionic 4, Angular 8 and Cordova?

In this Ionic 4 tutorial, we will show you how to create mobile apps quickly using Ionic 4, Angular 8, and Cordova

In this Ionic 4 tutorial, we will show you how to create mobile apps quickly using Ionic 4, Angular 8, and Cordova. In other words, this tutorial is the starting point to learn Ionic Framework, Angular, and Cordova. What you will get is not just create and running Ionic-Angular apps, but a little bit meaningful by displaying data in the Ionic list or grid, details, and Google maps. For more details about accessing data from REST API, Firebase, or Google Maps related, we have written the tutorials for you.

Table of Contents:

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

  1. Node.js (Choose the recommended version)
  2. Ionic 4 with the type of Angular
  3. Cordova
  4. Terminal or Node Command Line
  5. IDE or Text Editor (We are using Visual Studio Code)
Step #1. Preparation

The first step of this tutorial prepares the environment to create the Ionic 4 apps. We will use the Mac OS of the Windows environment (choose one of them) and download the Node.js (recommended version). Install the downloaded Node.js to your Mac or Windows machine then check the version of Node.js by typing this command in the terminal or command line.

node -v
v10.15.1
npm -v
6.11.3

Next, we will install the Ionic CLI which now is version 5 and Cordova. Type this command in the terminal or command line to install it.

sudo npm i -g ionic cordova

As you see at the end of the Ionic and Cordova installation, we have Ionic-CLI 5 and Cordova 9.

+ [email protected]
+ [email protected]

Now, we have a ready to use Ionic Frameworks and Cordova environment in our machine.

Step #2. Create an Ionic 4 App with the Type of Angular

We will create an Ionic 4 app with the type of Angular and default template using Tabs. To do that, type this command in the terminal or command line.

ionic start ionic-tutorial tabs --type=angular

We will add Cordova later before we test the Ionic-Angular apps to the Android/iOS devices. Next, go to the newly created Ionic-Angular application then run the Ionic-Angular apps for the first time.

cd ./ionic-tutorial
ionic serve -l

If there's a question to install the @ionic/lab just type "Y". Now, the Ionic-Angular apps will open the default browser automatically and here the apps looks like in Lab mode.

Step #3. Modify Angular 8 Routes

We need to pass ID parameters to Tab2 and Tab3. So, we will modify the default or generated src/app/tabs/tabs-routing.module.ts. Just change the Angular routes constant to be like this.

const routes: Routes = [
  {
    path: 'tabs',
    component: TabsPage,
    children: [
      {
        path: 'tab1',
        children: [
          {
            path: '',
            loadChildren: () =>
              import('../tab1/tab1.module').then(m => m.Tab1PageModule)
          }
        ]
      },
      {
        path: 'tab2',
        children: [
          {
            path: '',
            loadChildren: () =>
              import('../tab2/tab2.module').then(m => m.Tab2PageModule)
          }
        ]
      },
      {
        path: 'tab2/:id',
        children: [
          {
            path: '',
            loadChildren: () =>
              import('../tab2/tab2.module').then(m => m.Tab2PageModule)
          }
        ]
      },
      {
        path: 'tab3',
        children: [
          {
            path: '',
            loadChildren: () =>
              import('../tab3/tab3.module').then(m => m.Tab3PageModule)
          }
        ]
      },
      {
        path: 'tab3/:id',
        children: [
          {
            path: '',
            loadChildren: () =>
              import('../tab3/tab3.module').then(m => m.Tab3PageModule)
          }
        ]
      },
      {
        path: '',
        redirectTo: '/tabs/tab1',
        pathMatch: 'full'
      }
    ]
  },
  {
    path: '',
    redirectTo: '/tabs/tab1',
    pathMatch: 'full'
  }
];
Step #4. Display Data to the Ionic List/Grid

To display data as Ionic List or Grid, first, we have to create an array of objects. We will put this array of objects in a separate file. For that, create a new Typescript file src/app/player.ts then add these lines of the export constant array.

export const Player = [
    { id: 1, name: 'Robbie Fowler', team: 'Liverpool', photo: 'assets/imgs/photo1.jpeg', lat: 53.430855, lng: -2.960833 },
    { id: 2, name: 'Paul Ince', team: 'Manchester United', photo: 'assets/imgs/photo2.jpeg', lat: 53.463097, lng: -2.291351 },
    { id: 3, name: 'Eric Cantona', team: 'Manchester United', photo: 'assets/imgs/photo3.jpeg', lat: 53.463097, lng: -2.291351 },
    { id: 4, name: 'Thierry Henry', team: 'Arsenal', photo: 'assets/imgs/photo4.jpeg', lat: 51.554902, lng: -0.108449 },
    { id: 5, name: 'Alan Shearer', team: 'Newcastle United', photo: 'assets/imgs/photo5.jpeg', lat: 54.975593, lng: -1.621678 },
    { id: 6, name: 'Dennis Bergkamp', team: 'Arsenal', photo: 'assets/imgs/photo6.jpeg', lat: 51.554902, lng: -0.108449 },
    { id: 7, name: 'Didier Drogba', team: 'Chelsea', photo: 'assets/imgs/photo7.jpeg', lat: 51.481683, lng: -0.190956 },
    { id: 8, name: 'Jurgen Klinsmann', team: 'Tottenham Hotspur', photo: 'assets/imgs/photo8.jpeg', lat: 51.604320, lng: -0.066395 },
    { id: 9, name: 'Robin Van Persie', team: 'Arsenal', photo: 'assets/imgs/photo9.jpeg', lat: 51.554902, lng: -0.108449 },
    { id: 10, name: 'David Beckham', team: 'Manchester United', photo: 'assets/imgs/photo10.jpeg', lat: 53.463097, lng: -2.291351 },
    { id: 11, name: 'Steven Gerrard', team: 'Liverpool', photo: 'assets/imgs/photo11.jpeg', lat: 53.430855, lng: -2.960833 },
    { id: 12, name: 'Ian Rush', team: 'Liverpool', photo: 'assets/imgs/photo12.jpeg', lat: 53.430855, lng: -2.960833 },
];

Next, open and edit src/app/tab1/tab1.page.ts then add these imports of Player array and Angular Router.

import { Router } from '@angular/router';
import { Player } from '../player';

Inject the Angular Router in the constructor params.

constructor(public router: Router) {}

Add the variables before the constructor to handle an array of objects and view type (list/grid).

persons = Player;
viewType = 'list';

Add these functions after the constructor to switch the view of the list or grid.

changeToGrid() {
  this.viewType = 'grid';
}

changeToList() {
  this.viewType = 'list';
}

Next, open and edit src/app/tab1/tab1.page.html then add these lines of HTML tags that display the list/grid from the array of objects.

<ion-header>
  <ion-toolbar>
    <ion-title>
      Player
    </ion-title>
    <ion-buttons slot="end">
      <ion-button *ngIf="viewType === 'grid'" (click)="changeToList()">
        <ion-icon slot="icon-only" name="list-box"></ion-icon>
      </ion-button>
      <ion-button *ngIf="viewType === 'list'" (click)="changeToGrid()">
        <ion-icon slot="icon-only" name="grid"></ion-icon>
      </ion-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-list *ngIf="viewType === 'list'">
    <ion-list-header>
      Premier League Legend
    </ion-list-header>
    <ion-item *ngFor="let p of persons" [routerLink]="['/tabs/tab2/', p.id]">
      <ion-avatar slot="start">
        <img src="{{p.photo}}">
      </ion-avatar>
      <ion-label>
        <h2>{{p.name}}</h2>
        <h3>{{p.team}}</h3>
      </ion-label>
    </ion-item>
  </ion-list>

  <ion-grid *ngIf="viewType === 'grid'">
    <ion-row>
      <ion-col *ngFor="let p of persons" size-lg="3" size-md="4" size-sm="6" size="12">
        <ion-card>
          <img src="{{p.photo}}" />
          <ion-card-header>
            <ion-card-subtitle>{{p.name}}</ion-card-subtitle>
            <ion-card-title>{{p.team}}</ion-card-title>
          </ion-card-header>
        </ion-card>
      </ion-col>
    </ion-row>
  </ion-grid>
</ion-content>

As you can see, there are the buttons in the toolbar that show and hide based on the list/grid types. The click action will trigger the change of list/grid view types. So, there are 2 type of or which hide by "viewType" variable.

Don't forget to change the Tabs name in src/app/tabs/tabs.page.html to make tabs and its content is related.

<ion-tab-bar slot="bottom">
  <ion-tab-button tab="tab1">
    <ion-icon name="flash"></ion-icon>
    <ion-label>Player</ion-label>
  </ion-tab-button>

  <ion-tab-button tab="tab2">
    <ion-icon name="apps"></ion-icon>
    <ion-label>Details</ion-label>
  </ion-tab-button>

  <ion-tab-button tab="tab3">
    <ion-icon name="send"></ion-icon>
    <ion-label>Maps</ion-label>
  </ion-tab-button>
</ion-tab-bar>
Step #5. Display Data to the Ionic Card

We make two options to display details in Ionic Card in Tab 2. First, we will display a default static Ionic Card content if there's no data comes from other tabs. Second, we will display the Ionic Card of details that come from the other tabs. To do that, open and edit src/app/tab2/tab2.page.ts then add or modify these imports of Angular OnInit, ActivatedRoute, Router, and Player array.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Player } from '../player';

Add implementation of Angular OnInit to the class name.

export class Tab2Page implements OnInit {
...
}

Inject the Angular ActivatedRoute and Router to the constructor.

constructor(public route: ActivatedRoute, public router: Router) {}

Add the variables before the constructor that hold details object and Player array.

details = { id: null, name: '', team: '', photo: '', lat: null, lng: null };
persons = Player;

Add an Angular ngOnInit function that finds an object from the player array.

ngOnInit() {
  if (this.route.snapshot.paramMap.get('id') !== 'null') {
    const id = parseInt(this.route.snapshot.paramMap.get('id'), 0);
    this.details = this.persons.find(x => x.id === id);
  }
}

We use ngOnInit because want to load the details from the array every tab 2 showed up. Next, add a function to navigate to the Tab 3 or show the Google Maps.

showMap(id: any) {
  this.router.navigate(['/tabs/tab3/', id]);
}

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

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

<ion-content>
  <ion-card *ngIf="!details">
    <img src="assets/imgs/anfield.jpg" />
    <ion-card-header>
      <ion-card-subtitle>Anfield Stadium</ion-card-subtitle>
      <ion-card-title>Liverpool</ion-card-title>
    </ion-card-header>
    <ion-card-content>
      Anfield is a football stadium in Anfield, Liverpool, Merseyside, England, which has a seating capacity of 54,074, making it the seventh largest football stadium in England. It has been the home of Liverpool FC since their formation in 1892\. It was originally the home of Everton FC from 1884 to 1891, before they moved to Goodison Park after a dispute with the club president.
    </ion-card-content>
  </ion-card>

  <ion-card *ngIf="details">
    <img src="{{details.photo}}" />
    <ion-card-header>
      <ion-card-subtitle>{{details.name}}</ion-card-subtitle>
      <ion-card-title>{{details.team}}</ion-card-title>
    </ion-card-header>
    <ion-card-content>
      <ion-button (click)="showMap(details.id)">
        <ion-icon slot="icon-only" name="map"></ion-icon>
      </ion-button>
    </ion-card-content>
  </ion-card>
</ion-content>

As you see, there are two that display static details and details from objects based on details object's existence.

Step #6. Display Google Maps

In this step, we will show the Google Maps that come from the static latitude/longitude or dynamic latitude/longitude from the object. For that, open and edit src/app/tab3/tab3.page.ts then add or modify these imports of Ionic ViewChild, ElementRef, OnInit, Platform, ActivatedRoute, and Player object.

import { Component, ViewChild, ElementRef, OnInit } from '@angular/core';
import { Platform } from '@ionic/angular';
import { ActivatedRoute } from '@angular/router';
import { Player } from '../player';

Declare the Google Map and map variable before the @Component.

declare var google;
let map: any;

Add the implementation of Angular OnInit in the class name line.

export class Tab3Page implements OnInit {
...
}

Inject the ActivatedRoute and Platform to the constructor.

constructor(public route: ActivatedRoute, public platform: Platform) {}

Add the variables of @ViewChild, details object, and Player array before the constructor.

@ViewChild('map', {static: false}) mapElement: ElementRef;
details = { id: null, name: '', team: '', photo: '', lat: null, lng: null };
persons = Player;

Add the Angular ngOnInit function that loads the Google Maps inside the Platform ready method.

ngOnInit() {
  if (this.route.snapshot.paramMap.get('id') === null) {
    this.platform.ready().then(() => {
      map = new google.maps.Map(this.mapElement.nativeElement, {
        center: { lat: -6.930560, lng: 107.558439 },
        zoom: 15
      });
    });
  } else {
    const id = parseInt(this.route.snapshot.paramMap.get('id'), 0);
    this.details = this.persons.find(x => x.id === id);
    this.platform.ready().then(() => {
      map = new google.maps.Map(this.mapElement.nativeElement, {
        center: { lat: this.details.lat, lng: this.details.lng },
        zoom: 15
      });
    });
  }
}

The Google Maps coordinate based on data that come from the previous Tab, if the data is null then we put the static coordinate. Next, open and edit src/app/tab3/tab3.page.html then replace all HTML tags with these.

<ion-header>
  <ion-toolbar>
    <ion-title>
      Show Google Maps
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <div #map id="map"></div>
</ion-content>

As you see, there's just a

that holds the Google Maps. To make this Google Maps working, we need to import or load the Google Maps Javascript API library to the src/index.html before the end of the tag.

<body>
  <app-root></app-root>
  <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBgZZK8umUqJn8d5CoIZqWPJ_qtMfqD9q0"></script>
</body>

The Google Maps only working if the

have a height. So, add these CSS codes to the src/app/tab3/tab3.page.scss.

#map {
    height: 690px;
}
Step #7. Run and Test the Ionic 4/Angular 8 Apps

In this step, we will be working with the Cordova before run and test the Ionic 4/Angular 8 apps to the Android and iOS devices. First, remove and add all platforms from this Ionic app project by typing these commands.

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

Now, you can run the Ionic 4/Angular 8 apps to the Android and iOS devices. Type this command to run directly to the Android device and make sure the Android device connected to your computer.

ionic cordova run android

For iOS, we need to build this app first.

ionic cordova build ios

Open the platforms/ios/MyApp.xcworkspace in your XCode application then run it to iOS device or Simulator. The app will look like this.




That it's, the Ionic 4 Tutorial: How to Create Mobile Apps Quickly. As usual, we always provide full working source codes from our GitHub.

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!