Handling Lists from Firebase within Ionic Framework apps

First Published: 2 July 2018
Updated on: 24 July 2019

This is the fourth post of a 7 post series that will teach you how to build an app using Ionic Framework and Firebase from scratch.

We have been learning a lot about Firestore in these few chapters, ranging from authentication to CRUD (hey, you even learned about Promises).

In this chapter, we are going to start working with lists of data, reading them from our database to display them in our app, adding more items to those lists, and more.

The idea is to let our user start creating events, so she can keep track of the events she is hosting.

I think that is enough for an intro, so let’s jump into business!

Creating the Event Service

As always we are going to be using a service to handle all of our event data, the reason we are using services throughout this book is that they will help you with one of the most common programming principles: DRY, which stands for Don’t Repeat Yourself.

For example, I just moved this entire application from the Firebase RTDB to Firestore, and I only needed to change the service files and a couple of functions and it works perfectly.

Now that we are ready let’s dive into event.service.ts and create the functions that are going to communicate with Firebase.

The first thing we are going to do here is to import Firestore and create a variable to hold our eventList collection so that we can use it in all of our functions.

import { Injectable } from '@angular/core';
import * as firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';

@Injectable({
  providedIn: 'root',
})
export class EventService {
  public eventListRef: firebase.firestore.CollectionReference;
  constructor() {
    firebase.auth().onAuthStateChanged(user => {
      if (user) {
        this.eventListRef = firebase
          .firestore()
          .collection(`/userProfile/${user.uid}/eventList`);
      }
    });
  }
}

Now it is time to start thinking what kind of features we need in our service. We want our users to be able to:

  • Create new events.
  • Get the full list of events.
  • Get a particular event from the list.

Knowing that, let’s start with creating a new event:

createEvent(
  eventName: string,
  eventDate: string,
  eventPrice: number,
  eventCost: number
): Promise<firebase.firestore.DocumentReference> {
  return this.eventListRef.add({
    name: eventName,
    date: eventDate,
    price: eventPrice * 1,
    cost: eventCost * 1,
    revenue: eventCost * -1,
  });
}

A couple of things to note:

  • We are using .add() on the eventList sub-collection because we want firebase to append every new document to this list, and to auto-generate a random ID, so we know there aren’t going to be two objects with the same ID.

  • We are adding the name, date, ticket price and cost of the event (Mostly because in the next chapter I am going to use them for transactions + real-time updates on revenue per event.)

After we have the function that is going to create our events, we’ll need one more to list them.

getEventList(): firebase.firestore.CollectionReference {
  return this.eventListRef;
}

And one for receiving an event’s ID and returning that event:

getEventDetail(eventId: string): firebase.firestore.DocumentReference {
  return this.eventListRef.doc(eventId);
}

This will be it for now (we will return to this page for some cool stuff later), we are going to start playing with our events to see what we can do.

Setting up the HomePage

Now since I have not given much thought to the app’s UI, I am going to create two buttons on the HomePage to take me to the event create or list pages.

Go to home.page.html and add the buttons inside the <ion-content> tag:

<ion-content padding>
  <ion-button expand="block" color="primary" routerLink="/event-create">
    Create a new Event
  </ion-button>

  <ion-button expand="block" color="primary" routerLink="/event-list">
    See your events
  </ion-button>
</ion-content>

Creating new events

Now that we have that, it is time to create the event part of the app, we are going to start with adding a new event, for that go to event-create.page.html and create a few inputs to save the event’s name, date, ticket price and costs:

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/home"></ion-back-button>
    </ion-buttons>
    <ion-title>New Event</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding>
  <ion-item>
    <ion-label position="stacked">Event Name</ion-label>
    <ion-input
      [(ngModel)]="eventName"
      type="text"
      placeholder="What's your event's name?"
    >
    </ion-input>
  </ion-item>

  <ion-item>
    <ion-label position="stacked">Price</ion-label>
    <ion-input
      [(ngModel)]="eventPrice"
      type="number"
      placeholder="How much will guests pay?"
    >
    </ion-input>
  </ion-item>

  <ion-item>
    <ion-label position="stacked">Cost</ion-label>
    <ion-input
      [(ngModel)]="eventCost"
      type="number"
      placeholder="How much are you spending?"
    >
    </ion-input>
  </ion-item>

  <ion-item>
    <ion-label>Event Date</ion-label>
    <ion-datetime
      [(ngModel)]="eventDate"
      displayFormat="D MMM, YY"
      pickerFormat="DD MMM YYYY"
      min="2017"
      max="2020-12-31"
    >
    </ion-datetime>
  </ion-item>

  <ion-button
    expand="block"
    (click)="createEvent(eventName, eventDate, eventPrice, eventCost)"
  >
    Create Event
  </ion-button>
</ion-content>

Nothing we have not seen in previous examples, we are using a few inputs to get the data we need, and then creating a createEvent() function and passing it those values so we can use them later.

After you finish doing this, go to event-create.page.ts first we will need to import our event service and the angular router.

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { EventService } from '../../services/event/event.service';

