Ionic Components

In this article by Gaurav Saini the authors of the book Hybrid Mobile Development with Ionic, we will learn following topics:

  • Building vPlanet Commerce
  • Ionic 2 components

(For more resources related to this topic, see here.)

Building vPlanet Commerce

The vPlanet Commerce app is an e-commerce app which will demonstrate various Ionic components integrated inside the application and also some third party components build by the community.

Let’s start by creating the application from scratch using sidemenu template:

Hybrid Mobile Development with Ionic

You now have the basic application ready based on sidemenu template, next immediate step I took if to take reference from ionic-conference-app for building initial components of the application such aswalkthrough.

Let’s create a walkthrough component via CLI generate command:

$ ionic g page walkthrough

As, we get started with the walkthrough component we need to add logic to show walkthrough component only the first time when user installs the application:

// src/app/app.component.ts
// Check if the user has already seen the walkthrough
this.storage.get('hasSeenWalkThrough').then((hasSeenWalkThrough) => {
if (hasSeenWalkThrough) {
this.rootPage = HomePage;
} else {
this.rootPage = WalkThroughPage;
}
this.platformReady();
})

So, we store a boolean value while checking if user has seen walkthrough first time or not. Another important thing we did create Events for login and logout, so that when user logs into the application and we can update Menu items accordingly or any other data manipulation to be done:

// src/app/app.component.ts

export interface PageInterface {
title: string;
component: any;
icon: string;
logsOut?: boolean;
index?: number;
tabComponent?: any;
}
export class vPlanetApp {
   loggedInPages: PageInterface[] = [
    { title: 'account', component: AccountPage, icon: 'person' },
    { title: 'logout', component: HomePage, icon: 'log-out', logsOut: true }
  ];
  loggedOutPages: PageInterface[] = [
    { title: 'login', component: LoginPage, icon: 'log-in' },
    { title: 'signup', component: SignupPage, icon: 'person-add' }
  ];

listenToLoginEvents() {
this.events.subscribe('user:login', () => {
this.enableMenu(true);
});

this.events.subscribe('user:logout', () => {
this.enableMenu(false);
});
}

enableMenu(loggedIn: boolean) {
this.menu.enable(loggedIn, 'loggedInMenu');
this.menu.enable(!loggedIn, 'loggedOutMenu');
}

// For changing color of Active Menu

isActive(page: PageInterface) {
if (this.nav.getActive() && this.nav.getActive().component === page.component) {
return 'primary';
}
return;
}

}

Next we have inside our app.html we have multiple <ion-menu> items depending upon whether user is loggedin or logout:

// src/app/app.html<!-- logged out menu -->
<ion-menu id="loggedOutMenu" [content]="content">

<ion-header>
<ion-toolbar>
<ion-title>{{'menu' | translate}}</ion-title>
</ion-toolbar>
</ion-header>

<ion-content class="outer-content">

<ion-list>
<ion-list-header>
        {{'navigate' | translate}}
</ion-list-header>
<button ion-item menuClose *ngFor="let p of appPages" (click)="openPage(p)">
<ion-icon item-left [name]="p.icon" [color]="isActive(p)"></ion-icon>
        {{ p.title | translate }}
</button>
</ion-list>

<ion-list>
<ion-list-header>
        {{'account' | translate}}
</ion-list-header>
<button ion-item menuClose *ngFor="let p of loggedOutPages" (click)="openPage(p)">
<ion-icon item-left [name]="p.icon" [color]="isActive(p)"></ion-icon>
        {{ p.title | translate }}
</button>
<button ion-item menuClose *ngFor="let p of otherPages" (click)="openPage(p)">
<ion-icon item-left [name]="p.icon" [color]="isActive(p)"></ion-icon>
        {{ p.title | translate }}
</button>
</ion-list>
</ion-content>

</ion-menu>

<!-- logged in menu -->
<ion-menu id="loggedInMenu" [content]="content">

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

<ion-content class="outer-content">

<ion-list>
<ion-list-header>
        {{'navigate' | translate}}
