Module 6.2: Getting Data from Firebase

First, go ahead and add in a few more messages so our database has more than just one.

Just like our “write” database call, we should do our “read” query in the “MessageService”. Open message.service.ts and add in a “read” function. Let’s use a very descriptive name for this function, like “getAllMessages()”.

The “read” function should query the database node “/messages”, the part of the database where where we are storing our messages. It should query it one time only, and then we can play with the value we receive. Side note: there is a very powerful ability of Firebase that deals with observables and subscriptions, where Firebase will automatically send us a new copy of the information any time the table is updated, but that is outside the scope of this walk-through, but definitely something to look into in the future.

Add the below function below sendMessage:

getAllMessages() {
    this.afd.database.ref('/messages').once('value').then( returnedResponse => {
      const allMessagesAsOneGiantObject = returnedResponse.val() ? returnedResponse.val() : {};

      console.log(allMessagesAsOneGiantObject);
    }).catch( err => {
      console.log('err: ', err);
    });
  }

Alright, so we’ve written the function and it shoud work - I hope - but before we can see it in action we need to call the function. The goal is to display all of the messages on the home page, so open home.component.ts and import and declare our MessageService.

import { Component, OnInit } from '@angular/core';

import { MessageService } from '../services/message.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

  constructor(public messageService: MessageService) { }

  ngOnInit() {
  }

}

We’re finally going to use the ngOnInit lifecycle hook! When our component is initialized, we want to call the getAllMessagesfunction! Here in a moment we will learn how to display those messages in the HTML view, but for now, let’s just focus on calling the function. Note: It is VERY important that you put the word “public” in the constructor this time! Every other time we’ve used the word “private”. By declaring messageService with “public”, we will be able to reference messageService in our HTML file! Very important for displaying the messages in said HTML file. Make your ngOnInit look like:

ngOnInit() {
    this.messageService.getAllMessages();
  }

Serve up your web app and look at the home page. In your console, you should see an object, open it up, and you’ll discover all of the posts you’ve made to your backend! So, now all we need to do is take that data and displaying nicely in our html.

Loops in Angular

Let's talk for a second about loops in Angular. They are very commonly used and it’s the same idea as the loops in old fashioned JavaScript. In fact, to use a loop in Angular, nothing really changes in the TypeScript file. The cool feature that Angular provides is the ability to perform loops inside the HTML file. You can still write all the loops you want in the TypeScript file, but why bother? If we’re just writing the loop so we can display something on the screen, why not just write that loop directly in the HTML file?

Similar to “ngIf”, which controls showing/hiding content, loops use “ngFor”. Simply put, “*ngFor” loops over an array. Let’s look at an example:

In our TypeScript file we define an array:

arrayOfPets = ['dog', 'cat', 'bird', 'fish', 'other']

In our HTML file, we loop over the “arrayOfPets” with *ngFor

*ngFor="let pet of arrayOfPets"

We are creating a local variable here named “pet”. The contents of “pet” is the current element of the array.

Remember, the way to pass data from JavaScript (or TypeScript) into visible HTML is with double curly braces. So to reference the current element of the loop, use double curly braces.

<p *ngFor="let pet of arrayOfPets">{{ pet }}</p>

This would loop over and create a <p> tag for every pet in the arrayOfPets. We'll use this *ngFor to display our message information.

Display our data

Let’s use this idea to display our messages on screen. Notice how “ngFor” loops over an array? We received an object from Firebase. In fact, *we always receive an object from Firebase - Firebase even stores the data in JSON format. Let’s use some old fashioned JavaScript to turn our object into an Angular-friendly array. Don’t forget to declare a new global variable. In message.service.ts add:

First, before the constructor() add the following variable:

allMessagesArray = [];

Next, in getAllMessages:

