How to get the best performance out of your Angular apps

<strong>Angular is a great framework and can be used for developing large scale applications, but can be tricky to fine tune and achieve good load time and run-time performance. In this post, I’ll detail some best practices I have learned along the way, so you will not make the same mistakes I made.</strong>

Angular is a great framework and can be used for developing large scale applications, but can be tricky to fine tune and achieve good load time and run-time performance. In this post, I’ll detail some best practices I have learned along the way, so you will not make the same mistakes I made.

Change detection

Change detection is Angular’s mechanism to see if there are any values that have been changed and require the view to be updated. By default, Angular runs change detection with nearly every user interaction. In order to know if the view should be rendered again, Angular accesses the new updated value, compares it with the old one and makes the decision. As your application grows, it will include a lot of expressions, and having a change detection cycle on each one of them will cause a performance problem.

We can optimize things if we create a ‘dumb’ component with a certain attribute to handle the change detection cycles. This component relies only on non specific input data and in that way, we can tell Angular only to run change detection when an input changes or when we manually trigger it. When a reference type is immutable, every time the object is being updated, the reference on the stack memory will have to change. Now we can have a simple reference check on the object between the memory address and the stack. If the memory address has changed, then we check all the values. This will skip change detection in that component.

We need to keep in mind that primitive types such as numbers, booleans, strings, etc are passed by value. Objects, arrays and functions are also passed by value, but the value is a copy of a reference.

You can look for more details on that here.

And now we will see two examples of how this is implemented.

Example: ChangeDetectionStrategy.Default

You don’t have to specify changeDetection type, it will be ‘ChangeDetectionStrategy.Default’ by default.

@Component({
  selector: 'app',
  template: `
  <person-details [person]="person"></person-details>
  <button type="button" (click)="changeProperties()">
      Change Properties
  </button>
  <button type="button" (click)="changeObject()">
      Change Object
  </button>
  `
})

export class AppComponent {
person: Person = new Person('John', 'Lennon');

changeProperties(): void {
this.person.firstName = 'George';
this.person.lastName = 'Harrison';
}

changeObject(): void {
this.person = new Person('Paul', 'McCartney');
}
}
export class Person {
constructor(public firstName: string, public lastName: string) { }
}
@Component({
selector: 'person-details',
template: &lt;p&gt; &lt;label&gt;Person: &lt;/label&gt; &lt;span&gt;{{person.firstName}} {{person.lastName}}&lt;/span&gt; &lt;/p&gt;,
})

export class PersonDetailsComponent {
@Input() person: Person;
}

ChangeDetectionStrategy.OnPush

In order to use the OnPush change detection, we need to modify the child component from the first example.