</ion-list-header>
<button ion-item menuClose *ngFor="let p of appPages" (click)="openPage(p)">
<ion-icon item-left [name]="p.icon" [color]="isActive(p)"></ion-icon>
        {{ p.title | translate }}
</button>
</ion-list>

<ion-list>
<ion-list-header>
        {{'account' | translate}}
</ion-list-header>
<button ion-item menuClose *ngFor="let p of loggedInPages" (click)="openPage(p)">
<ion-icon item-left [name]="p.icon" [color]="isActive(p)"></ion-icon>
        {{ p.title | translate }}
</button>
<button ion-item menuClose *ngFor="let p of otherPages" (click)="openPage(p)">
<ion-icon item-left [name]="p.icon" [color]="isActive(p)"></ion-icon>
        {{ p.title | translate }}
</button>
</ion-list>

</ion-content>

</ion-menu>

As, our app start mainly from app.html so we declare rootPage here:

<!-- main navigation -->
<ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>

Let’s now look into what all pages, services, and filter we will be having inside our app. Rather than mentioning it as a bullet list, the best way to know this is going through app.module.ts file which has all the declarations, imports, entryComponents and providers.

// src/app/app.modules.ts

import { NgModule, ErrorHandler } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { TranslateModule, TranslateLoader, TranslateStaticLoader } from 'ng2-translate/ng2-translate';
import { Http } from '@angular/http';
import { CloudSettings, CloudModule } from '@ionic/cloud-angular';
import { Storage } from '@ionic/storage';
import { vPlanetApp } from './app.component';

import { AboutPage } from '../pages/about/about';
import { PopoverPage } from '../pages/popover/popover';
import { AccountPage } from '../pages/account/account';
import { LoginPage } from '../pages/login/login';
import { SignupPage } from '../pages/signup/signup';
import { WalkThroughPage } from '../pages/walkthrough/walkthrough';
import { HomePage } from '../pages/home/home';
import { CategoriesPage } from '../pages/categories/categories';
import { ProductsPage } from '../pages/products/products';
import { ProductDetailPage } from '../pages/product-detail/product-detail';
import { WishlistPage } from '../pages/wishlist/wishlist';
import { ShowcartPage } from '../pages/showcart/showcart';
import { CheckoutPage } from '../pages/checkout/checkout';
import { ProductsFilterPage } from '../pages/products-filter/products-filter';
import { SupportPage } from '../pages/support/support';
import { SettingsPage } from '../pages/settings/settings';
import { SearchPage } from '../pages/search/search';
import { UserService } from '../providers/user-service';
import { DataService } from '../providers/data-service';
import { OrdinalPipe } from '../filters/ordinal';

// 3rd party modules
import { Ionic2RatingModule } from 'ionic2-rating';

export function createTranslateLoader(http: Http) {
return new TranslateStaticLoader(http, './assets/i18n', '.json');
}

// Configure database priority
export function provideStorage() {
return new Storage(['sqlite', 'indexeddb', 'localstorage'], { name: 'vplanet' })
}

const cloudSettings: CloudSettings = {
'core': {
'app_id': 'f8fec798'
}
};

@NgModule({
declarations: [
vPlanetApp,
AboutPage,
AccountPage,
LoginPage,
PopoverPage,
SignupPage,
WalkThroughPage,
HomePage,
CategoriesPage,
ProductsPage,
ProductsFilterPage,
ProductDetailPage,
SearchPage,
WishlistPage,
ShowcartPage,
CheckoutPage,
SettingsPage,
SupportPage,
OrdinalPipe,
],
imports: [
IonicModule.forRoot(vPlanetApp),
Ionic2RatingModule,
TranslateModule.forRoot({ 
provide: TranslateLoader,
useFactory: createTranslateLoader,
deps: [Http]
}),
CloudModule.forRoot(cloudSettings)
],
bootstrap: [IonicApp],
entryComponents: [
vPlanetApp,
AboutPage,
AccountPage,
LoginPage,
PopoverPage,
SignupPage,
WalkThroughPage,
HomePage,
CategoriesPage,
ProductsPage,
ProductsFilterPage,
ProductDetailPage,
SearchPage,
WishlistPage,
ShowcartPage,
CheckoutPage,
SettingsPage,
SupportPage
],
providers: [
{provide: ErrorHandler, useClass: IonicErrorHandler},
{ provide: Storage, useFactory: provideStorage }, 
UserService, 
DataService
]
})

