Skip to main content

Building an Ionic app using ConfigCat’s Feature Flags

· 10 min read
Manuel Popa

We live in a world overflowing with information, where we’re connected and online almost every single day. Informational overload is a real thing, and we should all be aware enough not to fall victim to this behavioral pattern loop.

To-do lists are a real "Hail Mary" in today’s clickbait-centric and attention-grabbing world, so I figured that it’d be fun for us to build our very own to-do app from scratch, using Ionic and ConfigCat's feature flags.

Cover Image

What is Ionic JS?

Ionic is a platform for building neat hybrid (PWA) apps for both Android and iOS, using JavaScript.

Up until Ionic 4, we had a perfect symbiosis of sorts between the Ionic and Angular frameworks, where Ionic was mostly responsible for providing the UI components while Angular was used as the foundation for the mobile application. The arrival and adoption of web components broke this symbiosis and Ionic became framework-agnostic, meaning that you can now use it with or without any underlying framework.

The core of the Ionic SDK is the Capacitor, a cross-platform native bridge used to turn any web project into a native iOS or Android mobile application. It includes a rich library of plugins that enables easy access to most native device features with basic JavaScript.

What is a feature flag, and why use ConfigCat?

A feature flag is simply a toggle used to activate or deactivate specific features you may have in your application. Here are some examples of why you may want to use a feature flag:

  • giving early access to new features to a select few;
  • targeting specific countries;
  • various testing purposes.

Note: You could create your own in-house feature flag service or choose from an array of services available online such as ConfigCat, which has everything you need from such a service in the free tier.

Starting our To-do list

Note: You can find the finished app here, in case you want to follow along.

The first thing on our to-do list (pun intended) is to generate a new Ionic project using: ionic start ionic-to-do blank. This command will generate a blank template for our project.

The structure is pretty common. We'll have a src folder where our application will store its components and providers (also known as services).

Inside the src folder we'll find the app, assets, pages, providers, and theme folders. The root component is located in src/app/app.component.ts. This file is important because it'll be the first component added to the application, which will later be used to display all other components on top of it, just like a tree.

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';

import { HomePage } from '../pages/home/home';

@Component({
templateUrl: 'app.html',
})
export class MyApp {
rootPage: any = HomePage;

constructor(platform: Platform) {
platform.ready().then(() => {});
}
}

Notice that we’re importing our HomePage in the root component, so we can set it as the rootPage of our application.

Good to know: We can use the any type since TypeScript is strongly typed which makes the declaration of each variable a little more complicated compared to JavaScript. We use 'any' when the type is not known from the beginning, or when you work with variables that may change over time.

Next, we need to set our rootPage in the template found in src/app/app.html:

<ion-nav [root]="rootPage"></ion-nav>

Having set our root page, we need to take care of the navigation for the other pages. This can be done using push and pop to navigate between the needed views on the top of the main page. The push method is used to display a new view, while pop will remove the current view and go back to the previous one.

Let's turn our attention to the pages folder, where we add "home", "item-detail", and "add-item" folders for each of the respective views. Each folder will include the template and the class for the view.

The home.html file combines Ionic elements with Angular directives, as you can see from the snippet:

<ion-header>
<ion-navbar color="secondary">
<ion-title> My to-do list </ion-title>
<ion-buttons end>
<button ion-button icon-only (click)="addItem()">
<ion-icon name="add-circle"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>

<ion-content>
<ion-list>
<ion-item *ngFor="let item of items" (click)="viewItem(item)"
>{{item.title}}</ion-item
>
</ion-list>
</ion-content>

This file contains some Ionic specific elements like ion-header and ion-content, which are Ionic UI components that can be reused throughout the application. Some of these elements also have specific Ionic attributes.

For example, ion-buttons has an end attribute used to automatically place that element at the end of the nav bar (right of the screen).

To do demo app

We can attach listeners to these elements by using the parenthesis - in our case, the button element has a click event which will call the method addItem().

