3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 6

Creating a Messages Service to Interact with Firestore

Creating and displaying messages

EXTENDED

Creating a Messages Service to Interact with Firestore

Once again, our usual approach is to start with the “main” feature of the application. Although we will eventually have things like login and account creation, the main purpose of our application is creating and displaying messages. Let’s start there.

NOTE: Just a reminder that for all of the application stuff that we have already covered throughout the course, I am going to go very light on the details/directions in this application build. This is to give you a chance to apply what you have learned, highlight areas you might need to focus on more, and hopefully give you a better shot at creating applications independently once we are done with this course. However, there will be a lot of new things introduced in this module, and we will still talk about those in detail

Create an Interface for messages

Create a new file at src/app/shared/interfaces/message.ts and add the following:

export interface Message {
  author: string;
  content: string;
  created: string;
}

Creating the Message Service

Create a new file at src/app/shared/data-access/message.service.ts and add the following:

import { Injectable } from '@angular/core';
import {
  collection,
  collectionData,
  Firestore,
  limit,
  orderBy,
  query,
} from '@angular/fire/firestore';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Message } from '../interfaces/message';

@Injectable({
  providedIn: 'root',
})
export class MessageService {
  constructor(private firestore: Firestore) {}

  getMessages() {
    const messagesCollection = query(
      collection(this.firestore, 'messages'),
      orderBy('created', 'desc'),
      limit(50)
    );
    return collectionData(messagesCollection, { idField: 'id' }).pipe(
      map((messages) => [...messages].reverse())
    ) as Observable<Message[]>;
  }
}

We are already dealing with integrating with Firestore now. What we are doing here is using methods provided by the @angular/fire package in order to retrieve documents from a specific collection in our Firestore database. Let’s walk through what is happening step-by-step here:

  getMessages() {
    const messagesCollection = query(
      collection(this.firestore, 'messages'),
      orderBy('created', 'desc'),
      limit(50)
    );
    return collectionData(messagesCollection, { idField: 'id' }).pipe(
      map((messages) => [...messages].reverse())
    ) as Observable<Message[]>;
  }

The goal here is that we want this to return an observable stream of the last 50 messages that have been added to our database. First, we could just do this to get a reference to a specific collection:

const messagesCollection = collection(this.firestore, 'messages')

This will give us a reference to the messages collection of documents in our Firestore database (we haven’t actually added any collections or documents to the database, we will do that shortly).

But, we don’t just want to get everything from that collection, we want to query for specific documents, which is why we return a query instead:

    const messagesCollection = query(
      collection(this.firestore, 'messages'),
      orderBy('created', 'desc'),
      limit(50)
    );

Now, we will only return 50 documents which are ordered by their created date in descending order. Creating this query won’t actually give us the stream od data we want though, we need to supply it to the collectionData method and return that:

    return collectionData(messagesCollection, { idField: 'id' }).pipe(
      map((messages) => [...messages].reverse())
    ) as Observable<Message[]>;

We also supply a couple of configurations for this - we want to return the unique id from our documents so we supply the idField configuration, and we also want the messages in reverse order (the latest should be at the bottom of the screen) so we map and reverse them.

Now we can just call our getMessages method and we will get a stream of the latest 50 messages from our Firestore database that will automatically update in real time whenever a new message is added to the database!

Displaying Messages

Now that we have a way to get messages, we need a way to display them. We should be getting back to familiar territory now, as we will be creating a simple dumb component to display the data we get back from the database.

Create a new file at home/ui/message-list.component.ts and add the following:

import { CommonModule } from '@angular/common';
import {
  ChangeDetectionStrategy,
  Component,
  Input,
  NgModule,
} from '@angular/core';
import { IonicModule } from '@ionic/angular';
import { Message } from '../../shared/interfaces/message';

@Component({
  selector: 'app-message-list',
  template: `
    <ion-list lines="none">
      <ion-item *ngFor="let message of messages; trackBy: trackByFn">
        <ion-avatar class="animate-in-primary">
          <img
            *ngIf="message.author"
            src="https://avatars.dicebear.com/api/bottts/{{
              message.author.split('@')[0]
            }}.svg"
          />
        </ion-avatar>
        <div class="chat-message animate-in-secondary">
          <ion-note>{{ message.author }}</ion-note>
          <p>{{ message.content }}</p>
        </div>
      </ion-item>
    </ion-list>
  `,
  styles: [
    `
      .chat-message,
      ion-avatar {
        filter: drop-shadow(2px 4px 6px var(--ion-color-primary-shade));
      }

      .chat-message {
        width: 100%;
        padding: 10px;
        border-radius: 10px;
        margin: 10px 0;
        background-color: var(--ion-color-light);

        p {
          margin: 5px 0;
        }
      }
    `,
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MessageListComponent {
  @Input() messages!: Message[];

  trackByFn(index: number, message: Message) {
    return message.created;
  }
}

@NgModule({
  declarations: [MessageListComponent],
  exports: [MessageListComponent],
  imports: [CommonModule, IonicModule],
})
export class MessageListComponentModule {}
EXTENDED
Key

Thanks for checking out the preview of this lesson!

You do not have the appropriate membership to view the full lesson. If you would like full access to this module you can view membership options (or log in if you are already have an appropriate membership).