export class AppModule {}

Ionic components

There are many Ionic JavaScript components which we can effectively use while building our application. What's best is to look around for features we will be needing in our application. Let’s get started with Home page of our e-commerce application which will be having a image slider having banners on it.

Slides

Slides component is multi-section container which can be used in multiple scenarios same astutorial view or banner slider. <ion-slides> component have multiple <ion-slide> elements which can be dragged or swipped left/right. Slides have multiple configuration options available which can be passed in the ion-slides such as autoplay, pager, direction: vertical/horizontal, initialSlide and speed. 

Using slides is really simple as we just have to include it inside our home.html, no dependency is required for this to be included in the home.ts file:

<ion-slides pager #adSlider (ionSlideDidChange)="logLenth()" style="height: 250px">
<ion-slide *ngFor="let banner of banners">
<img [src]="banner">
</ion-slide>
</ion-slides>

// Defining banners image path

export class HomePage {

products: any;
banners: String[];

constructor() {
this.banners = [
'assets/img/banner-1.webp',
'assets/img/banner-2.webp',
'assets/img/banner-3.webp'
]
}
}

Lists

Lists are one of the most used components in many applications. Inside lists we can display rows of information. We will be using lists multiple times inside our application such ason categories page where we are showing multiple sub-categories:

// src/pages/categories/categories.html

<ion-content class="categories">

<ion-list-header *ngIf="!categoryList">Fetching Categories ....</ion-list-header>

	<ion-list *ngFor="let cat of categoryList">

<ion-list-header>{{cat.name}}</ion-list-header>

<ion-item *ngFor="let subCat of cat.child">
<ion-avatar item-left>
<img [src]="subCat.image">
</ion-avatar>
<h2>{{subCat.name}}</h2>
<p>{{subCat.description}}</p>
<button ion-button clear item-right (click)="goToProducts(subCat.id)">View</button>
</ion-item>

</ion-list>

</ion-content>

Loading and toast

Loading component can be used to indicate some activity while blocking any user interactions. One of the most common cases of using loading component is HTTP/ calls to the server, as we know  it takes time to fetch data from server, till then for good user experience we can show some content showing Loading .. or Login wait .. for login pages.

Toast is a small pop-up which provides feedback, usually used when some action  is performed by the user. Ionic 2 now provides toast component as part of its library, previously we have to use native Cordova plugin for toasts which in either case can now be used also.

Loading and toast component both have a method create. We have to provide options  while creating these components:

// src/pages/login/login.ts

import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';
import { NavController, LoadingController, ToastController, Events } from 'ionic-angular';
import { SignupPage } from '../signup/signup';
import { HomePage } from '../home/home';

import { Auth, IDetailedError } from '@ionic/cloud-angular';

import { UserService } from '../../providers/user-service';

@Component({
selector: 'page-user',
templateUrl: 'login.html'
})
export class LoginPage {
login: {email?: string, password?: string} = {};

submitted = false;

constructor(public navCtrl: NavController, 
public loadingCtrl: LoadingController,
public auth: Auth,
public userService: UserService,
public toastCtrl: ToastController,
public events: Events) { }

onLogin(form: NgForm) {
this.submitted = true;

if (form.valid) {
// start Loader
let loading = this.loadingCtrl.create({
content: "Login wait...",
duration: 20
});
loading.present();

this.auth.login('basic', this.login).then((result) => {
// user is now registered
this.navCtrl.setRoot(HomePage);
this.events.publish('user:login');
loading.dismiss();
this.showToast(undefined);
}, (err: IDetailedError<string[]>) => {
console.log(err);
loading.dismiss();
this.showToast(err)
});
}
}

showToast(response_message:any) {
let toast = this.toastCtrl.create({
message: (response_message ? response_message : "Log In Successfully"),
duration: 1500
});
toast.present();
}

onSignup() {
this.navCtrl.push(SignupPage);
}
}