On the other hand, *ngFor is an Angular directive used for iterating through an array of objects and creating elements that will be inserted into the DOM.

To control the functionality of the home page, we need the home.ts file which has the class HomePage:

import { Component } from '@angular/core';
import { ModalController, NavController } from 'ionic-angular';
import { AddItemPage } from '../add-item/add-item';
import { ItemDetailPage } from '../item-detail/item-detail';
import { Data } from '../../providers/data';

@Component({
selector: 'page-home',
templateUrl: 'home.html',
})
export class HomePage {
public items = [];

constructor(
public navCtrl: NavController,
public modalCtrl: ModalController,
public dataService: Data,
) {
this.dataService.getData().then((items) => {
if (items) {
this.items = items;
}
});
}

ionViewDidLoad() {}

addItem() {
let addModal = this.modalCtrl.create(AddItemPage);

addModal.onDidDismiss((item) => {
if (item) {
this.saveItem(item);
}
});
addModal.present();
}

saveItem(item) {
this.items.push(item);
this.dataService.save(this.items);
}

viewItem(item) {
this.navCtrl.push(ItemDetailPage, {
item: item,
});
}
}

In this file, we use ionViewDidLoad - an Ionic lifecycle hook to be triggered when the page is loaded. Also, importing the NavController from 'ionic-angular' helps us control the toggle of our views using 'push' and 'pop' (I mentioned them earlier).

There are also three custom methods which we can use for adding, saving, and viewing our items. The next view will be constructed in the add-item folder:

<ion-header>
<ion-toolbar color="secondary">
<ion-title> Add Item </ion-title>
<ion-buttons end>
<button ion-button icon-only (click)="close()">
<ion-icon name="close"></ion-icon>
</button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item>
<ion-label floating>Title</ion-label>
<ion-input type="text" [(ngModel)]="title"></ion-input>
</ion-item>
<ion-item>
<ion-label floating>Description</ion-label>
<ion-input type="text" [(ngModel)]="description"></ion-input>
</ion-item>
</ion-list>
<button full ion-button color="secondary" (click)="saveItem()">Save</button>
</ion-content>

This time we encounter a new Angular specific functionality - [(ngModel)] - which is used for two-way data bindings.

import { Component } from '@angular/core';
import { NavController, ViewController } from 'ionic-angular';

@Component({
selector: 'page-add-item',
templateUrl: 'add-item.html',
})
export class AddItemPage {
title: string;
description: string;

constructor(public nav: NavController, public view: ViewController) {}

saveItem() {
let newItem = {
title: this.title,
description: this.description,
};

this.view.dismiss(newItem);
}

close() {
this.view.dismiss();
}
}

The class for add-item has a new Ionic service called ViewController, which is imported just like the NavController from 'ionic-angular'. This service helps close the modal using the 'dismiss' method which can also be used to pass the item saved to the home page through this event.

This new component (AddItemPage) will be imported into the home.ts file, so we can use it to create the add item modal.
The next item on our to-do list is the item-detail component:

<ion-header>
<ion-navbar color="secondary">
<ion-title> {{title}} </ion-title>
</ion-navbar>
</ion-header>

<ion-content>
<ion-card>
<ion-card-content> {{description}} </ion-card-content>
</ion-card>
<ion-card *ngIf="dueDate">
<ion-card-content> {{dueDate}} </ion-card-content>
</ion-card>
</ion-content>

Nothing new on this template. However, we will use a feature flag inside the ItemDetailPage class.

Using ConfigCat's Feature Flags

Let's say that we want to implement a new application feature and make it available for some beta users. Our new feature will take the form of another element on our to-do card called 'dueDate':

import { Component } from '@angular/core';
import { NavParams } from 'ionic-angular';

@Component({
selector: 'page-item-detail',
templateUrl: 'item-detail.html',
})
export class ItemDetailPage {
title;
description;
dueDate;

constructor(public navParams: NavParams) {}

ionViewDidLoad() {
this.title = this.navParams.get('item').title;
this.description = this.navParams.get('item').description;
this.dueDate = this.navParams.get('item').dueDate;
}
}

