TL;DR: In this article, you will learn about what's new in Ionic 4, changes in the existing features, how to migrate your Ionic 3 app to Ionic 4 app, and about using Ionic with Stencil and Capacitor. Finally, you will create a simple application using Ionic 4 to see the new version of this framework in action. If you need, you can check the final code of the app that you will create on this GitHub repository.

"Learn about what's new in Ionic 4, changes in the existing features, how to migrate your Ionic 3 app to Ionic 4 app, and about using Ionic with Stencil and Capacitor."

Ionic 4 Beta: Learn What's New

The Ionic framework is quite a famous name in the mobile application development area. Ever since this framework came into existence, it's been tightly coupled to Angular. For example, Ionic 1 was built on Angularjs, and Ionic 2 and Ionic 3 are based on Angular2+.

Starting with Ionic 4, things are now different. Ionic components are now built using Web Components with the help of Stencil. What is interesting about Web Components is that you can use them with any framework or even without a framework. That makes it possible to use Ionic with Angular, React, Vue, or whatever other frameworks you can imagine.

Right now, the new (but still beta) version 4 of Ionic is available via the @ionic/core NPM package. Also, there are Angular bindings available via the @ionic/angular NPM package. Alongside with that, the official bindings for React and Vue are currently in development.

This article will mostly focus on @ionic/angular. Here, you will learn how to create new Ionic/Angular applications, make navigation, migrate Ionic3 app to Ionic4 app, and you will also create a demo application using Ionic/Angular.

Ionic 4 and Angular

In this section, you will read about the changes that Ionic 4 brought with respect to Angular. In other words, you will be learning about the changes in @ionic/angular@v4.

Routing in Ionic 4 and Angular

One of the biggest changes in Ionic 4 is the support of Angular Router. With Ionic 4, you can use @angular/router to handle the navigation of your Ionic apps. However, keep in mind that Ionic 4 still supports the Push/Pop ion-nav navigation system.

In a typical Angular application, you have to use router-outlet component to project the view. In Ionic 4 applications, you will use a version of router-outlet called as ion-router-outlet. Basically, you have to add following HTML element in the template where you want to project the view.

<ion-router-outlet></ion-router-outlet>

After adding the ion-router-outlet component, you will need to create a router config, which is required to configure the navigation paths of your Angular application. This can be done as follows:

let routes:Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home',loadChildren:'./pages/home/home.module#HomePageModule' },
  { path: 'user/:id', loadChildren: './pages/user/user.module#UserPageModule' },
  { path: '**', redirectTo: 'home', pathMatch: 'full'},
]

Angular Router parses the router config from top to bottom. The above config basically conveys the follows:

  • If users navigate to the / route, then your app will redirect them the /home route.
  • If users navigate to /home route, then your app will load the HomePageModule.
  • If users navigate to /user/:id route, then your app will load the UserPageModule. In this case, :id is a navigation parameter. For example: if users try to reach /users/u101, u101 is the value :id will get.
  • If users open any route which doesn't match any path in your config, your app will redirect them to /home.

Note: the loadChildren property in a route is used for Lazily Loading an Angular Module. By default, if you generate a page using Ionic CLI, it will be lazily loaded.

Then you have to provide these routes to the RouterModule in the root @NgModule of your Angular application, as follows:

// your imports here

@NgModule({
  ...
  imports: [
    ...
    RouterModule.forRoot(routes);
  ]
})

Now, when your Angular application bootstraps, it will load the default route specified in your Router configuration. But the rest of the navigation will depend upon users' interaction with the app per se. To trigger navigation, you two options: one is declarative, and the other one is imperative.

For example, to define a declarative navigaiton in your Angular application, you can use the routerLink directive:

<a routerLink="/home">Home</a>

With that, if your users click on the above anchor element, the app will trigger navigation to the /home route. If you come from previous versions of the Ionic framework, you will probably remember that you used to have nice animation flows that appeared when you were pushing a new view or page to your users. This is still possible by using routerDirection directive along with routerLink.

Basically, there are three possible values for this directive:

  • forward : It's just like pushing a view to the navigation stack.
  • backward: It's like popping a view from the navigation stack.
  • root: It behaves like setRoot method of NavController. This means that it will make the page that you are about to navigate to the root page of the application.

