v19 launch sale ends in ... Get 25% off BUY NOW
3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 6

Creating a Messages Service to Interact with Firestore

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, computed, inject, signal } from '@angular/core';
import { Observable, merge } from 'rxjs';
import { collection, query, orderBy, limit } from 'firebase/firestore';
import { collectionData } from 'rxfire/firestore';
import { map } from 'rxjs/operators';
import { connect } from 'ngxtension/connect';

import { Message } from '../interfaces/message';
import { FIRESTORE } from '../../app.config';

interface MessageState {
  messages: Message[];
  error: string | null;
}

@Injectable({
  providedIn: 'root',
})
export class MessageService {
  private firestore = inject(FIRESTORE);

  // sources
  messages$ = this.getMessages();

  // state
  private state = signal<MessageState>({
    messages: [],
    error: null,
  });

  // selectors
  messages = computed(() => this.state().messages);
  error = computed(() => this.state().error);

  constructor() {
    // reducers
    const nextState$ = merge(
      this.messages$.pipe(map((messages) => ({ messages })))
    );

    connect(this.state).with(nextState$);
  }

  private 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[]>;
  }
}

IMPORTANT: Currently, there is an issue when using rxfire with the moduleResolution option set to bundler (which is the default). If you experience an error when trying to import collectionData update the following field in your tsconfig.json file from:

    "moduleResolution": "bundler",

to:

    "moduleResolution": "node",

Once again we have our basic state management setup, except this time we are using the connect function that we covered in the advanced state management module.

To quickly recap, this is essentially the same idea as our normal reducers that we have been creating:

  constructor() {
    // reducers
    const nextState$ = merge(
      this.messages$.pipe(map((messages) => ({ messages })))
    );

    connect(this.state).with(nextState$);
  }

Except rather than subscribing and calling state.update with the source values, instead we map those values and return an object with whatever values we want to set in the state. In this case, we return { messages } because we want to update the messages property in our state signal with the messages emitted on the messages$ stream.

Notice that we are also already integrating with Firestore now and making use of our injection token by injecting FIRESTORE as this.firestore:

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).