Handling realtime data storage in Angular using Firebase Cloud Firestore
This article will give you a step by step walkthrough on implementing a realtime database in your Angular Apps using Firebase Cloud Firestore.

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 write
s at the moment. If you navigate to the Rules
tab, the current rules are set like:

rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
As you can see in the rule, allow read, write: if false;
would mean:
allow read
as 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:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if true;
allow write: if request.auth.uid != null;
}
}
}
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 AppFirebaseModule
.
...
import { AngularFirestoreModule } from '@angular/fire/firestore';
...
@NgModule({
...
exports: [
...
AngularFirestoreModule,
...
],
})
export class AppFirebaseModule {}
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 AppModule
.
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 model
interface
for this for type-safety reasons. So let's create it in the app/models
folder:
export interface UserPost {
description: string;
id?: string;
lastUpdated: number;
photoUrl: string;
purrs: number;
userAvatar: string;
userName: string;
doc?: any;
}
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
.
In our DatabaseService
once we inject the AngularFirestore
service as a dependency, we can then:
- Create a new collection by calling the
collection
method on it. It returns an instance of typeAngularFirestoreCollection
. - Retrieve items in the collection. This collection also has a
valueChanges
method on. Calling it would return anObservable
of the whole collection.
import {
AngularFirestore,
AngularFirestoreCollection,
DocumentReference,
} from '@angular/fire/firestore';
import { Injectable } from '@angular/core';
import { Observable, from } from 'rxjs';
import { UserPost } from './../../models/user-post.model';
@Injectable({
providedIn: 'root',
})
export class DatabaseService {
private userPostsCollection: AngularFirestoreCollection<UserPost>;
userPosts$: Observable<UserPost[]>;
constructor(private afs: AngularFirestore) {
this.userPostsCollection = afs.collection<UserPost>('user-posts');
this.userPosts$ = this.userPostsCollection.valueChanges({ idField: 'id' });
}
...
}
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.
The userPostsCollection
has an add
method that we can call to add a new UserPost
to the collection. Something like this:
addUserPost(userPost: UserPost): Observable<DocumentReference> {
return from(this.userPostsCollection.add(userPost));
}
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 AngularFirestore
with 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:
updatePost(userPost: UserPost): Observable<void> {
return from(
this.afs.doc<UserPost>(`user-posts/${userPost.id}`).update({
purrs: ++userPost.purrs,
}),
);
}
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 UserPost
Object.
Since the addUserPost
method returns Observable<DocumentReference>
, we'll just pipe through the downloadUrl$
and switch the context using the switchMap
operator.
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 EMPTY
.
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:
...
import { Observable, of, Subject } from 'rxjs';
import { catchError, filter, switchMap, takeUntil } from 'rxjs/operators';
...
import { AuthService } from '../../services/auth/auth.service';
import { DatabaseService } from './../../services/database/database.service';
...
import { UserPost } from './../../models/user-post.model';
...
@Component({ ... })
export class CreateComponent implements OnInit, OnDestroy {
...
constructor(
...
private readonly databaseService: DatabaseService,
...
) {}
...
postKitty() {
...
downloadUrl$
.pipe(
switchMap((photoUrl: string) => {
const userPost: UserPost = {
userAvatar: this.user.photoURL,
userName: this.user.displayName,
lastUpdated: new Date().getTime(),
photoUrl,
description: this.pictureForm.value.description,
purrs: 0,
};
return this.databaseService.addUserPost(userPost);
}),
catchError((error) => {
this.snackBar.open(`${error.message} 😢`, 'Close', {
duration: 4000,
});
return of(null);
}),
filter((res) => res),
takeUntil(this.destroy$),
)
.subscribe((downloadUrl) => {
this.submitted = false;
this.router.navigate([`/${FEED}`]);
});
}
...
}
Regarding the 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:
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
import { DatabaseService } from './../../services/database/database.service';
import { UserPost } from './../../models/user-post.model';
@Component({ ... })
export class FeedComponent {
userPosts$: Observable<Array<UserPost>> = this.databaseService.userPosts$;
constructor(private readonly databaseService: DatabaseService) {}
handlePurrClick(userPost: UserPost) {
this.databaseService.updatePost(userPost).pipe(take(1)).subscribe();
}
}
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 FeedItemComponent
.
Also, since this is a presentational/dumb Component let's use the ChangeDetectionStrategy
of OnPush
in here.
It would look something like this:
import { ChangeDetectionStrategy, Component, Input, Output, EventEmitter } from '@angular/core';
import { UserPost } from './../../models/user-post.model';
@Component({
selector: 'app-feed-item',
templateUrl: './feed-item.component.html',
styleUrls: ['./feed-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FeedItemComponent {
@Input() userPost: UserPost;
@Output() purrClick: EventEmitter<UserPost> = new EventEmitter<UserPost>();
handlePurr() {
this.purrClick.emit(this.userPost);
}
}
The template for this Component would just be a regular Card, something like this:
<mat-card class="feed-item-card">
<mat-card-header>
<div mat-card-avatar>
<img class="avatar" [src]="userPost.userAvatar" />
</div>
<mat-card-title>{{ userPost.userName }}</mat-card-title>
<mat-card-subtitle>{{ userPost.lastUpdated | date }}</mat-card-subtitle>
</mat-card-header>
<img
mat-card-image
class="preview-image"
[src]="userPost.photoUrl"
alt="Photo of a cute Kitty 😻"
/>
<mat-card-content>
<p>
{{ userPost.description }}
</p>
</mat-card-content>
<mat-card-actions>
<button mat-button (click)="handlePurr()">{{ userPost.purrs }} 😻</button>
</mat-card-actions>
</mat-card>
With that in place, we can now, in the feed.component.html
, just unwrap the userPosts$
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.
<div class="container">
<ng-container *ngIf="userPosts$ | async as userPosts">
<app-feed-item
*ngFor="let userPost of userPosts"
[userPost]="userPost"
(purrClick)="handlePurrClick($event)"
>
</app-feed-item>
</ng-container>
</div>
We will also listen to the purrClick
@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.
Since our 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?
Next Steps
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!
Closing Notes
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.