For example, to use the forward option you could have something like this:

<a routerLink="/home" routerDirection="forward">Home<a>

Now, when it comes to define imperative navigation in your app, you can call the navigateForward, navigateBack, and navigateRoot methods of NavController:

// similar to routerDirection="forward"
this.navCtrl.navigateForward('/user/u101');

// similar to routerDirection="backward"
this.navCtrl.navigateBack('/home');

// similar to routerDirection="root"
this.navCtrl.navigateRoot('/home');

Using Ionic and Angular CLI

With Ionic 4, Ionic CLI projects are almost similar to Angular CLI projects. As a matter of fact, Ionic CLI uses Angular CLI internally.

To use the latest Ionic CLI version, make sure you install it:

npm install -g ionic

Next step, if you were going to create a new app, you would issue commands with starter templates as below (don't really worry about running this now, you will soon create your app):

ionic start myApp blank --type=angular

Note: --type=angular tells Ionic CLI to generate an Angular-based project.

When you scaffold your app, you will get a file structure that looks like the following:

  • e2e: This is a directory that contains default end-to-end test code and where you can write your own tests.
  • resources: This is a directory that contains resources like icons and images for your mobile app.
  • src: This is where you will put the source code of your Ionic application.
  • angular.json: A file that contains all the configuration of your project. You can configure your production or dev environment and lots of the other things. This file makes it possible to use Angular CLI with Ionic projects.
  • config.xml: The config file for Cordova.
  • ionic.config.json: The config file used by Ionic CLI.
  • tsconfig.json: The config file that defines rules for the TypeScript compiler.
  • tslint.json: The config file that defines rules for the TypeScript linter.

Just like with Angular CLI, after scaffolding your app, you will be able to run a hello-world app by issuing a single command:

ionic serve

Theming in Ionic 4

With Ionic 4, the Ionic team is adopting web standards. In the new version of the framework, all of the Ionic Components are built using Stencil (which means that they are relying on the Web Component spec). With respect to theming, Ionic is using CSS variables.

By default, Ionic comes with nine colors, and each one of these has two extra variants called as shade and tint. You can easily edit the default color values along with their shade and tint variant.

Color Structure

Before you start modifying existing colors in Ionic, you need to understand some concepts. Each default color in Ionic has multiple properties, kind of like having multiple layers of colors. Each of these properties is applied to a different part of a particular component. These properties are as follows:

  • base: The main color that will be applied to the component.
  • shade: A bit darker than the base color and used to add shades.
  • tint: It's a bit lighter than the base color.
  • contrast: Contrast color is a color which is easily visible (perceptible) on the base color. For example, if the base is black, then contrast would be white. Basically, contrast is used for showing text on components like button, list, etc.

Following is an excerpt of the default primary color values from an Ionic project:

:root {
  --ion-color-primary: #488aff;
  --ion-color-primary-rgb: 72,138,255;
  --ion-color-primary-contrast: #fff;
  --ion-color-primary-contrast-rgb: 255,255,255;
  --ion-color-primary-shade: #3f79e0;
  --ion-color-primary-tint: #5a96ff;
}

Modifying Existing Color

When you open the /src/theme/variable.scss file, you can see all nine default colors in that file. You can modify any of the values as shown below:

:root {
  --ion-color-primary: #00ff00;
  --ion-color-primary-shade: #00fff0;
  --ion-color-primary-tint: #00fa00;
}

Above example changes the primary color (along with it's shade and tint properties) to #00ff00 for the whole application. You can also change this particular color for a particular component as follows:

.my-button {
  --ion-color-primary: #00ff00;
}

Migrating from Ionic 3 to Ionic 4

Migration is always a difficult and painful subject. However, the team behind the Ionic framework worked hard to make things as easy as possible. The following list presents some tasks that you will have to do to migrate from Ionic 3 to Ionic 4:

  • Angular Router: As mentioned earlier, you need to migrate your Push/Pop navigation to Angular Router navigation. You can follow the step mentioned above to do so.
  • NavParams Service: With Angular Router, you can't use the beloved NavParams service, but there are many alternatives to it. One of the best would be using NgRx to manage the state. It's fairly easy to use NgRx to mimic the same behavior of the NavParams service. However, there is a file inside Ionic 4's GitHub repo which might be hinting at NavParams debut in Ionic 4.
  • LifeCycleEvents: Since you will not use the ion-nav navigation element, you can't use ionViewWillLoad or similar life cycle events. However, you can use Angular's life cycle hooks in their place. To learn more about Angular lifecycle hooks, take a look here.
  • Markup: There are lots of changes in markup due to the fact that Ionic uses the Web component spec. It's out of scope of this blog post to talk about all those changes. Nevertheless, it is important to notice that the Ionic team introduced a migration linter for markup that you can use.

Using the Migration Linter

It's definitely fun to learn things and sometimes you need to change things manually. But if at a certain point you can do things automatically, you should opt for it. As mentioned, the Ionic team also introduced a migration linter tool that you can use to migrate your Ionic 3 markup to Ionic 4 markup.

To use it, you will have to install the lint rules:

npm i -D @ionic/v4-migration-tslint

Then you will need to create a file called ionic-migration.json at the root of your project and paste in the following JSON:

{
  "rulesDirectory": ["@ionic/v4-migration-tslint/rules"],
  "rules": {
    "ion-action-sheet-method-create-parameters-renamed": true,
    "ion-alert-method-create-parameters-renamed": true,
    "ion-datetime-capitalization-changed": true,
    "ion-item-option-method-get-sliding-percent-renamed": true,
    "ion-overlay-method-create-should-use-await": true,
    "ion-overlay-method-present-should-use-await": true,
    "ion-back-button-not-added-by-default": { "options": [true], "severity": "warning" },
    "ion-button-attributes-renamed": true,
    "ion-button-is-now-an-element": true,
    "ion-chip-markup-has-changed": true,
    "ion-fab-button-is-now-an-element": true,
    "ion-fab-attributes-renamed": true,
    "ion-fab-fixed-content": true,
    "ion-col-attributes-renamed": true,
    "ion-icon-attribute-is-active-removed": true,
    "ion-item-is-now-an-element": true,
    "ion-item-ion-label-required": true,
    "ion-item-attributes-renamed": true,
    "ion-item-divider-ion-label-required": true,
    "ion-item-options-attribute-values-renamed": true,
    "ion-item-option-is-now-an-element": true,
    "ion-label-attributes-renamed": true,
    "ion-list-header-ion-label-required": true,
    "ion-menu-toggle-is-now-an-element": true,
    "ion-navbar-is-now-ion-toolbar": true,
    "ion-option-is-now-ion-select-option": true,
    "ion-radio-attributes-renamed": true,
    "ion-radio-slot-required": true,
    "ion-radio-group-is-now-an-element": true,
    "ion-range-attributes-renamed": true,
    "ion-spinner-attribute-values-renamed": true,
    "ion-tab-attributes-renamed": true,
    "ion-text-is-now-an-element": true,
    "ion-buttons-attributes-renamed": true
  }
}

Then you can lint your project by running the following command:

npx tslint -c ionic-migration.json -p tsconfig.json

"Ionic 4 will have tools to help you migrate your code automatically."

Note : npx is a new utility that comes with NPM v5.2.0. This tool allows you to run local Node executables from the node_modules directory.

Ionic 4 and Stencil

Before you start digging into this section, you should learn a few things about Stencil. Basically speaking, Stencil is a compiler that lets you create UI Components and Web Apps using the Web Component specification. Coding with Stencil is kind of like a mixture of Angular and React. While developing with Stencil, you will use Decorators, JSX, and TypeScript to name a few.

A typical Stencil Component looks like follows:

import { Component, Prop } from '@stencil/core';

@Component({
  tag: 'name-component',
  styleUrl: 'name-component.scss'
})
export class NameComponent {
  @Prop() name: string;

  render() {
    return (
      <p>
        Welcome, {this.name}
      </p>
    );
  }
}

Then, to use your new web component, you can just add it to your HTML like any other element:

<name-componet name="Indermohan Singh"></name-component>

Using Ionic with Stencil

You might be wondering, why would I want to read about Stencil in an Ionic article? In other words, how would you benefit from learning about Ionic and Stencil?

The best answer to that question is that Ionic Core is built using the Stencil compiler. That is, they play really nicely with each other. Moreover, Stencil has enough plug-ins that you can use to write a complete application. Also, Stencil comes with an Ionic PWA (Progressive Web App) starter that can be used to create Ionic applications.

As you will see here, it's fairly easy to get started with Ionic and Stencil. To do so, you will need to run the following command to trigger Stencil CLI:

npm init stencil

This command will ask you questions like:

  1. Pick a starter: First, it will ask you to pick a starter. There is three starters for now: ionic-pwa, which is Ionic with the PWA toolkit; app, a minimal starter to build stencil app or website; and component, a collection of web components to test the PWA capabilities, select the ionic-pwa option.
  2. Project name: For this, you can add any name that you can come up with.
  3. Confirm?: Finally, it will ask you to confirm. Press y to confirm.

Now you can run the application as follows:

npm run start

To build the application, you can run the following command:

npm run build

The above command will generate a www directory with all the files required to deploy or build a mobile application using Capacitor or Cordova.

Using Stencil Plugins with Ionic 4

With Ionic, there are some plug-ins available to do certain things, for example:

  • @stencil/router: This is a router plugin for stencil applications that can be used to manage routing in your apps.
  • @stencil/redux: If you like managing the state of your apps with Redux style, you can do that via this package.

Furthermore, you can use any JavaScript library with Stencil applications, because Stencil components transpile into plain JavaScript web components.

Building Mobile Apps with Capacitor on Ionic 4

Capacitor is the latest kid on the block when it comes to hybrid app development. What is impressive about Capacitor is that you can literally drop it inside any JavaScript project.

Capacitor is a new tool doing the same thing (like Cordova for example) with a different API for creating plugins. But in the end, both things are running your application inside a web view (i.e., an embedded browser instead of a real native app).

Capacitor supports Cordova Plugin. So it's easy to migrate your Cordova application to Capacitor application. To add Capacitor into your front-end application, you can simply go to some app that you have and run the following command:

npm install --save @capacitor/core @capacitor/cli

After installing it, you will have to initialize Capacitor by running this:

npx cap init

Then, you need to add the platform for which you want to build the application. For example, to configure your project to run on Android, you can issue the following command:

npx cap add android

Finally, you need to open the project and run or build it via platform's provide IDE. Run the following command to open Android Studio:

npx cap open android

After that, you can use respective platform features to build or run the application.

Note: It's also worth mentioning that you can combine Ionic, Stencil, and Capacitor to create a mobile application. Just follow the above instructions inside your Stencil Ionic PWA app, and you are good to go.

Ionic 4 In Action

In this section, you are going to create a very simple IonicaApplication with a couple of pages. What will be interesting about this app is that some of the content will be publicly available (that is, no authenticated is required) and some are for authenticated users only.

The application that you will build is similar to a contact directory. The first page will have a list of people with their names and avatar picture. Then, when users click on contact, the app will show the details of the contact clicked.

"Start building Ionic 4 apps right now!"

Creating an Auth0 Account

Before you move on and start developing the application, you will need to sign up to Auth0. The idea of using Auth0 is to leverage some state-of-the-art functionality that will give you enough power (and confidence) to focus on what matters the most, your app details. As such, the first thing you will do is to sign up for a free Auth0 account here.

After signing up to your Auth0 account, you will need to create an Auth0 Application to represent your new app. So, move to the Applications page in your Auth0 dashboard and click on Create Application. After clicking on it, Auth0 will show a dialog where you will have to type the name of your application (e.g., "Ionic 4 In Action") and you will have to select the type of application (in this case, you will have to choose Native App). Then, you can press the Create button.

Clicking on this button will redirect you to the configuration details of your app. In this page, you will have to add the following values to the Allowed Web Origins box:

http://localhost:8100

This makes Auth0 accept requests from this URLs. Alongside with that, you will also need to configure the callback URL for the application:

http://localhost:8100/callback

This is the URL that Auth0 will call after users authenticate.

Scaffolding the Ionic Application

First, you need to have the latest Ionic CLI installed, as mentioned earlier. If you haven't done so yet, run the following command:

npm install -g ionic

Now, run the following command to create a new blank project and to move into the project folder:

# scaffold your new app
ionic start ionic-4-demo blank --type=angular

# move into it
cd ionic-4-demo

The first command will ask you a couple of questions:

  • Integrate your new app with Cordova to target native iOS and Android? (y/N): Press Y
  • Install the free Ionic Pro SDK and connect your app? (Y/n): Press N

Inside your new project, issue the following command to install the auth0-js library:

npm install --save auth0-js

Also install a CSS library to show some country flags, as follows:

npm install --save flag-icon-css

After that, run the default app using Ionic CLI:

ionic serve

CLI will spin-up the web server and open this app in your default browser. If you take a closer look, you will see that Ionic CLI under the hood is using Angular CLI to run this app.

Initializing an Ionic 4 app

Creating a Service for your Data

Now that you have scaffolded your Ionic app, you will create a service called DataService to fetch dummy data to your app. To create this service, run the following command from the project root:

ionic g service services/data/data

This will generate a couple of files in the ./src/app/services/data directory. Now, open the data.service.ts file and replace its content with the following:

import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {of as observableOf} from 'rxjs';
import {map, tap} from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  people: any = [];

  constructor(private http: HttpClient) {
  }

  getPeopleList() {
    if (this.people.length) {
      return observableOf(this.people);
    } else {

      return this.http.get('https://randomuser.me/api/?results=20&?seed=foobar')
        .pipe(
          map((data: any) => data.results),
          tap(people => this.people = people),
        );
    }
  }

  getPerson(index) {
    if (!this.people[index]) {
      return this.getPeopleList()
        .pipe(
          map(people => this.people[index])
        );
    } else {
      return observableOf(this.people[index]);
    }
  }
}

As you can see, this service is using an online API called Random User Generator to load a list of dummy contacts to your app. This service contains two methods:

  1. getPeopleList: The method that loads the dummy data and returns a list of 20 random contacts.
  2. getPerson: A method that returns the details of a particular contact.

After creating this service, you will have to create another one. This other service will be responsible for handling your users' sessions and, as such, you will call it AuthService. To create this service, issue the following command:

ionic g service services/auth/auth

Again, this command will generate a couple of files in the /src/app/services/auth directory. Now, open the auth.service.ts file and replace its code with the following:

import {Injectable} from '@angular/core';
import {Router} from '@angular/router';
import {Subject} from 'rxjs';
import * as auth0 from 'auth0-js';

(window as any).global = window;

@Injectable()
export class AuthService {
  isLoggedIn$ = new Subject();
  isLoggedIn: Boolean = false;
  auth0 = new auth0.WebAuth({
    clientID: 'YOUR_CLIENT_ID',
    domain: 'YOUR_AUTH_DOMAIN',
    responseType: 'token id_token',
    audience: 'https://YOUR_AUTH_DOMAIN/userinfo',
    redirectUri: 'http://localhost:8100/callback',
    scope: 'openid'
  });

  constructor(public router: Router) {
    // Check if user is logged In when Initializing
    const loggedIn = this.isLoggedIn = this.isAuthenticated();
    this.isLoggedIn$.next(loggedIn);
  }

  public login(): void {
    this.auth0.authorize();
  }

  public handleAuthentication(): void {
    this.auth0.parseHash((err, authResult) => {
      if (authResult && authResult.accessToken && authResult.idToken) {
        window.location.hash = '';
        this.setSession(authResult);
        const loggedIn = this.isLoggedIn = true;
        this.isLoggedIn$.next(loggedIn);
        this.router.navigate(['/home']);
      } else if (err) {
        const loggedIn = this.isLoggedIn = false;
        this.isLoggedIn$.next(loggedIn);
        this.router.navigate(['/home']);
      }
      console.log(this.isLoggedIn);
    });
  }

  private setSession(authResult): void {
    // Set the time that the Access Token will expire at
    const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());
    localStorage.setItem('access_token', authResult.accessToken);
    localStorage.setItem('id_token', authResult.idToken);
    localStorage.setItem('expires_at', expiresAt);
  }

  public logout(): void {
    // Remove tokens and expiry time from localStorage
    localStorage.removeItem('access_token');
    localStorage.removeItem('id_token');
    localStorage.removeItem('expires_at');
    // Go back to the home route
    const loggedIn = this.isLoggedIn = false;
    this.isLoggedIn$.next(loggedIn);
  }

  public isAuthenticated(): boolean {
    // Check whether the current time is past the
    // Access Token's expiry time
    const expiresAt = JSON.parse(localStorage.getItem('expires_at') || '{}');
    return new Date().getTime() < expiresAt;
  }
}