getAllMessages() {
    this.afd.database.ref('/messages').once('value').then( returnedResponse => {
      const allMessagesAsOneGiantObject = returnedResponse.val() ? returnedResponse.val() : {};
      Object.keys(allMessagesAsOneGiantObject).forEach( eachKey => {
        this.allMessagesArray.push({
          key: eachKey,
          title: allMessagesAsOneGiantObject[eachKey]['title'],
          content: allMessagesAsOneGiantObject[eachKey]['content'],
          owner: allMessagesAsOneGiantObject[eachKey]['owner']
        });
      });
      console.log(allMessagesAsOneGiantObject);
    }).catch( err => {
      console.log('err: ', err);
    });
  }

Now, supposedly our array full of messages has been created, now we need to display said array in home’s HTML file. Before we forget,let’s throw some CSS rules into the home.component.css file.

/* Ensure the content is never covered by the footer, and is offset from the header */
.container {
     margin-bottom: 7.5rem;
     margin-top: 2rem;
}

/* Make each post take up less vertical space */
.card-body {
     padding: 0.25rem 1rem;
}
.card-header {
     margin-bottom: 1rem;
}

/* Style the Edit and Delete buttons */
.btn {
     margin-right: 1rem;
     background-color: #343a40;
     border: 0;
     font-weight: bold;
     letter-spacing: 0.75px;
}
.btn:hover {
     color: #343a40;
     background-color: #007bff;
}

Now open home.component.html, we’re going to try to make our messages appear. Similar to what we saw in the example above, let’s try this:

<div class="container">
  <div class="card text-white bg-dark">

    <div class="card-header"><h3>Message Board</h3></div>

   <p *ngFor="let message of allMessagesArray">{{ message }}</p>

  </div>
 </div>

If you load up your app, you'll notice no messages appear in the browser. The reason why is because our HTML file is looking for the array allMessagesArray in the associated TypeScript file, meaning the home.component.ts file - but there’s no such array there! The array is actually in our MessageService! Remember back to Step 5 when we gave you a hint, warning you to use “public” to declare the “messageService” so we could access it in the HTML file? This is why!

Adjust your code to call the array from the message service instead.

<p *ngFor="let message of messageService.allMessagesArray">{{ message }}</p>

Now, we’re getting there. We’ve got 5 weird [object object] things going on, but here’s the issue: HTML doesn’t know how to render a whole object, so it just prints out “Hey, this is an object” instead. We don’t want to display the entire object, we just want to display the title and content properties! Adjust your home.component.html code to reflect the fact that each message is an object. Each message had a property named “key”, “title”, “content”, and “owner”. It should look like below!

<div class="container">
  <div class="card text-white bg-dark">

    <div class="card-header"><h3>Message Board</h3></div>

    <div *ngFor="let message of messageService.allMessagesArray" class="card-body">

      <div class="card text-white bg-secondary mb-3">
        <div class="d-flex justify-content-between">
          <div class="card-body">
            <h5>{{ message.title }}</h5>
            <p class="card-title">{{ message.content }}</p>
          </div>

          <div class="d-flex align-items-center">
            <button class="btn btn-primary">Edit</button>
            <button class="btn btn-primary">Delete</button>
          </div>

        </div>
      </div>

    </div>
  </div>
 </div>

And now look at it in the browser again, it works! We’ve got out messages appearing!

We’re finished with this module now - right? One small thing... Switch to the “create” page and then back to the home view. Do it a few times. And now look at your array. We’ve got duplicates!

Don’t panic, this is an easy fix, the reason this is happening is because every time we load the Home view, it makes a fresh call to get all messages, and then pushes them onto the allMessagesArray, but we never reset the array! It just keeps growing and growing and growing, always adding more to it every time we load the Home view. So, to fix this, reset the allMessagesArray variable to be empty whenever we call the getAllMessages() function. Open message.service.ts and reset our variable. Add this line to the top of your getAllMessages() function above this.afd etc.

this.allMessagesArray = [];

Cool, try it now and you shouldn’t get duplicate messages. We're at the end of a module, so don't forget to do a git commit!

Last updated