This is the third article in a series of articles where Martina(who's a fellow GDE in Angular & Web Tech) and I create KittyGram: A super-minimal Instagram Clone that allows uploading only Cat Photos. Please find more information regarding the project overview in my previous article. And you can find more information about what we've implemented so far in this article by Martina.
For the scope of this article, we'll mainly implement features to:
- Store the image URL and metadata related to it in Firebase Cloud Firestore.
- Read this data as a list to populate our feed.
Wanna save time and jump directly to a specific section of this article? I've got you covered:
- Setting up Firebase Cloud Firestore
- Storing post data in the Firestore
- Reading data for the feed from Firestore
- Next Steps
- Closing Notes
Setting up Firebase Cloud Firestore
In the Firebase Console
So first up, we need to set up Cloud Firestore on the Firebase Console. Doing that is pretty straightforward. Just follow the steps below:
Develop > Database > Create Database(in the Header) > Start in Production Mode > Select a Cloud Firestore Location > Done
Also, our Cloud Firestore Database doesn't really allow any
writes at the moment. If you navigate to the
Rules tab, the current rules are set like:
As you can see in the rule,
allow read, write: if false; would mean:
allow readas in, reads are allowed.
write: if false;i.e. writes are not allowed.
Now, we do need to allow reads to the data even by unauthenticated users. But we only intend to allow writes by authenticated users. So we can update the rules to this:
Once you update the rules, it would look something like this:
In our Angular App
In order to interact with the Cloud Firestore, we also need to have access to the relevant APIs from the
@angular/fire package. All these APIs are exposed as a part of the
AngularFirestoreModule. So we'll have to add that as well, to the
exports array of our
Now that we've added that to the
exports array, we'll be able to use these exposed APIs in the declarables(Components, Pipes, and Directives) and services registered on our
Storing post data in the Firestore
Perfect! Now that we have the Firestore base set-up, let's now work on leveraging it to store data in our Firebase Cloud Firestore.
The things that we might want to store for a particular post are:
- Name and Avatar URL of the User posting the Cat photo.
- Photo URL and Description from the Post.
- Last Updated timestamp.
- Number of likes(that we're calling purrs).
- An Id(that would be generated by Cloud Firestore) that we can refer to this post by.
We'd generally create a
interface for this for type-safety reasons. So let's create it in the
I'm sure you've noticed that the
id field is optional. That's because we won't have the
id while creating a
post. But we will have it while reading it.
You might have also noticed the
doc optional field which is of type
any. We'll get back to it when we're implementing lazy loading in the next article.
Alright, next up we need a way to connect to the Firebase Cloud Firestore and then store the data in there. To do that, we can use the
AngularFirestore service exposed by the
AngularFirestoreModule. We can create a service to encapsulate this business logic. Let's call it
DatabaseService once we inject the
AngularFirestore service as a dependency, we can then:
- Create a new collection by calling the
collectionmethod on it. It returns an instance of type
- Retrieve items in the collection. This collection also has a
valueChangesmethod on. Calling it would return an
Observableof the whole collection.
As you can also see here, we have a
userPosts$ Observable that we're exposing for the
FeedComponent to show us the feed.
This service also needs:
- A method to add a post to the collection.
- A method to update an existing post.
userPostsCollection has an
add method that we can call to add a new
UserPost to the collection. Something like this:
Regarding the update, we need it coz we want to update the purrs(likes) on a post once a user clicks on the purr button.
We can call the
doc method on the
user-posts/postId to create a reference to that document in the collection. Doing that will return a reference to that document on which we can call the
update method passing the partial update user post as an argument. Something like this:
Awesome! So we now have the
DatabaseService in place that we can inject as a dependency in the
CreateComponent to store the created post. We can simply call the
addUserPost after creating a
addUserPost method returns
Observable<DocumentReference>, we'll just pipe through the
downloadUrl$ and switch the context using the
Also, as we don't want the
Observable stream to die and we're handling the error using the
catchError operator already, we'll now return
of(null) from there instead of
That way, we can filter the
Observable stream to just pass through if the value is not null. That's what the usage of
filter operator in there does. The rest is pretty much the same. After making all these changes, our
CreateComponent class would look something like this:
UserPost the purrs(likes) are set to 0. Rest all the fields are pretty straightforward. Please comment down below if you have any doubts/confusion regarding it.
Let's quickly test if our application in the current state works as expected or not.
And, it does. As you can see, initially on the console for both the Storage and Database section, there isn't anything. But as soon as you upload a post from our Angular App, the image gets stored in the storage bucket and the data is stored in the Cloud Firestore.
Purrfect! Now that we have a way to store a
UserPost. Let's now figure out a way to read this stored data.
Reading data for the feed from Firestore
Reading data is actually pretty straightforward. We already have the
userPosts$ property(which is a collection of user posts), exposed as an
Observable from the
DatabaseService. We can inject the
DatabaseService as a dependency in our
FeedComponent and leverage this
userPosts$ to populate our feed. Something like this:
For the feed, we also need to have a child presentational/dumb Component that could take the
UserPost as a
@Input and emit the click on the purr(like) button as an
@Output event. We'll just create one and call it
Also, since this is a presentational/dumb Component let's use the
OnPush in here.
It would look something like this:
The template for this Component would just be a regular Card, something like this:
With that in place, we can now, in the
feed.component.html, just unwrap the
Observable using an
async pipe, iterate through the unwrapped array using the
*ngFor directive, and render the
app-feed-item component passing it one
userPost at a time.
We will also listen to the
@Output event from the
FeedItemComponent by calling the
handlePurrClick method on it.
Awesome, we now have everything in place. We can add items to our database and we can also list them out in the feed and see if a logged-in user could update purrs on a cat post by liking it.
Awesome! Our App looks and behaves as expected. BUT, there's an issue with it at the moment.
userPosts$ is a
valueChanges call on our
AngularFirestoreCollection we'll basically get everything that we have in our Cloud Firestore every time there's a change.
This isn't such a big deal for just one or two or maybe even 10 posts. But what if our Cloud Firestore has 1M records(Who knows? It might). At the moment, these 1M records would be downloaded on the initial load of the App. And that's crazy, right? It would just kill our App's load time. So, what do we do?
Well, remember this optional
doc property of type
any that we had in our
UserPost model. That's something that could be used to potentially paginate our feed and request for new data as the user scrolls through and reached the end of the current batch of data.
That's exactly what we'll be implementing as a part of the next article. So stay tuned!
And that brings us to the end of this article. Thanks for sticking around. I hope you liked it.
A big shout-out to Martina Kraus for proofreading this and collaborating with me on this project.
I hope this article taught you something new related to Angular and Firebase. If it did, share this article with your friends and peers who are new to Angular and want to achieve something similar.
Please find the GitHub repo here.
As always, if you had any doubts, questions, suggestions or constructive criticism, please comment them down below. Until next time then.