Note that, before running your app again, you will have to replace YOUR_CLIENT_ID and both places that show YOUR_AUTH_DOMAIN with your own Auth0 values. The first value, you will have to replace with the Client ID property of the Auth0 Application you created before. The second value, YOUR_AUTH_DOMAIN, you will have to replace with the Domain value of the same Auth0 Application.

Besides configuring your app to communicate with Auth0, this service also contains the following methods:

  • login: This method initiates the login process and shows an Auth0's hosted login page.
  • handleAuthentication: This method is called when the authentication process is finished. If the user is successfully logged in, this method calls this.setSession so it sets this.isloggedIn to true and redirect users to the /home route. If the authentication is not successful, it sets this.isloggedIn to false but also redirects to the /home route. You will have to call this method inside the root component of your application so that it can read the URL hash using Auth0 library to determine the authentication status.
  • setSession: This method stores user's login information like access_token, id_token, and expires_at into local storage to facilitate the management of these data.
  • logout: This method removes all information about logged in user from local storage and sets this.loggedin value to false.
  • isAuthenticated: This method checks if the user is authenticated or not by checking the token expiration date from local storage.

Creating Pipes in Ionic 4

To see pipes in action, you are going to create a very simple pipe to capitalize the first character of words. As such, the first thing you will need to do is to create the pipe:

