by Ahmed Bouchefra June 7, 2020

In this tutorial, we’ll learn how to use Ionic 4 and Angular to build a cross platform hybrid mobile application.

Unlike native apps, a hybrid mobile application makes use of web technologies such as JavaScript, HTML and CSS (and their related technologies like TypeScript, Angular and Sass etc.) to build mobile applications. This works thanks to native containers like the popular Cordova tool and more recently Capacitor by the Ionic team. Both of these tools allow developers to call native SDKs on iOS, Android with one code base. The difference is that Capacitor is optimized for the Ionic framework and target desktop apps (using Electron) and Progressive Web Apps.

Ionic 4 is the latest version of the popular Ionic framework which is one of the most powerful and mature hybrid mobile frameworks. It’s tightly integrated with Angular, a popular platform for building client side web apps created by Google but starting with version 4, Ionic is framework agnostic. This means, developers can use it with plain JavaScript/TypeScript or other popular libraries like React or Vue.

Ionic 4 makes use of modern browser technologies such as:

  • Web components: A set of standards defined by the W3C which allow developers to build native components that can be used in modern web browsers.
  • Custom Elements: Used to extend HTML with new tags and components using JavaScript. It’s the base of web components.
  • Shadow DOM: Shadow DOM allows developers to independently encapsulate the DOM elements and CSS styles from the rest of elements in the HTML document.
  • CSS variables: They allow developers to use custom properties in CSS to store and reuse values such as colors and font widths similar to how we can use variables in programming languages.

Prerequisites

In order to follow this tutorial, you will need to have a few prerequisites such as:

  • A recent versions of Node.js and NPM (or Yarn) installed on your development machine. The straightforward way to install both of them is through the official website by simply downloading the binaries required for your system. You can also use nvm (Node Version Manager) which allows you to install and manage multiple versions of Node in your system without conflicts.
  • Java and the Android SDK installed on your system if you intend to build the final release for Android devices. You can get JDK from its official website and Android Studio from its official website. Android Studio is required to download and install the Android SDK.
  • For building iOS apps, you need to work under a macOS with the Xcode IDE. You can get Xcode from its official website or from the Apple Store.
  • You need to be familiar with web technologies like JavaScript, HTML and CSS.
  • Since we’ll be using Ionic with Angular, you also need to have some working experience of TypeScript and Angular.

If you have the above prerequisites, let’s get started!

Installing Ionic CLI

Ionic CLI is the official tool for creating and working with Ionic projects. Let’s install the latest version.

Open a new terminal and execute the following command:

npm install -g ionic  

Note: Please note that you may have to use sudo before your command in debian based systems or macOS since we are installing the package globally on the system. For Windows, you may need to run the command prompt or PowerShell as administrator. In case, you get any EACCESS errors, another alternative is to fix your npm permissions. See the docs for how you can resolve EACCES permissions errors when installing packages globally. If you install Node through NVM, this will be automatically resolved for you.

At the time of this writing ionic@4.12.0 is installed.

Creating your Ionic Project

After installing the CLI, you can proceed to create your Ionic project by invoking the ionic utility with the start command.

You can either specify the other options, such as the project name and the template and type of project, immediately after the command or after being prompted by the CLI.

Go back to your terminal, navigate to your working directory and run the following command:

cd ~
ionic start

You will be prompted for the name of your project — Enter ionic-todos-app, the base template — Choose sidemenu (which generates a base project with a side menu with navigation in the content area).

The CLI will create the necessary project structure and files and will start installing the dependencies from npm:

After that, the CLI will ask you if you Install the free Ionic Appflow SDK and connect your app? (Y/n). You can type n as we will not use any cloud services from Ionic in our example.

Ionic Appflow is a cloud service that allows you to continuously build, deploy and ship Ionic mobile apps.

Serving your Application

After creating your project, you can now serve the application locally and play with it using your web browser. You don’t need a native mobile device or an emulator unless you want to use native device features like the camera or SQLite database. In fact, you can even mock native plugins to provide a fake class that has the same API as the native wrapper which allows you to work entirely in the browser when developing your mobile application.

Go back to your terminal, navigate to your project’s folder and run the ionic serve command to start a live-reload development server:

cd ./ionic-todos-app
ionic serve

The server will be listening from the http://localhost:8100 address and your default web browser will be automatically opened and navigated to your app. If that’s not the case, just open your browser and go to that address to see your app up and running.

This is a screenshot of our app before doing any development:

Building and Theming the UI

At this point, we already have a home and list pages and a side menu. Let’s remove the list page because we don’t need it.