The change for the feature flag will be made in the add-item.ts file. This is the component used to control what details are added to the item card.

ConfigCat has good documentation. The entire process of setting it up will take about 10 minutes. The first thing to do is to navigate to ConfigCat and sign up for a free account. Then you can create the feature flag:

Creating the first Feature Flag

The next step is to install the configcat-js package via npm - npm install configcat-js. This will give access to the ConfigCat client to use our newly created flag in our application:

import { Component } from '@angular/core';
import { NavController, ViewController } from 'ionic-angular';
import * as configcat from 'configcat-js';

@Component({
selector: 'page-add-item',
templateUrl: 'add-item.html',
})
export class AddItemPage {
title: string;
description: string;
dueDate: string;
configCatClient;
flag;

constructor(public nav: NavController, public view: ViewController) {
this.configCatClient = configcat.createClient(
'1K3ZCCa1S0GKW6RN4S5wyA/o33xY-5E_U-aEzyeTlme_g',
{},
);
this.configCatClient
.getValueAsync('isMyFirstFeatureEnabled', false)
.then((value) => {
this.flag = value;
});
}

saveItem() {
let newItem = {
title: this.title,
description: this.description,
dueDate: this.dueDate,
};

this.view.dismiss(newItem);
}

close() {
this.view.dismiss();
}
}

Notice how we added the three new properties to our AddItemPage class - dueDate, configCatClient, and flag. AddItemPage.flag is the property that will keep the value which we retrieve from the ConfigCat client. Based on this value, we’ll show or hide the 'Due Date' item using Angular's *ngIf directive.

Final app

<ion-header>
<ion-toolbar color="secondary">
<ion-title> Add Item </ion-title>
<ion-buttons end>
<button ion-button icon-only (click)="close()">
<ion-icon name="close"></ion-icon>
</button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item>
<ion-label floating>Title</ion-label>
<ion-input type="text" [(ngModel)]="title"></ion-input>
</ion-item>
<ion-item>
<ion-label floating>Description</ion-label>
<ion-input type="text" [(ngModel)]="description"></ion-input>
</ion-item>
<ion-item *ngIf="flag">
<ion-label floating>Due Date</ion-label>
<ion-input type="text" [(ngModel)]="dueDate"></ion-input>
</ion-item>
</ion-list>
<button full ion-button color="secondary" (click)="saveItem()">Save</button>
</ion-content>

How do we save our data?

Our application is almost complete. The only problem is that it currently doesn't really save any data - every item you save will be wiped clean on reload. This can be fixed by accessing the local storage.

Good to know: Ionic provides a service called 'Storage' that can be used for saving data using a native SQLite database. However, if no such plugin is available, it falls back to the browser's local storage.

import { Storage } from '@ionic/storage';
import { Injectable } from '@angular/core';

@Injectable()
export class Data {
constructor(public storage: Storage) {}

getData() {
return this.storage.get('todos');
}

save(data) {
this.storage.set('todos', data);
}
}

In home.ts, we can access our data service in the constructor to get our list of items from the local storage. This is done asynchronously, meaning the rest of the application will load the rest of the components and not wait for the completion of this request, thus blocking it in the process.

You can check out the sample application code right here.

Key Takeaways

If you want to build a hybrid application with a native touch regardless of the device that it's running on, Ionic is the choice to go with.

Ionic 4 breaks away from the restriction of using Angular as the base for your development, giving you the freedom to choose from all those cool frameworks available out there. For more details about Ionic and what they are cooking next, have a look at their website.

Releasing a great application in production doesn't mean the work is done. Any newly added feature may hurt your user base if it is not what they want. This can be avoided by using feature flags, giving access to only a fraction of them for getting some live feedback.

You can read more about feature flags on the ConfigCat website, or stay up to date via the Facebook, Twitter, or LinkedIn accounts.