# from the project root
ionic g pipe pipes/capitalize

The above command will generate a file named capitalize.pipe.ts inside the ./src/app/pipes/ directory. Open this file and replace its content with this:

import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
  name: 'capitalize'
})
export class CapitalizePipe implements PipeTransform {
  transform(value: any, args?: any): any {
    return this.capitalize(value);
  }

  capitalize(string) {
    return string.charAt(0).toUpperCase() + string.slice(1);
  }
}

The capitalize method takes a string as an input and returns another string by capitalizing the first character of that string. When you use capitalize pipe inside Angular Template, it will call the transform method, which will further the call to this capitalize method from this pipe.

As you will need to use pipes in multiple sub-modules inside your application, it's a good idea to create an extra NgModule for all the pipes and then import those modules in your other modules. To do so, create a file named pipes.module.ts inside ./src/app/pipes/ directory and add the following code to it:

import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {CapitalizePipe} from './capitalize.pipe';

@NgModule({
  imports: [
    CommonModule,
  ],
  declarations: [CapitalizePipe],
  exports: [CapitalizePipe]
})
export class AppPipesModule { }

Creating Pages in Ionic 4

By Default, the blank ionic project has only one page called HomePage. To avoid creating a new one and removing this one, you will use that page to show the list of people.

So, replace the content of the ./src/app/home/home.page.ts with the following:

