3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 7

Adding Live Chat Messages

EXTENDED

Adding Live Chat Messages

Currently, the only way we can add new messages to our Firestore database is to do it manually through the emulator interface. In this lesson, we are going to provide a way to do it through the application.

Update the MessageService

Add the following method to the MessageService:

import { Observable, defer, merge } from 'rxjs';
import { collection, query, orderBy, limit, addDoc } from 'firebase/firestore';
  private addMessage(message: string) {
    const newMessage: Message = {
      author: '[email protected]',
      content: message,
      created: Date.now().toString(),
    };

    const messagesCollection = collection(this.firestore, 'messages');
    return defer(() => addDoc(messagesCollection, newMessage));
  }

An important thing to note here as that we are just using [email protected] as the author since we don’t have a login system/user authentication yet — we will need to come back to this method later to use the user’s actual email.

Just like with the getMessages example, we create a reference to the collection we are interested in:

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

But this time, we use the addDoc method from firebase to add the new document we just created to the messages collection. Note that we do not have to supply an id for our documents as this is created automatically by Firestore.

Also notice that we are wrapping the call to addDoc in defer. This is because addDoc returns a Promise. We want to convert this into an Observable. We can use from from RxJS to do this, but the benefit of using defer is that addDoc will be executed lazily.

The general idea is that Promises are executed eagerly/immediately. Even if you are not using the result, the code in a Promise (e.g. adding a document to Firebase in this case) will be executed immediately. This is different to an observable which is lazy — an observable won’t execute any code until we subscribe to it.

You might think that since from can convert a Promise to an Observable that it would make it lazy. However, if we use from the Promise will still be executed immediately, even if we don’t subscribe to it. Often this won’t cause any problems, but just to be safe, we will generally always use defer so that it will function in the way we expect: no code gets executed until we subscribe to the observable.

Now we are going to add our source for handling adding messages.

Add the following source to the MessageService:

  add$ = new Subject<Message['content']>();

We’re using that TypeScript trick again here so that we can use the type of whatever content is in our Message interface for our source.

Now we are going to make use of that concept we talked about in the advanced state management module.

Add the following reducer:

  constructor() {
    // reducers
    const nextState$ = merge(
      this.messages$.pipe(map((messages) => ({ messages }))),
      this.add$.pipe(
        exhaustMap((message) => this.addMessage(message)),
        ignoreElements(),
        catchError((error) => of({ error }))
      )
    );

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

Remember how we aren’t actually interested in setting the values from the add$ source in our state. The whole point of the add$ source is just to trigger the addMessage method. It is the addMessage method that will cause data to be added to Firestore, which will then also automatically cause our messages$ source to emit with the new data, and that is how the data gets set in our state.

We call the this.addMessage method by switching to it in the stream, and we use ignoreElements so that our stream does not actually emit any values — we are not interested in them. We are however interested in errors, which ignoreElements will not prevent. If we get an error we want to set that error into our state, but we don’t want the error to break our stream. So, we use

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