How to use Modals as detail pages

First Published: 4 April 2019
Updated on: 4 April 2019

Should you always create a new page when you want to display data? This is something I started to ask myself after a project I was working for a big bank, the UX designers proposed we used full-screen modals to show some of the information.

I kinda liked it so started applying it in some of my personal projects, for example, I’m building a budgeting app to use with my wife, we had a page where we see the list of all of our accounts, there are no interactions, it just shows us the accounts with the money that’s in them, so it made sense to transform that one into a modal.

Today you’ll learn how to use modals in Ionic to display details about your data, specifically:

  • How to present a modal.
  • How to pass the data it will show.
  • How to dismiss the modal when you’re done.

The first thing you need to know is that a modal needs a component to present, what does that mean? That you need to create a component that holds the information/logic that you want the modal to show/execute.

So go ahead and inside your app use the CLI to generate a new component, in my case, it’s the account list component.

ionic generate component components/accounts

Important Note

When you create a component it’s not registered to be used globally, this has been the case I think since lazy loading was introduced into Angular, so you’ll need to register it inside the module you’re going to use, for example, if I’m presenting the modal from my home page, then I need to go to home.module.ts and register the component there:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';

import { HomePage } from './home.page';
import { AccountsComponent } from '../components/accounts/accounts.component';

@NgModule({
  entryComponents: [AccountsComponent],
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    RouterModule.forChild([
      {
        path: '',
        component: HomePage
      }
    ])
  ],
  declarations: [HomePage, AccountsComponent]
})
export class HomePageModule {}

Note how I’m registering AccountsComponent in both the entryComponents array and the declarations array.

Once it’s registered we need to create a function that will trigger it, for example, I want to have a card in the home page that shows the current balance of all my accounts combined, and when I click that card I want to trigger the modal that will show me the list of accounts with their respective balances.

For that, we need to create the card in the HTML first, so in my case, I open home.component.html and add the following:

<ion-header>
  <ion-toolbar>
    <ion-title>Family Finances</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding="">
  <ion-card (click)="displayAccountList()">
    <ion-card-header>
      <ion-card-title>{{ fullBalance | currency }}</ion-card-title>
      <ion-card-subtitle>Balance</ion-card-subtitle>
    </ion-card-header>
  </ion-card>
</ion-content>

Nothing too weird, I’m creating a page that has a title (Family finances) and a card that displays the balance of my accounts, and when clicked it triggers the displayAccountList() method.

Inside the home.component.ts the first thing we want to do is to import everything we’re going to need and create our class variables.

import { Component, OnInit } from '@angular/core';
import { ModalController } from '@ionic/angular';
import { Account } from '../models';
import { AccountsComponent } from '../components/accounts/accounts.component';
import { AngularFirestoreCollection, AngularFirestore } from '@angular/fire/firestore';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {
  public fullBalance: number;
  private accountList: Account[];

  constructor(private firestore: AngularFirestore, private modalCtrl: ModalController) {}

  ngOnInit() {}

}
  • We’re importing AngularFirestore and AngularFirestoreCollection as part of AngularFire because I’m getting my data from Firebase.
  • We’re importing Account which is a model/interface I created that dictates the type of object an Account is, feel free to create your own models.
  • We’re importing the AccountsComponent because that’s the component we’ll use for the modal.
  • And we’re importing ModalController because that’s the helper class that allows us to use and interact with modals.
  • We’re also injecting Firestore and the modal controller into the constructor.

After that’s ready, we need to get the data from Firebase, the data we’ll later send to the modal.

import { Component, OnInit } from '@angular/core';
import { ModalController } from '@ionic/angular';
import { Account } from '../models';
import { AccountsComponent } from '../components/accounts/accounts.component';
import { AngularFirestoreCollection, AngularFirestore } from '@angular/fire/firestore';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {
  public fullBalance: number;
  private accountList: Account[];

  constructor(private firestore: AngularFirestore, private modalCtrl: ModalController) {}

  ngOnInit() {
    this.getAccountList()
      .valueChanges()
      .subscribe(accounts => {
        this.accountList = accounts;
        this.fullBalance = 0;
        accounts.forEach(account => {
          this.fullBalance += account.balance;
        });
    });
  }

  getAccountList(): AngularFirestoreCollection<account> {
    return this.firestore.collection('accounts');
  }

}