import { Component, OnInit } from '@angular/core';
import { DataService } from '../services/data/data.service';
import { NavController } from '@ionic/angular';
import { AuthService } from '../services/auth/auth.service';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {
  people: Array<any> = [];
  constructor(private dataService: DataService, private navCtrl: NavController, private authService: AuthService) {
  }

  ngOnInit() {
    this.dataService.getPeopleList()
    .subscribe((people: any) => {
      this.people = people;
    });
  }

  openPerson(index) {
    this.navCtrl.navigateForward(`/person/${index}`);
  }
}

Then, replace the content of the ./src/app/home/home.page.html file with the following:

<ion-header>
  <ion-toolbar color="primary">
    <ion-title>
      People
    </ion-title>
    <ion-buttons slot="end">
      <ion-button shape="round" fill="outline" color="light" (click)="authService.login()" *ngIf="!authService.isLoggedIn">Login</ion-button>
      <ion-button shape="round" fill="outline" color="light" (click)="authService.logout()" *ngIf="authService.isLoggedIn">Logout</ion-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-list>
      <ion-item *ngFor="let person of people; let i = index" (click)="openPerson(i)">
          <ion-avatar slot="start">
            <img [src]="person.picture.thumbnail" />
          </ion-avatar>
          <ion-label>
            <h2>{{ person.name?.title | capitalize }} {{ person.name?.first | capitalize }} {{person.name?.last | capitalize }}</h2>
          </ion-label>
        </ion-item>  
    </ion-list>  