@Component({
  selector: 'app-event-create',
  templateUrl: './event-create.page.html',
  styleUrls: ['./event-create.page.scss'],
})
export class EventCreatePage implements OnInit {
  constructor(private router: Router, private eventService: EventService) {}

  ngOnInit() {}
}

After doing that we will create our createEvent function, it will send the data to the createEvent() function we already declared in our EventService.

createEvent(
  eventName: string,
  eventDate: string,
  eventPrice: number,
  eventCost: number
): void {
  if (
    eventName === undefined ||
    eventDate === undefined ||
    eventPrice === undefined ||
    eventCost === undefined
  ) {
    return;
  }
  this.eventService
    .createEvent(eventName, eventDate, eventPrice, eventCost)
    .then(() => {
      this.router.navigateByUrl('');
    });
}

Nothing too crazy, we are sending the data to our EventService, and as soon as we create the event, we are using this.router.navigateByUrl(''); to go back a page to the HomePage.

We use this.router.navigateByUrl(''); because it is a good practice to redirect the user after a form submits, this way we avoid the user clicking multiple times the submit button and create several entries.

Listing the events

Now that we can create events, we need a way to see our events, so let’s go to the event-list.page.html file and create a list of your events:

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-back-button></ion-back-button>
    </ion-buttons>
    <ion-title>Your Events</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding>
  <ion-list>
    <ion-list-header> <ion-label>Your next events</ion-label> </ion-list-header>
    <ion-item
      tappable
      *ngFor="let event of eventList"
      routerLink="/event-detail/{{ event.id }}"
    >
      <ion-label>
        <h2>{{event?.name}}</h2>
        <p>Ticket: <strong>${{event?.price}}</strong></p>
        <p>Date: <strong>{{event?.date}}</strong></p>
      </ion-label>
    </ion-item>
  </ion-list>
</ion-content>
  • We are creating an item that will repeat itself for every event we have in our database.

  • We are showing necessary event data like the name, the ticket price for guests and the event date.

  • When users tap on the event, they are going to be taken to the event’s detail page.

  • We send the event id in the routerLink="/event-detail/{{ event.id }}" callso we can pull the specific ID from Firebase.

Now we need the logic to implement all of that so go into event-list.page.ts and first import and declare everything you’ll need:

import { Component, OnInit } from '@angular/core';
import { EventService } from '../../services/event/event.service';

@Component({
  selector: 'app-event-list',
  templateUrl: './event-list.page.html',
  styleUrls: ['./event-list.page.scss'],
})
export class EventListPage implements OnInit {
  public eventList: Array<any>;
  constructor(private eventService: EventService) {}

  ngOnInit() {}
}
  • We are importing EventService to call the service’s functions.

  • We are declaring a variable called eventList to hold our list of events.

Now we need to get that list of events from Firebase.

ngOnInit() {
  this.eventService
    .getEventList()
    .get()
    .then(eventListSnapshot => {
      this.eventList = [];
      eventListSnapshot.forEach(snap => {
        this.eventList.push({
          id: snap.id,
          name: snap.data().name,
          price: snap.data().price,
          date: snap.data().date,
        });
        return false;
      });
    });
}

We have done this before. We are:

  • Calling the getEventList() method from our service.

  • Pushing every record into our eventList array.

Now the first part of the HTML will work, it is going to show users a list of their events in the app.

The Event Detail Page

Now that we are sending the user to the event detail page, and we are passing the event ID we have everything we need to show the event details.

Go into event-detail.page.ts, you’re going to import and initialize:

import { Component, OnInit } from '@angular/core';
import { EventService } from '../../services/event/event.service';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-event-detail',
  templateUrl: './event-detail.page.html',
  styleUrls: ['./event-detail.page.scss'],
})
export class EventDetailPage implements OnInit {
  public currentEvent: any = {};

  constructor(
    private eventService: EventService,
    private route: ActivatedRoute,
  ) {}

  ngOnInit() {}
}

We are importing ActivatedRoute because that is the module that handles navigation parameters (like the event ID we sent to this page)

And we are creating currentEvent to hold our event’s information, and now it is time to pull that information from our Firebase database:

ngOnInit() {
  const eventId: string = this.route.snapshot.paramMap.get('id');
  this.eventService
    .getEventDetail(eventId)
    .get()
    .then(eventSnapshot => {
      this.currentEvent = eventSnapshot.data();
      this.currentEvent.id = eventSnapshot.id;
    });
}

Now that we have our event’s information available, we can display it in our event-detail.html file:

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-back-button></ion-back-button>
    </ion-buttons>
    <ion-title>{{currentEvent?.name}}</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding>
  <ion-card>
    <ion-card-content>
      <p>Ticket: <strong>${{currentEvent?.price}}</strong></p>
      <p>Date: <strong>{{currentEvent?.date}}</strong></p>
    </ion-card-content>
  </ion-card>
</ion-content>

We are not spending much time or brain power with the UI of this page because it is going to get heavily modified in our next chapter.

And that’s it if you felt this was a bit easier, that was the point, I wanted to create a small introduction to lists because we are going to get a little deeper in our next chapter and I wanted you to be ready for it.

Next Steps

In the next lesson we’re going to cover Firebase Transactions.

Next Lesson: Handling Firebase Transactions.