We’re creating the getAccountList() function to fetch the data from a firestore collection, and then inside the ngOnInit()

ngOnInit() {
  this.getAccountList()
    .valueChanges()
    .subscribe(accounts => {
      this.accountList = accounts;
      this.fullBalance = 0;
      accounts.forEach(account => {
        this.fullBalance += account.balance;
      });
  });
}

We’re getting the list of accounts and calculating the combined balance of the accounts.

Once we have the data it’s time to pass the data to our modal component and then trigger it:

import { Component, OnInit } from '@angular/core';
import { ModalController } from '@ionic/angular';
import { Account } from '../models';
import { AccountsComponent } from '../components/accounts/accounts.component';
import { AngularFirestoreCollection, AngularFirestore } from '@angular/fire/firestore';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {
  public fullBalance: number;
  private accountList: Account[];

  constructor(private firestore: AngularFirestore, private modalCtrl: ModalController) {}

  ngOnInit() {
    this.getAccountList()
      .valueChanges()
      .subscribe(accounts => {
        this.accountList = accounts;
        this.fullBalance = 0;
        accounts.forEach(account => {
          this.fullBalance += account.balance;
        });
    });
  }

  getAccountList(): AngularFirestoreCollection<account> {
    return this.firestore.collection('accounts');
  }

  async displayAccountList(): Promise<void> {
    const accountListModal = await this.modalCtrl.create({
      component: AccountsComponent,
      componentProps: { accounts: this.accountList },
    });
    return await accountListModal.present();
  }
}

Note that we’re passing the account list as a parameter called accounts, and then presenting the modal.

async displayAccountList(): Promise<void> {
  const accountListModal = await this.modalCtrl.create({
    component: AccountsComponent,
    componentProps: { accounts: this.accountList },
  });
  return await accountListModal.present();
}

If you test this, it should display the modal, you’ll probably see a white page or whatever is in your component’s HTML. For us it was simply showing a title, a dismiss button and the account list.

<ion-header>
  <ion-toolbar>
    <ion-title>
      Accounts
    </ion-title>
    <ion-buttons slot="end">
      <ion-button (click)="dismiss()">
        <ion-icon slot="icon-only" name="close"></ion-icon>
      </ion-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

<ion-content padding>
  <ion-list>
    <ion-item *ngfor="let account of accounts">
      <ion-label text-wrap></ion-label>
    </ion-item>
  </ion-list>
  <h2>{{ account.name }}</h2>
  <strong>{{ account.balance | currency }}</strong>
  {{ account.description }}
</ion-content>

Now, inside of our accounts.component.ts there are 2 things we need to do, connect the accounts variable with the accounts we’re sending from the home page, and create the dismiss function.

Let’s start with the account list binding, all we need to do is to create an input with the name accounts in our page, that will recognize the accounts we’re sending from the home page and bind them to that variable.

import { Component, OnInit, Input } from '@angular/core';
import { Account } from 'src/app/models';

@Component({
  selector: 'app-accounts',
  templateUrl: './accounts.component.html',
  styleUrls: ['./accounts.component.scss'],
})
export class AccountsComponent implements OnInit {
  @Input() public accounts: Account[];
  constructor() {}

  ngOnInit() {}
}

That’s it, you can see the account list now, but wait, if the modal isn’t in the navigation stack, how do I go back? That’s what the dismiss button is for, to give it the dismiss functionality, we need to import the ModalController and inject it to the constructor, then call the controller’s dismiss function:

import { Component, OnInit, Input } from '@angular/core';
import { Account } from 'src/app/models';
import { ModalController } from '@ionic/angular';

@Component({
  selector: 'app-accounts',
  templateUrl: './accounts.component.html',
  styleUrls: ['./accounts.component.scss'],
})
export class AccountsComponent implements OnInit {
  @Input() public accounts: Account[];
  constructor(private modalCtrl: ModalController) {}

  ngOnInit() {}

  dismiss(): void {
    this.modalCtrl.dismiss();
  }
}

Now you have a fully functional modal that shows the data you fetched from Firebase and is able to dismiss itself on your command :-)

Are you using modals inside your app? Do let me know how in the comments below!