First, remove the list folder with its contents from the src/app folder. Also, open the src/app/app.component.ts file, locate the appPages array:

    export class AppComponent {
      public appPages = [
        {
          title: 'Home',
          url: '/home',
          icon: 'home'
        },
        {
          title: 'List',
          url: '/list',
          icon: 'list'
        }
      ];

Next, remove the entry that references the list page:

    export class AppComponent {
      public appPages = [
        {
          title: 'Home',
          url: '/home',
          icon: 'home'
        }
      ];

This will remove the link to the list page from the side menu:

You also need to remove the reference to the list page from the Angular router configuration. Open the src/app/app-routing.module.ts file, locate the routes array:

    const routes: Routes = [
      {
        path: '',
        redirectTo: 'home',
        pathMatch: 'full'
      },
      {
        path: 'home',
        loadChildren: './home/home.module#HomePageModule'
      },
      {
        path: 'list',
        loadChildren: './list/list.module#ListPageModule'
      }
    ];

Next, remove the route for the list page:

    const routes: Routes = [
      {
        path: '',
        redirectTo: 'home',
        pathMatch: 'full'
      },
      {
        path: 'home',
        loadChildren: './home/home.module#HomePageModule'
      }
    ];

Note: In an Ionic 4 project based on Angular, the official Angular Router is used to implement routing and navigation instead of the Ionic built-in routing mechanism.

Adding an About Page

Now, let’s generate an about page. Leave the development server running in the first terminal and open a new terminal, navigate to your project’s folder and use the ionic generate command to generate a page:

    cd ./ionic-todos-app
    ionic generate page about

This will generate an about folder in the src/app folder with the following files:

  • src/app/about/about.module.ts
  • src/app/about/about.page.scss
  • src/app/about/about.page.html
  • src/app/about/about.page.spec.ts
  • src/app/about/about.page.ts

And will also update the src/app/app-routing.module.ts file to include a route for the about page:

    const routes: Routes = [
      {
        path: '',
        redirectTo: 'home',
        pathMatch: 'full'
      },
      {
        path: 'home',
        loadChildren: './home/home.module#HomePageModule'
      },
      { path: 'about', loadChildren: './about/about.module#AboutPageModule' }
    ];

The [loadChildren](https://angular.io/api/router/Route#loadChildren) property of the Route interface is used to lazy load modules.

Ionic pages are implemented as Angular modules and they are lazy-loaded by the Angular Router.

Let’s now add an entry for the about page in the side menu. Open the src/app/app.component.ts file and add:

      public appPages = [
        {
          title: 'Home',
          url: '/home',
          icon: 'home'
        },
        {
          title: 'About',
          url: '/about',
          icon: 'information-circle'
        },
      ];

For the icon, we use an information-circle icon. You can see the list of available icons from the official website.

Next, open the src/app/about/about.page.html file and update it with the following content:

    <ion-header>
        <ion-toolbar color="primary">
          <ion-title>
            About
          </ion-title>
          <ion-buttons slot="start">
            <ion-back-button  defaultHref="home"></ion-back-button>
          </ion-buttons>
        </ion-toolbar>
      </ion-header>
    <ion-content color="primary" padding>
      <h2>
        Todo app v1.0
      </h2>
      <p>
       This is a simple todo app for managing 
       your todos    
      </p>
    </ion-content>

We used a ion-back-button component to create a back button that takes us to the previous page or the to the home route if the navigation stack is empty. we also added a primary color for the toolbar and the content area.

This is a screenshot of the about page at this point:

Building and Theming the Home Page

Open the src/app/home/home.page.html file and add the following code:

    <ion-header>
      <ion-toolbar color="primary">
        <ion-buttons slot="start">
          <ion-menu-button></ion-menu-button>
        </ion-buttons>
        <ion-buttons slot="end">
        <ion-button>
            <ion-icon name="add-circle" slot="start"></ion-icon>  
        </ion-button>
        </ion-buttons>
        <ion-title>
          Home
        </ion-title>
      </ion-toolbar>
    </ion-header>
    <ion-content color="primary" padding>
        <ion-list style="background: var(--ion-color-primary);">
            <ion-item color="primary" *ngFor="let todo of todos">
              <ion-icon [name]="isCompleted(todo)" slot="start">
              </ion-icon>
              {{todo.title}}
              <div slot="end">
                {{todo.note}}
              </div>
            </ion-item>
          </ion-list>
    </ion-content>

Next, open the src/app/home/home.page.ts file and update is accordingly:

    import { Component } from '@angular/core';
    @Component({
      selector: 'app-home',
      templateUrl: 'home.page.html',
      styleUrls: ['home.page.scss'],
    })
    export class HomePage {
      public todos: Array<{ title: string; note: string; completed: boolean}> = [];
      
      isCompleted(todo){
        if(todo.completed) return 'checkmark-circle';
        else return 'stopwatch';
      }
      constructor() {
        for (let i = 0; i < 9; i++) {
          this.todos.push({
            title: 'Todo ' + i,
            note: 'This is todo #' + i,
            completed: !!i
          });
        }
      }
    }

We add a todos array that holds our todos and we use a for loop to fill the array with nine fake todos.

We also define the isCompleted() method which returns either the checkmark-circle icon if the todo is completed or a stopwatch icon if the todo is not completed.

Each todo has a title, note and completed flag.

This is a screenshot of the home page:

Theming the Side Menu

Let’s now theme the side menu. Open the src/app.component.html file and update it as follows:

    <ion-app>
      <ion-split-pane>
        <ion-menu>
          <ion-header>
            <ion-toolbar color="primary">
              <ion-title>Menu</ion-title>
            </ion-toolbar>
          </ion-header>
          <ion-content color="primary">
            <ion-list style="background: var(--ion-color-primary);" color="primary">
              <ion-menu-toggle auto-hide="false" *ngFor="let p of appPages">
                <ion-item color="primary" [routerDirection]="'root'" [routerLink]="[p.url]">
                  <ion-icon slot="start" [name]="p.icon"></ion-icon>
                  <ion-label>
                    {{p.title}}
                  </ion-label>
                </ion-item>
              </ion-menu-toggle>
            </ion-list>
          </ion-content>
        </ion-menu>
        <ion-router-outlet main></ion-router-outlet>
      </ion-split-pane>
    </ion-app>

We simply add color=``"``primary``" to the <ion-toolbar>, <ion-content> and <ion-item> components. We also set the background of the <ion-list> component to var(--ion-color-primary). --ion-color-primary is a CSS variable that holds the value of the primary color.

This is a screenshot of our side menu:

Adding an Angular Service

Until now we just hardcoded a set of fake todos, let’s add the code for actually creating todos.

Go back to your terminal and run the following command:

    ionic generate service todos

Next, let’s install Ionic Storage which will be used to save todos in the local storage of the Ionic app:

  npm install --save @ionic/storage

As the time of this writing @ionic/storage@2.2.0 is installed.

Next, import the src/app/app.module.ts file and import IonicStorageModule:

    // [...]
    import { IonicStorageModule } from '@ionic/storage';
    
    @NgModule({
      declarations: [AppComponent],
      entryComponents: [],
      imports: [
        // [...]
        IonicStorageModule.forRoot(),
        
      ],
      // [...]
    })
    export class AppModule {}

Open the src/app/todos.service.ts file then import and inject the Storage service:

    import { Injectable } from '@angular/core';
    import { Storage } from '@ionic/storage';
    @Injectable({
      providedIn: 'root'
    })
    export class TodosService {
      constructor(private storage: Storage) { }
    }

Next, add the following interface:

    interface Todo{
      title: string; 
      note: string;
      completed: boolean;
    }

Next, define the following methods:


      public async generateNewKey(): Promise<string>{
        let key = `todo${ parseInt(`${Math.random() * 100}`)}`;
        let ret = await this.storage.get(key);
        
        while(ret){
          key = `todo${ parseInt(`${Math.random() * 100}`)}`;
          ret = await this.storage.get(key);
        }
        return key;
      }
      public async getTodos(): Promise<Todo[]>{
        
        let todos: Array<Todo> = [];
        await this.storage.forEach((v, key, i)=>{
          if(key.startsWith("todo")){
              todos.push(v);
          }
        });
        return todos;
      }
      public async createTodo(key: string , todo: Todo){
        console.log("Creating todo: ", todo);
        return await this.storage.set(key, todo);
      }

Now, let’s update the home page to use TodosService. Open the src/app/home/home.page.ts file and add the following imports:

    import {  OnInit } from '@angular/core';
    import { TodosService } from '../todos.service';

Next, inject TodosService via the constructor and implement the OnInit interface:

    export class HomePage implements OnInit {
    constructor(private todosService: TodosService) {}

Next, define the ngOnInit() hook and fetch the todos from the local storage:

      async ngOnInit(){
        this.todos = await this.todosService.getTodos();
      }

We store the fetched todos in the todos array.

Next, add the following method to create a random todo:

      public async createTodo(){
        let key = await this.todosService.generateNewKey();
        let todo = {
          title: `${key}`,
          note: "A new todo",
          completed: true
        };
        await this.todosService.createTodo(key,todo);
        this.todos = await this.todosService.getTodos();
      }

Now, we need to bind this method to the button for creating todos in the toolbar of the home page. Open the src/app/home/home.page.html file and add a click event with "``createTodo()``" as value :

        <ion-buttons slot="end">
        <ion-button (click)="createTodo()">
            <ion-icon name="add-circle" slot="start"></ion-icon>  
        </ion-button>
        </ion-buttons>

If you now click on the button, a new todo with a random title will be created.

In this simple example, we create random todos but in a real world situation you need to add a form to get the todo information from the user before calling the createTodo() method to persist it in the local storage.

You can find the source code of this project from this GitHub repository.

Conclusion

After developing your application (which can be done entirely in the browser unless you need to use a device feature that’s not available in the browser and can’t be mocked), you can either host your application in the web as a Progressive Web App or build your application to target native devices like Android or iOS.

In this tutorial, we’ve seen how you can get started with Ionic 4 to create cross platform mobile apps with web technologies such as TypeScript, SCSS and Angular.

Author: Ahmed Bouchefra

Ahmed Bouchefra

Ahmed Bouchefra is a web developer with 5+ years of experience and a technical author with an engineering degree in software development. You contact him via his personal website.