import { Component, Input, ChangeDetectionStrategy} from '@angular/core';
import {Person} from '../models/person';
@Component({
selector: 'person-details',
templateUrl: './personDetails.component.html',
styleUrls: [ './personDetails.component.css' ],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PersonDetailsComponent {
@Input() person: Person;
}

Minimize DOM manipulations

If you have some list of data that was retrieved from some server and you need to show it, you are probably using the Angular directive, ngFor. Angular will create a new template for you for each item in that list.

If at some point some of the data has been changed, Angular can’t really know that and will replace the whole list, instead of just the items that were changed. In order to improve that, Angular provide us with the trackByfunction.trackBy takes a function which has two arguments: index and [item](https://angular.io/api/core/IterableChangeRecord#item). If trackBy is given, Angular tracks changes by the return value of the function.

Syntax:

<li *ngFor="let item of items; index as i; trackBy: trackByFn">...</li>

Most common use is just to return the index itself or item.id as a unique identifier for the item: trackByFn(index, item){ return item.id; }.

With that, Angular can track which items have been added or removed according to the unique identifier and create or destroy only the things that were changed.

Avoid using methods in your template

While it is very convenient to use methods in Angular templates, Angular will recalculate them on each change detection. For larger lists it will affect rendering time and the application may even get stuck due to huge memory consumption. In the following example, Angular will run getNumberOfCars on each change detection cycle (ie upon adding a new row).

How can we handle this situation? We can pre-compute the results and then just access the data we have computed. In our example, we can add a new attribute to the person object, vehiclesNumber, which holds in advance the amount of vehicles each person has. The other way to do this is by implementing the method getNumberOfCars as a pure pipe.

According to Angular pipe documentation:

Angular executes a pure pipe only when it detects a pure change to the input value. A pure change is either a change to a primitive input value (StringNumberBooleanSymbol) or a changed object reference (DateArrayFunctionObject).
Angular ignores changes within (composite) objects.
This may seem restrictive but it’s also fast. An object reference check is fast — much faster than a deep check for differences — so Angular can quickly determine if it can skip both the pipe execution and a view update.

The pipe will still be executed on each change detection cycle. However, if a pipe is executed more than once with the same parameters, the results of the first execution are returned. Meaning, Angular will cache the results for better performance.

Let’s see an example.

Without a pipe:

import { Component } from '@angular/core';
import { Person } from '../models/person';
@Component({
selector: 'my-app',
template: &lt;button (click)="addPerson()"&gt;add person&lt;/button&gt; &lt;ul&gt; &lt;li *ngFor="let person of personsList"&gt; &lt;span&gt; Name: {{person.firstName}} {{person.lastName}},&lt;/span&gt; &lt;span&gt; Number of cars: {{getNumberOfCars(person)}}&lt;/span&gt; &lt;/li&gt; &lt;/ul&gt;
})
export class AppComponent {
personsList: Person[] = [
{
firstName: 'John',
lastName: 'Lennon',
vehicles: ['Austin maxi', '1979 Mercedes-Benz 300TD Wagon', '1965 Rolls-Royce Phantom V', '1956 Bentley S1']
},
{
firstName: 'George',
lastName: 'Harisson',
vehicles: ['McLaren F1', 'Mini Cooper S', 'Ford Anglia']
}
];

addPerson(): void {
const person: Person = {
firstName: 'Paul',
lastName: 'McCartney',
vehicles: ['Radford Mini Cooper S', 'Lamborghini 400GT', 'Austin Healy']
}
this.personsList.push(person)
}
getNumberOfCars(person: Person): number {
console.log("calculating number of cars")
return person.vehicles.length;
}
}

While with a pipe we will get:

@Pipe({ name: 'carsCounter' })
export class CarsCounter implements PipeTransform {
transform(person: Person) {
console.log("transforming..")
return person.vehicles.length;
}
}
@Component({
selector: 'my-app',
template: &lt;button (click)="addPerson()"&gt;add person&lt;/button&gt; &lt;ul&gt; &lt;li *ngFor="let person of personsList"&gt; &lt;span&gt; Name: {{person.firstName}} {{person.lastName}},&lt;/span&gt; &lt;span&gt; Number of cars: {{person | carsCounter}}&lt;/span&gt; &lt;/li&gt; &lt;/ul&gt;
})

We can see it will recalculate only on the new data, instead of the whole list.

Use Prod flag in production

It will disable Angular’s development mode, which turns off assertions and other checks within the framework. This will also increase your performance. You can find more details here.

Don’t use console.log in production code

console.log prints can really slow down your application, as it takes some time to compute what you want to print. Also, for long information it will also consume some more time for the printing process.

Don’t forget to unsubscribe from your observables

Your subscription holds a reference to your component instance. If you will not unsubscribe from it, the instance will not be cleared by the garbage collector which will cause a memory leak. You can unsubscribe easily by using ngOnDestory(){this.subscription.unsubscribe();}. You can read more about it here.

Final Words

If you run into any issues, feel free to drop me a line at : markgrichanik[at]gmail[dot]com.

I would also love to hear any feedback/tips you have while working on large scale applications with Angular.

Originally published by Mark Grichanik at https://medium.freecodecamp.org

Learn more

☞ Angular 7 (formerly Angular 2) - The Complete Guide

☞ The Complete Angular Course: Beginner to Advanced

☞ NativeScript + Angular: Build Native iOS, Android & Web Apps

☞ Ionic 4 Crash Course with Heartstone API & Angular

☞ Angular 6 , Angular 7 Step by Step for beginners

☞ NgRx In Depth (Angular 7 and NgRx 7, with FREE E-Book)

☞ Learn Protractor(Angular Automation) from scratch +Framework

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!

How to Create a Machine Learning Mobile App with Angular

How to Create a Machine Learning Mobile App with Angular

It's easier than you might think to utilize machine learning in your next mobile app. See how you can quickly Creat a Machine Learning Mobile App with Angular

Machine learning is becoming increasingly prevalent in modern applications, and it’s easier than you think to incorporate it into your mobile app with minimal coding. To work with machine learning concepts we need data to be analyzed. We can also do funny things with this concept, like analyzing your smile to determine how funny you think something is. Wouldn’t it be cool if we could quickly build an app that made using basic machine learning concepts easy? To show you how simple it can be, let’s walk through what it takes to create an app using machine learning with the popular JavaScript framework, Angular, and the Progress Kinvey high productivity platform.

Our app will be called the Joke-O-Matic app, where you can check your “smile factor” just by capturing your picture with your phone, or selecting a picture you’ve already taken. The app will display a number of jokes and ask you to upload a picture of your reaction, and it will use machine learning to analyze your smile and tell you just how funny you thought the joke was.

Our app will also be able to find out how funny other users think the joke is, and then visualize our results on a graph, letting us know what the average “funniness” is for any given joke. I think this app will be gold for standup comedians everywhere. Let’s dive in.

Setting up the Kinvey Console

This application needs a high-performing backend to store the data we’re going to accumulate, and to help us quickly access our data when we need to show graphs or filter the data.

Kinvey Setup

Kinvey is fast and easy to use for the backend. Within the Kinvey console you can easily use Business Logic, Custom endpoints, Scheduled code or MIC (Mobile Identity Connect). You can also create a collection through Kinvey’s datastore or import other platforms’ data, and much more.

The first step is to login or sign up on the Kinvey console. If you’re new to Kinvey, you can sign up for the Developer edition, which is free forever.

Create a new app by clicking +Add an app button on top right corner of the screen. After clicking on the button you will see the dialog box just like the image below.

And then on the home screen of the Kinvey console create a collection by clicking on +Add a collection button. Once you’ve done this, you’re done with the Kinvey console for now. Your backend is now created, but you don’t have anything in your datastore yet. We’ll return to the console once you do.

Setting up the Frontend

Now let’s download NativeScript Sidekick, which will give us an excellent development experience to create this app.

Login to/Signup for Sidekick, and then start by clicking the “create an app” button. After that you will see this screen.

Sidekick has a wide array of choices for building an app. You can choose from a variety of Templates as well as multiple Project Types, like Angular & TypeScript, TypeScript or JavaScript. Choose whichever options you are most comfortable with.

For this application I have used Angular & Typescript and the Blank Template design.

Next, create your application and open it in a code editor.

Note that we want to use Machine Learning concepts like face detection and image labeling in our app. For that, we will need to use Firebase MLkit in our application.

Setting up Firebase

To get started with Firebase first you need to add a plugin to your app. We have an easy way to use a NativeScript plugin for Firebase.

To use this plugin, you’ll need to make sure you have a Firebase account first.

Go to “https://console.firebase.google.com/” and either register for an account or sign in if you have not done so already. Add a new project by clicking on the “Add project” button.

Once you’ve created your Firebase account, head back to NativeScript Sidekick. Click on the option to “Get started by adding Firebase to your app,” and select your platform: iOS, Android or both.

Now navigate through your application’s package.json file and copy your nativescript id, which should read: “org.nativescript.NAME_of_App”.

Here my package name is “org.nativescript.camac”.

Next, paste it on iOS bundle id under registering your app with Firebase.

As you complete registration of the app, Firebase will generate a config file named “GoogleService-Info.plist”.

Download the “GoogleService-Info.plist” file and put it in your project path at app/App_Resources/iOS/GoogleService-Info.plist.

Add this plugin by running the following command in your app terminal.

tns plugin add nativescript-plugin-firebase

This plugin will ask you specific questions, like which services you would like to install with it. You can say “No” to every feature except MLkit. MLKit is required to install Face Detection and Image Labeling in your app.

After that open your Kinvey console, and from the Dashboard find your App Key and App Secret.

Go to your app component file and initialize Kinvey by writing this snippet.

import { Kinvey , CacheStore } from 'kinvey-nativescript-sdk';
Kinvey.init({
    appKey: " kid_HJiukDq5X",
    appSecret: " 239cbe5644034b10b77b47469701a1b1",
});

We are now all set with configuration, so let’s get coding!

Here is the first page of the application.

This is the list of jokes. So, when a user likes a particular joke, he/she clicks on that joke.

For this list you can use a listview. And in the Array list, you can push each joke so we can display it.

this.jokes = [];
        for(let i=0; i<myjokes.length;i++){
            this.jokes.push(new joke(myjokes[i]));
        }

Note that here you have to be logged in all the time to use the backend with Kinvey.

To make staying logged in easier, you can add this sample to your home component or app component.

if(Kinvey.User.getActiveUser()){
           console.log("Active user");
           }
           else{
            Kinvey.User.login("admin","admin").then(()=>{
                console.log("Will be active");
                }).catch((e)=>{
                    alert(e);
                });
            }

To log in using the code snippet you have to create a user in the Kinvey console with both the user and password being “admin”.

Next, when users click on a specific joke it will redirect them to the next component, which is the heart of the app.

public onItemTap(args) {
            myGlobals.UrlComponent.urlArray = args.index;
            this._routerExtensions.navigate(['/home']);
            console.log("Item Tapped at cell index: " + args.index);
         }

Now you have moved the user to the ‘home’ component from the ‘list’ component. Here you’ll need to use some plugins again to make it work as it supposed to. These are necessary to natively access the camera and image gallery.

import * as Camera from "nativescript-camera";
import * as ImagePicker from "nativescript-imagepicker";

Next you’ll add some additional code, so you can capture a picture or select an image from your phone’s local storage. This way, when a user reads a joke and clicks on the joke, you can have the app capture or upload a picture of your face to calculate your smile percentage.

fromCameraPicture(): void {
        if (!isIOS) {
          Camera.requestPermissions();
        }
        Camera.takePicture({
          width: 800,
          height: 800,
          keepAspectRatio: true,
          saveToGallery: true,
          cameraFacing: "rear"
        }).then(imageAsset => {
          new ImageSource().fromAsset(imageAsset).then(imageSource => {
            this.pickedImage = imageSource;
            setTimeout(() => this.selectMLKitFeature(imageSource), 500);
          });
        });
    }
    fromCameraroll(): void {
        const imagePicker = ImagePicker.create({
          mode: "single"
        });
        imagePicker
            .authorize()
            .then(() => imagePicker.present())
            .then((selection: Array<ImageAsset>) => {
              if (selection.length === 0) return;
     
              const selected = selection[0];
              selected.options.height = 800;
              selected.options.width = 800;
              selected.options.keepAspectRatio = true;
              selected.getImageAsync((image: any, error: any) => {
                if (error) {
                  console.log(`Error getting image source from picker: ${error}`);
                  return;  }
                if (!image) {
                  alert({
                    title: `Invalid image`,
                    message: `Invalid`,
                    okButtonText: "ok."
                  });
                  return; }
                const imageSource = new ImageSource();
                imageSource.setNativeSource(image);
                this.zone.run(() => {
                  this.pickedImage = imageSource;
                });
                 
                setTimeout(() => this.selectMLKitFeature(imageSource), 500);
              });
            })
            .catch(e => {
              console.log(`Image Picker error: ${e}`);
            });
      }

You should be able to either capture or upload a picture now. Here is a screenshot showing what that screen will look like.


By clicking on Gallery, you can select any picture from your camera roll. Alternatively, by selecting the camera, the user can open their device’s camera. And it will display the percentage and visualize it with a progress bar, to show you how funny it is based on your smile.

You can change it too if you don’t like the current picture.

Now let’s get back to home page, where you can find the list of jokes. On the list view there is a segmented bar. You can navigate to the “most funny” segmented page. There will be a graph which shows a bar chart containing each joke and the average value of each users’ funniness rating. Here’s a screenshot of that page.

Whenever you tap on a particular bar, it will show up tool-tip with the text of that specific joke.

Setting up the Chart

NativeScript also has an amazing UI plugin for adding a variety of different charts, and it is so simple and easy to use. Start by running the following command:

tns plugin add nativescript-ui-chart

In order to create a chart, we need to format the data as the chart requires. And to format the data, we will have to first gather it and sync it with our Kinvey datastore. Let’s set up our datastore below.

Syncing your Data with a Kinvey Datastore

Before we sync data to the datastore we need to add two columns to the datastore: one for jokes number and another for percentage. That way you can store your data in the proper column.

We can store the joke number and its different values in the Kinvey backend.

const entity = {_id:null, jnum:myGlobals.UrlComponent.urlArray,percentage:this.confidence};
              const promise = this.dataStore.save(entity)
                .then((entity: {}) => {
                  // ...
                })
                .catch((error: Kinvey.BaseError) => {
                  // ...
                });
                const promise1 = this.dataStore.sync()
              .then(() => {
              })

By using this snippet, you should be able to store and sync data to your backend.

And we are passing jokes number index in ‘jnum’ and its confidence value in ‘percentage’ under entity.

Note that we should give id as ‘null’ because we want to store different values, not just one for each joke.

Here is the screenshot of the data.

This is the whole datastore in the Kinvey console.

To get the result and pass it to the graph we can use this code below.

const aggregation = Kinvey.Aggregation.average("percentage").by("jnum");
  return this.dataStore.group(aggregation).subscribe(
      d => {this.myjokes = d});

Using this code, our collection will be filtered by each joke’s average funniness value. And this json format data we can pass to the ‘myjokes.’

Finally, we can pass this to draw a chart.

And for the tool-tip of each bar we can use this HTML tag.

<Trackball tkCartesianTrackball snapMode="AllClosestPoints" showIntersectionPoints="true" textWrap="true" (trackBallContentRequested)="onTrackBallContentRequested($event)"></Trackball>

That’s how quickly we can build a mobile app that utilizes machine learning. Combining Kinvey with NativeScript we can build a powerful and incredibly fast application with relatively little code. Kinvey has great documentation here to help you get started with NativeScript, but you can also try a different platform as per your requirements.

I hope this tutorial helps you to build your own mobile apps with Kinvey, NativeScript, and Firebase MLKit. I have pushed whole project to GitHub just in case you would like to look at the source code. If you still have some questions, you can reach out to support or feel free to leave comments below.

This post was originally published here

Thanks For Visiting, Keep Visiting!

Building CRUD Mobile App using Ionic 4, Angular 8

Building CRUD Mobile App using Ionic 4, Angular 8

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

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

The Angular 8 just released a few weeks ago, but Ionic 4 still using Angular 7. As usual, we will start this tutorial using Ionic CLI with the new version 5.

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

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

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

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

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

sudo npm install -g ionic

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

ionic -v
5.1.0

To update the Angular CLI, type this command.

sudo npm install -g @angular/cli

Now, the Angular version should be like this.

ng version
Angular CLI: 7.3.9
Create Ionic 4 Application and Update to Angular 8

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

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

The created Ionic 4 application still using Angular 7.2.2, for that we have to upgrade the Angular 7 to Angular 8. Go to the newly created folder then type this command using Angular CLI.

ng update @angular/cli @angular/core

If you get dependency incompatibility like below.

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

Uninstall then install again the required dependency version.

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

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

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

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

Install Angular 8 Material and CDK

For UI, we will use Angular 8 Material and CDK. To install Angular 8 Material and CDK, simply run this command.

ng add @angular/material

Type enter or yes for every question that showed up.

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

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

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

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

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

Register the above modules to @NgModule imports.

imports: [
  BrowserModule,
  FormsModule,
  ReactiveFormsModule,
  IonicModule.forRoot(),
  AppRoutingModule,
  BrowserAnimationsModule,
  DragDropModule,
  ScrollingModule
],
Use Dynamic Imports for Angular 8 Route Configuration

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

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

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

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

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

{
  "compilerOptions": {
  …
  "module": "esnext",
  "moduleResolution": "node",
  …
  "target": "es2015",
  …
},
Create Ionic 4 Angular 8 RESTful API Service

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

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

Add it to @NgModule imports after BrowserModule.

imports: [
  BrowserModule,
  FormsModule,
  HttpClientModule,
  AppRoutingModule
  ...
],

We will use type specifier to get a typed result object. For that, create a new Typescript file src/app/product.ts then add these lines of Typescript codes.

export class Product {
  _id: number;
  prod_name: string;
  prod_desc: string;
  prod_price: number;
  updated_at: Date;
}

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

ionic g service api

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

import { Observable, of, throwError } from 'rxjs';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { catchError, tap, map } from 'rxjs/operators';
import { Product } from './product';

Add these constants before the @Injectable.

const httpOptions = {
  headers: new HttpHeaders({'Content-Type': 'application/json'})
};
const apiUrl = 'http://localhost:3000/api/v1/products';

Inject HttpClient module to the constructor.

constructor(private http: HttpClient) { }

Add the error handler function.

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

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

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

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

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

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

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

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

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

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

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

export class HomePage implements OnInit

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

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

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

products: Product[] = [];

Add function for getting Product list from API.

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

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

ngOnInit() {
  this.getProducts();
}

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

drop(event: CdkDragDrop) {
  moveItemInArray(this.products, event.previousIndex, event.currentIndex);
}

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

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

Register to @NgModule imports array.

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

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


  
    Home
  



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

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

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

.example-item {
  min-height: 50px;
}
View Data Details and Add Delete Function

Every time you click the list item in the List of data, you will be redirected to Details tab including the ID of the selected data. Open and edit src/app/product-detail/product-detail.page.ts then add/replace this imports.

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

Inject above modules to the constructor.

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

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

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

Add an asynchronous function to getting Product detail from API.

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

Add an asynchronous function for display an alert.

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

  await alert.present();
}

Call get product function from Angular 8 init function.

ngOnInit() {
  this.getProduct();
}

Add the functions to delete the data.

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

Add a function to navigate to the Edit Product page.

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

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


  
    
      
    
    Product Details
  



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

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

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

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

.example-loading-shade {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 56px;
  right: 0;
  background: rgba(0, 0, 0, 0.15);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mat-flat-button {
  margin: 5px;
}
Create a Form to Add Data using Angular 8 Material

To create a form for adding a Product Data using Angular 8 Material, open and edit src/app/product-add/product-add.page.ts then add these imports.

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

Inject above modules to the constructor.

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

Declare variables for the Form Group and all of the required fields inside the form before the constructor.

productForm: FormGroup;
prod_name = '';
prod_desc = '';
prod_price: number = null;
isLoadingResults = false;

Add initial validation for each field in the ngOnInit function.

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

Create a function for submitting or POST product form.

onFormSubmit() {
  this.isLoadingResults = true;
  this.api.addProduct(this.productForm.value)
    .subscribe((res: any) => {
        const id = res._id;
        this.isLoadingResults = false;
        this.router.navigate(['/product-details', id]);
      }, (err: any) => {
        console.log(err);
        this.isLoadingResults = false;
      });
}

Next, add this import for implementing ErrorStateMatcher.

import { ErrorStateMatcher } from '@angular/material/core';

Create a new class before the main class @Components.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Instantiate that MyErrorStateMatcher as a variable in the main class.

matcher = new MyErrorStateMatcher();

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

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

Declare that imported modules to then @NgModule imports array.

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

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


  
    
      
    
    Product Add
  



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

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

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

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child(0) {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}

.example-card {
  margin: 5px;
}
Create a Form to Edit Data using Angular 8 Material

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

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

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

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

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

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

Inject the constructor params with these modules.

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

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

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

Create a new Angular 8 function to call the product data by ID.

getProduct(id: any) {
  this.api.getProduct(id).subscribe((data: any) => {
    this._id = data._id;
    this.productForm.setValue({
      prod_name: data.prod_name,
      prod_desc: data.prod_desc,
      prod_price: data.prod_price
    });
  });
}

Create a new Angular 8 function that handles the form submission to save data to the REST API.

onFormSubmit() {
  this.isLoadingResults = true;
  this.api.updateProduct(this._id, this.productForm.value)
    .subscribe((res: any) => {
        const id = res._id;
        this.isLoadingResults = false;
        this.router.navigate(['/product-details', id]);
      }, (err: any) => {
        console.log(err);
        this.isLoadingResults = false;
      }
    );
}

Add a function to navigate to the Product Detail page.

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

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

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

Declare that imported modules to then @NgModule imports array.

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

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


  
    
      
    
    Product Edit
  



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

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

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

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child(0) {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}

.example-card {
  margin: 5px;
}
Run and Test the Whole Ionic 4 Angular 8 Mobile Apps

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

mongod
nodemon

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

ionic serve -l

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

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