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 {}