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