</ion-content>

Since each page in Ionic is lazily loaded by default, each page comes with its own NgModule. As such, you need to import the AppPipesModule you just created in your ./src/app/home/home.module.ts file, as shown below:

// ... other imports ...
import {AppPipesModule} from '../pipes/pipes.module';

@NgModule({
  imports: [
    // ... other imports ...
    AppPipesModule,
  ],
  // ... the rest of the config ...
})
export class HomePageModule {}

When users open your application, the app will load the home page. This homepage shows a list of people with their names and avatar. Users can click on anyone from the list and, clicking on any item, will trigger the openPerson method from the HomePage component. This will make your app load the /person route with that person's id as a query parameter.

As such, you will also need to create a new page to show the details of a particular person. You can call this new page as PersonPage and create it as follows:

# from the project root
ionic g page person

This will generate multiple files related to the PersonPage inside the ./src/app/person/ directory. Now, open the ./src/app/person/person.page.ts and replace its contents with this:

import {Component, OnInit} from '@angular/core';
import {ActivatedRoute, ParamMap, Router} from '@angular/router';
import {DataService} from '../services/data/data.service';
import {AuthService} from '../services/auth/auth.service';
import {switchMap} from 'rxjs/operators';

@Component({
  selector: 'app-person',
  templateUrl: './person.page.html',
  styleUrls: ['./person.page.scss'],
})
export class PersonPage implements OnInit {
  person: any;

