Use Angular and NativeScript to Build a Web and Mobile Application

Use Angular and NativeScript to Build a Web and Mobile Application

Angular has been around for a few years now and since its release it has been useful when creating many different categories of applications, including web as well as mobile. The problem, at least for me, has always been that the experience for creating these various applications has been inconsistent and often confusing even though the driving technology has always been the same.

Angular has been around for a few years now and since its release it has been useful when creating many different categories of applications, including web as well as mobile. The problem, at least for me, has always been that the experience for creating these various applications has been inconsistent and often confusing even though the driving technology has always been the same.
Angular has been around for a few years now and since its release it has been useful when creating many different categories of applications, including web as well as mobile. The problem, at least for me, has always been that the experience for creating these various applications has been inconsistent and often confusing even though the driving technology has always been the same.

Things have gotten better now that custom schematics can be used with the official Angular CLI. So what does that mean for us? We can take a project created with the Angular CLI, add a schematic, let’s say for NativeScript, and end up with CLI compatible for both web and mobile.

We’re going to see how to use the Angular CLI to build a web and mobile compatible application with the NativeScript schematics.

Table of Contents

  • Install the Required Global NPM Dependencies
  • Create a New Angular CLI Project with NativeScript Support
  • Understanding the Schematic Changes and Process for Angular Development
  • Running an Angular CLI Project on Android or iOS with the NativeScript CLI
  • Conclusion
Install the Required Global NPM Dependencies

Before we can create and maintain NativeScript applications from the Angular CLI, we need to make sure we have the appropriate global NPM dependencies available on our computer. Assuming that you already have Node.js installed and configured, execute the following from your command line:

npm install -g @angular/[email protected]
npm install -g @nativescript/schematics
npm install -g nativescript

The above commands will install the Angular CLI, the NativeScript CLI, and the NativeScript schematics for the Angular CLI. You have to remember that the schematics are only for creating and maintaining projects. The NativeScript CLI is still required for building and deploying the mobile applications.

Take note of the version of the Angular CLI being used. As of right now, 06/29/2018, beta.2 and rc.0 have some bugs that are scheduled to be fixed in rc.1. Until then, make sure you’re using beta.0 instead.

We won’t get into it in this tutorial, but your computer must also be configured for Android and iOS development if you wish to build locally. If you wish to do a cloud build and skip all the Android and iOS setup, you can check out NativeScript Sidekick.

Create a New Angular CLI Project with NativeScript Support

With the proper dependencies in place, we can add NativeScript support to an Angular CLI project. While you can do it to an existing project, we’re going to start with a fresh project for simplicity. From the command line, execute the following:

ng new angular-project

The above command will create a new Angular project, which by default, will be for web applications, not native mobile applications. We can add mobile application support by using the following, very simple, command:

ng add @nativescript/schematics

Make sure you had navigated into your project before executing the command above. You have to be in a project that was created with the Angular CLI, so if you had created a NativeScript project with the NativeScript CLI, I don’t think schematics can be included.

In the scenario that you’re truly creating a fresh code-sharing project, you can create an Angular with NativeScript project using the following single command as an alternative:

ng new [email protected]/schematics --name=angular-project --shared

However, if you’re adding schematics to an existing project, the prior command should be used. More information on the schematics for NativeScript can be found in the official @nativescript/schematics repository.

Understanding the Schematic Changes and Process for Angular Development

When you add the NativeScript schematics to your Angular project, you’ll notice that it created quite a few files and even altered a few of the configuration files. Your original project should be in tact, so don’t worry about something destructive happening.

The most obvious thing that you’ll notice is that you have some .tns.ts and some .tns.html files. At first glance you’ll probably think that you now have to manage two different code-sets, which is not entirely true. NativeScript is a native mobile application framework which means you can’t use standard HTML markup for the UI. For this reason, yes you do have to maintain a web UI and a NativeScript UI. When it comes to the TypeScript, you could have a web version and a NativeScript version for everything, but you don’t need to. Where it makes sense is in the modules and routing areas since there are services like NativeScriptRoutingModule and RoutingModule which accomplish the same, but are specific to platform.

Have I confused you yet?

Free Node eBook

Build your first Node apps and learn server-side JavaScript.

  

📧

Get the eBook

Let’s modify our new project so that we can see the power of having so many platforms under “almost” a single CLI. Starting with the project’s src/app/app.routing.tns.ts file, clone it to src/app/app.routing.ts and make it look like the following:

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

const routes: Routes = [
    { path: '', redirectTo: '/players', pathMatch: 'full' },
];

@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
})
export class AppRoutingModule { }

If you haven’t noticed, the differences are in the naming of the modules being used. Since this file is for the web, we have removed the tns from the filename and used the vanilla Angular modules.

We’re not quite in the clear yet. We need to alter our project’s src/app/app.module.ts file to be more in line with the NativeScript version:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app.routing';

import { AppComponent } from './app.component';
import { BarcelonaModule } from './barcelona/barcelona.module';

@NgModule({
    declarations: [
        AppComponent,
        AboutComponent
    ],
    imports: [
        BrowserModule,
        AppRoutingModule,
        BarcelonaModule
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }

If you’re coming from an established Angular project, you’ll likely be playing catchup with the NativeScript version instead. In other words the reverse of what we’re doing.

The last step is to fix up the src/app/app.component.html file. As of now, June 2018, we have a default landing page with no routing in our Angular project. We need to finish with the routing setup. In the project’s src/app/app.component.html, replace everything with the following:

<router-outlet></router-outlet>

We’re only trying to match the behavior between the two applications. Since the application is fresh, the NativeScript version accomplished a lot more than the web version. The initial content further validated this.

At this point our project is done, but what if we wanted to use the Angular CLI going forward with our project? How about we create a new component with the following command:

ng g component about

The above command will use the CLI to create a component called about and as a result you’ll end up with the following:

src/app/about/about.component.css
src/app/about/about.component.html
src/app/about/about.component.tns.html
src/app/about/about.component.ts
src/app/about/about.component.spec.ts

Where I’m going with this is that you can use the Angular CLI like you would any other Angular application. You’ll end up with a .tns.html file that you can add your custom UI for mobile to. There are plenty of other commands beyond generating components, so don’t feel that you’re limited.

Running an Angular CLI Project on Android or iOS with the NativeScript CLI

With the project created, altered, and ready to go, we can worry about running our application. In its simplest form, we can test that the project still works as a web application. From the command line, execute the following:

ng serve

When the command completes, you can visit http://localhost:4200 in your web browser to see it in action. To run the application on a mobile device, you can similarly run the following:

tns run ios --bundle

The above command will run the application on iOS. You can easily switch the ios part for android if that is better for you. The most important part of the command is the --bundle. If you don’t bundle the application, it will crash with a lot of confusing errors.

Conclusion

You just saw how to use the Angular CLI with a NativeScript Android and iOS application. This was a long awaited and insanely requested feature by both the Angular and NativeScript community. Having separation of CLIs for development that used Angular was doing no one any favors.

It is important to remember that NativeScript projects build native mobile applications. For this reason you can recycle your logic, but separate UI components must exist. Even though it sounds like a bad thing, it is a good thing because you can give your mobile and web applications a separate, but very polished user experience. If you want to give NativeScript a quick try, check out the NativeScript Playground. You can build your first truly native mobile app deployed on your own device in 3 minutes.

This content is sponsored via Syndicate Ads.

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] ioni[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.