As, you can see from the previouscode creating a loader and toast is almost similar at code level. The options provided while creating are also similar, we have used loader here while login and toast after that to show the desired message.

Setting duration option is good to use, as in case loader is dismissed or not handled properly in code then we will block the user for any further interactions on app. In HTTP calls to server we might get connection issues or failure cases, in that scenario it may end up blocking users.

Tabs versussegments

Tabs are easiest way to switch between views and organise content at higher level. On the other hand segment is a group of button and can be treated as a local  switch tabs inside a particular component mainly used as a filter. With tabs we can build quick access bar in the footer where we can place Menu options such as Home, Favorites, and Cart. This way we can have one click access to these pages or components. On the other hand we can use segments inside the Account component and divide the data displayed in three segments profile, orders and wallet:

// src/pages/account/account.html

<ion-header>
<ion-navbar>
<button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Account</ion-title>
</ion-navbar>
<ion-toolbar [color]="isAndroid ? 'primary' : 'light'" no-border-top>
<ion-segment [(ngModel)]="account" [color]="isAndroid ? 'light' : 'primary'">
<ion-segment-button value="profile">
Profile
</ion-segment-button>
<ion-segment-button value="orders">
Orders
</ion-segment-button>
<ion-segment-button value="wallet">
Wallet
</ion-segment-button>
</ion-segment>
</ion-toolbar>
</ion-header>

<ion-content class="outer-content">
<div [ngSwitch]="account">

<div padding-top text-center *ngSwitchCase="'profile'" >
<img src="http://www.gravatar.com/avatar?d=mm&s=140">
<h2>{{username}}</h2>

<ion-list inset>
<button ion-item (click)="updatePicture()">Update Picture</button>
<button ion-item (click)="changePassword()">Change Password</button>
<button ion-item (click)="logout()">Logout</button>
</ion-list>

</div>

<div padding-top text-center *ngSwitchCase="'orders'" >
// Order List data to be shown here
</div>

<div padding-top text-center *ngSwitchCase="'wallet'">
// Wallet statement and transaction here.
</div>

</div>
</ion-content>

Hybrid Mobile Development with Ionic