  constructor(private dataService: DataService, private authService: AuthService,
              private route: ActivatedRoute,
              private router: Router) {
  }

  ngOnInit() {
    this.route.paramMap
      .pipe(
        switchMap((params: ParamMap) => this.dataService.getPerson(params.get('id')))
      ).subscribe(person => {
      this.person = person;
    });
  }
}

Also, replace the contents of the ./src/app/person/person.page.html file with the following:

<ion-header>
  <ion-toolbar color="secondary">
    <ion-buttons slot="start">
      <ion-back-button></ion-back-button>
    </ion-buttons>
    <ion-title>Person</ion-title>
    <ion-buttons slot="end">
      <ion-button routerLink="/home" routerDirection="root">Switch User</ion-button>
      <ion-button shape="round" fill="outline" color="light"  (click)="authService.login()" *ngIf="!authService.isLoggedIn">Login</ion-button>
      <ion-button shape="round" fill="outline" color="light"  (click)="authService.logout()" *ngIf="authService.isLoggedIn">Logout</ion-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

<ion-content>
<ion-card *ngIf="person">
  <ion-img [src]="person.picture?.large"></ion-img>

  <ion-card-content>
    <ion-card-title>{{ person.name?.title | capitalize }} {{ person.name?.first | capitalize }} {{ person.name?.last | capitalize }}</ion-card-title>
    <div padding *ngIf="!authService.isLoggedIn">
      <ion-button color="secondary" shape="round" fill="outline" (click)="authService.login()">Login to See More Detail</ion-button>
    </div>
    <div padding [hidden]="!authService.isLoggedIn">
      <ion-item icon-left>
          <span ngClass="flag-icon flag-icon-{{person.nat | lowercase}}"></span>
          <ion-icon slot="start" [name]="person.gender"></ion-icon>
        </ion-item>
      <ion-item icon-left>
          <ion-icon slot="start" name="call"></ion-icon>
          {{ person.phone }}
      </ion-item>
      <ion-item text-wrap>
          <ion-icon slot="start" name="mail"></ion-icon>
          {{ person.email }}
      </ion-item>
      <ion-button color="secondary" shape="round" fill="outline" (click)="authService.logout()">Logout</ion-button>
    </div>
  </ion-card-content>
</ion-card>
</ion-content>

Although the code of this new page is lengthy, you can see that it is not making anything too complex. When users click on someone from the list of people, the will open the PersonPage. Then, on this page, users will be able to see information about that person. Some information is hidden and can be seen after logging in ([hidden]="!authService.isLoggedIn").

The last thing you will need to do in relation to your PersonPage is to import the AppPipesModule you created before in the ./src/app/person/person.module.ts file, as shown below:

// ... other imports ...
import {AppPipesModule} from '../pipes/pipes.module';

@NgModule({
  imports: [
    // ... other imports ...
    AppPipesModule,
  ],
  // ... the rest of the config ...
})
export class PersonPageModule {}

Importing the Flag Icon Library

Since you want to show some nice country flags alongside with each person's detail, you will have to import this library inside your global.scss file. To do so, open this file and add the following line into it:

@import "~flag-icon-css/css/flag-icon.min.css";

Handling Authentication

After authentication requests are processed, Auth0 redirects users back to your application. Alongside with the user, it sends back authentication data via URL hashes. You need to parse that data and save it somewhere. As you probably remember, there is a method called handleAuthentication in the AuthService to do this. Now, you just have to call this method inside the constructor of app.component.ts, as shown below:

// ... other import statements ...
import { AuthService } from './services/auth/auth.service';

// ... @Component definition ...
export class AppComponent {
  constructor(
    private platform: Platform,
    private splashScreen: SplashScreen,
    private statusBar: StatusBar,
    private authService: AuthService
  ) {
    this.initializeApp();
    this.authService.handleAuthentication();
  }

  // ... initializeApp ...
}

Now, whenever you start a new project with Ionic CLI, it generates a module called app-routing.module.ts for handling routing in your app. This file contains all the routing configuration. When you generate a page, CLI also adds that page to the routing configuration in this file. As such, you should have following in the app-routing.module.ts file:

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

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

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

To make your PersonPage support query parameters, modify the path: 'person' route in the above file, as follows:

{ path: 'person/:id', loadChildren: './person/person.module#PersonPageModule' },

Then, to wrap things up, you will have to update the app.module.ts file (which is located inside the /src/app/ directory) to tie everything together. In this file, you will need to add all the services, pipes, and pages that you created into it so that Angular can recognize them:

import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {RouteReuseStrategy} from '@angular/router';
import {HttpClientModule} from '@angular/common/http';
import {IonicModule, IonicRouteStrategy} from '@ionic/angular';
import {SplashScreen} from '@ionic-native/splash-screen/ngx';
import {StatusBar} from '@ionic-native/status-bar/ngx';

import {AppComponent} from './app.component';
import {AppRoutingModule} from './app-routing.module';
import {AuthService} from './services/auth/auth.service';
import {DataService} from './services/data/data.service';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [BrowserModule, HttpClientModule, IonicModule.forRoot(), AppRoutingModule],
  providers: [
    StatusBar,
    SplashScreen,
    AuthService,
    DataService,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Now you can run the application as follows:

ionic serve

Note: If you get an error like, CapitalizePipe is declared in two modules, it's because, when you create a pipe using Ionic CLI, it also adds that pipe inside the app.module.ts file. In this case, you have to remove CapitalizePipe from the app.module.ts file.

Now, if you open the app in your browser (which actually Ionic does by default for you), you will see the following screen:

Developing apps with Ionic 4

Developing apps with Ionic 4

"I just built my first Ionic 4 app."

Conclusion

In this post, you learned about what's new in Ionic 4, changes from Ionic 3, and how to migrate to the new version. After that, you learned about using Stencil and Capacitor along with Ionic to create mobile applications and, finally, you created and secured a simple Ionic 4 app. To learn more about Ionic 4, check out the official Ionic 4 documentation.