This is how we define a segment in Ionic, we don’t need to define anything inside the typescript file for this component. On the other hand with tabs we have to assign a component for  each tab and also can access its methods via Tab instance. Just to mention,  we haven’t used tabs inside our e-commerce application as we are using side menu. One good example will be to look in ionic-conference-app (https://github.com/driftyco/ionic-conference-app) you will find sidemenu and tabs both in single application:

/
// We currently don’t have Tabs component inside our e-commerce application
// Below is sample code about how we can integrate it.

<ion-tabs #showTabs tabsPlacement="top" tabsLayout="icon-top" color="primary">
<ion-tab [root]="Home"></ion-tab>
<ion-tab [root]="Wishlist"></ion-tab>
<ion-tab [root]="Cart"></ion-tab>
</ion-tabs>



import { HomePage } from '../pages/home/home';
import { WishlistPage } from '../pages/wishlist/wishlist';
import { ShowcartPage } from '../pages/showcart/showcart';

export class TabsPage {

@ViewChild('showTabs') tabRef: Tabs;

// this tells the tabs component which Pages
// should be each tab's root Page

Home = HomePage;
Wishlist = WishlistPage;
Cart = ShowcartPage;

constructor() {

}

// We can access multiple methods via Tabs instance
// select(TabOrIndex), previousTab(trimHistory), getByIndex(index)
// Here we will console the currently selected Tab.

ionViewDidEnter() {
console.log(this.tabRef.getSelected());
}
}

Properties can be checked in the documentation (https://ionicframework.com/docs/v2/api/components/tabs/Tabs/) as, there are many properties available for tabs, like mode, color, tabsPlacement and tabsLayout. Similarly we can configure some tabs properties at Config level also, you will find here what all properties you can configure globally or for specific platform. (https://ionicframework.com/docs/v2/api/config/Config/).

Alerts

Alerts are the components provided in Ionic for showing trigger alert, confirm, prompts or some specific actions. AlertController can be imported from ionic-angular which allow us to programmatically create and show alerts inside the application. One thing to note here is these are JavaScript pop-up not the native platform pop-up. There is a Cordova plugin cordova-plugin-dialogs (https://ionicframework.com/docs/v2/native/dialogs/) which you can use if native dialog UI elements are required.

Currently five types of alerts we can show in Ionic app basic alert, prompt alert, confirmation alert, radio and checkbox alerts:

// A radio alert inside src/pages/products/products.html for sorting products

<ion-buttons>
<button ion-button full clear (click)="sortBy()">
<ion-icon name="menu"></ion-icon>Sort
</button>
</ion-buttons>

// onClick we call sortBy method 
// src/pages/products/products.ts
import { NavController, PopoverController, ModalController, AlertController } from 'ionic-angular';	

export class ProductsPage {
	
	constructor(
		public alertCtrl: AlertController
	) {

sortBy() {
let alert = this.alertCtrl.create();
alert.setTitle('Sort Options');

alert.addInput({
type: 'radio',
label: 'Relevance',
value: 'relevance',
checked: true
});
alert.addInput({
type: 'radio',
label: 'Popularity',
value: 'popular'
});
alert.addInput({
type: 'radio',
label: 'Low to High',
value: 'lth'
});
alert.addInput({
type: 'radio',
label: 'High to Low',
value: 'htl'
});
alert.addInput({
type: 'radio',
label: 'Newest First',
value: 'newest'
});

alert.addButton('Cancel');
alert.addButton({
text: 'OK',
handler: data => {
	console.log(data);
		// Here we can call server APIs with sorted data
		// using the data which user applied.
}
});

alert.present().then(() => {
// Here we place any function that
	   // need to be called as the alert in opened.
});
}

}

Hybrid Mobile Development with Ionic

Cancel and OK buttons. We have used this here for sorting the products according to relevance price or other sorting values.

We can prepare custom alerts also, where we can mention multiple options. Same as in previous example we have five radio options, similarly we can even add a text input box for taking some inputs and submit it. Other than this, while creating alerts remember that there are alert, input and button options properties for all the alerts present in the AlertController component.(https://ionicframework.com/docs/v2/api/components/alert/AlertController/).

Some alert options:

  • title:// string: Title of the alert.
  • subTitle:// string(optional): Sub-title of the popup.
  • Message:// string: Message for the alert
  • cssClass:// string: Custom CSS class name
  • inputs:// array: Set of inputs for alert.
  • Buttons:// array(optional): Array of buttons

Cards and badges

Cards are one of the important component used more often in mobile and web applications. The reason behind cards are so popular because its a great way to organize information and get the users access to quantity of information on smaller screens also. Cards are really flexible and responsive due to all these reasons they are adopted very quickly by developers and companies. We will also be using cards inside our application on home page itself for showing popular products. Let’s see what all different types of cards Ionic provides in its library:

  • Basic cards
  • Cards with header and Footer
  • Cards lists
  • Cards images
  • Background cards
  • Social and map cards

Social and map cards are advanced cards, which is build with custom CSS. We can develop similar advance card also.

// src/pages/home/home.html

<ion-card>
<img [src]="prdt.imageUrl"/>
<ion-card-content>
	<ion-card-title no-padding>
	{{prdt.productName}}
</ion-card-title>
	<ion-row no-padding class="center">
<ion-col>
<b>{{prdt.price | currency }} &nbsp; </b><span class="dis
count">{{prdt.listPrice | currency}}</span>
</ion-col>
</ion-row>
</ion-card-content>
</ion-card>

We have used here image card with a image on top and below we have favorite and view button icons. Similarly, we can use different types of cards where ever its required. Also, at the same time we can customize our cards and mix two types of card using their specific CSS classes or elements.

Badges are small component used to show small information, for example showing number of items in cart above the cart icon. We have used it in our e-commerce application for showing the ratings of product.

<ion-badge width="25">4.1</ion-badge>

Summary

In this article we have learned, building vPlanet Commerce and Ionic components. 

Resources for Article:


Further resources on this subject:


You've been reading an excerpt of:

Hybrid Mobile Development with Ionic

Explore Title