Firebase + NGXS, the perfect couple
Easily integrate Firebase with NGXS to keep all your data synced in the store

A few years ago Firebase was a game-changer for web-app development. Designed for high performance and ease-of-use, developer’s productivity was boosted and millions of apps were built since then.
Later on, NGXS brought to the Angular ecosystem a new reactive state management library. With simplicity and code ergonomics as its core focus, it helped thousands of developers to make the dreaded state management problem go away.
But how can we bring them together to take things to the next level?
NGXS recently introduced a plugin called @ngxs-labs/firestore-plugin
which provides a simple yet flexible API to connect an @Action
to a Firestore query, and its query results to the store, allowing you to easily keep all your app data in a single place, making things much easier to select and combine data from different origins (Firestore, REST APIs, etc) and displaying it in your components.
In this tutorial we'll go over how the plugin works and a few examples of how to use it.
How does it work?
Let's say you have a @State
, and you want to fetch items from a Firestore collection into the state. Using firestore-plugin
all you need to do is:
- Connect an
@Action
to a Firestore query - Listen for query results in the
StreamEmmitted
Action handler and update the state accordingly.

That's it. That's all you need. Now let's take a closer look how to do this and how it works under the hood.
Connecting an Action to a Firestore query
The plugin provides the NgxsFirestoreConnect
service, which takes care of connecting an Action
with an observable Firestore query.
this.ngxsFirestoreConnect.connect(MyAction, {
to: () => this.firestore.collection$()
})
Here, connect
method takes the Action
and { to: }
param where we pass a function that returns the query results as an observable.
The plugin also includes the NgxsFirestore<T>
abstract class, which we can extend from to quickly setup a service to connect with Firestore and perform common operations such as get items, create, update and delete.
We can write our query using AngularFirebase's service or use the NgxsFirestore<T>
utility abstract class.
export abstract class NgxsFirestore<T> {
protected abstract path: string;
doc$(id: string): Observable<T>
collection$(query: queryFn): Observable<T[]>
create$(value: T): Observable<void>
upsert$(value: T): Observable<void>
//...
}
We can easily extend from it, and we just need to define the path of the collection we'll query to. The service provides a simple API to perform common CRUD operations such as (doc$, collection$, update$, create$, upsert$, delete$)
@Injectable({
providedIn: 'root'
})
export class MyFirestoreService extends NgxsFirestore<Race> {
protected path = {firestore collection path};
}
It's important to notice that the query will not get executed until you dispatch MyAction
. Once the query is executed, NgxsFirestoreConnect
creates a subscription that will be kept alive until you explicitly Disconnect
from it. This means that all changes that occur on the Firestore DB will be emitted.
The place to set up this connection is the ngxsOnInit
lifecycle hook of our @State
.
Here's the complete example:
@State({
//...
})
@Injectable()
export class MyState implements NgxsOnInit {
constructor(
private ngxsFirestoreConnect: NgxsFirestoreConnect,
private firestore: AngularFirestore,
){ }
ngxsOnInit(){
this.ngxsFirestoreConnect.connect(MyAction, {
to: () => this.firestore.collection$()
})
}
}
So far, we established a connection between an Action
and a Firestore query.
Next, we'll see how to grab those results as they are emitted, and update the state.
Updating store when query results are emitted
Once connection is setup, and connected Action
executed, the plugin will dispatch a new action every time the query emits a result. In order to listen to each emission we use the StreamEmitted
action helper.
@Action(StreamEmitted(MyAction))
StreamEmitted
is a function that takes the connected Action
as param, and creates the hook for each value the query emits.
To complete the @Action
handler definition, we declare the parameters, which are the StateContext
and the StreamEmitted
payload. The Emitted
type contains the connected Action
and the query result.
To read more about actions on NGXS check the docs
type Emitted<A, T> = {
action: A,
payload: T
}
Since Emitted
is generic, we can type it Emitted<MyAction, Item[]>
to add type safety in the action.
The complete code would look like this:
@Action(StreamEmitted(MyAction))
myActionEmmited(ctx: StateContext<MyStateModel>, emitted :Emitted<MyAction, Item[]>){
ctx.patchState({ items: emmited.payload })
}
Firestore plugin in action
Now that we understand how the plugin works, let's go over a complete implementation of the @ngxs-labs/firestore-plugin
.
Before going through these steps you'll need to add@ngxs/store
and@angular/fire
to your app, which is out of the scope of this post.
The following example is a basic app that uses Firestore as the backend, and performs common operations such as read items, create, update and delete. We'll implement each of these operations and discuss them as we go through them.
The complete example can be found in stackblitz
First, install the library:
npm install @ngxs-labs/firestore-plugin
Next, we'll add it to the AppModule
@NgModule({
//...
imports: [
//...
// here goes all other Ngxs and AngularFire imports
NgxsFirestoreModule.forRoot()
]
})
export class AppModule {
}
Next, we create our @State
import {
NgxsFirestoreConnect,
Emitted,
StreamEmitted,
} from '@ngxs-labs/firestore-plugin';
//..
export interface Race {
id: string;
title: string;
description: string;
name: string;
}
export interface RacesStateModel {
races: Race[];
}
@State<RacesStateModel>({
name: 'races',
defaults: {
races: []
}
})
@Injectable()
export class RacesState implements NgxsOnInit {
@Selector() static races(state: RacesStateModel) {
return state.races;
}
constructor(private racesFS: RacesFirestore, private ngxsFirestoreConnect: NgxsFirestoreConnect) {}
ngxsOnInit(ctx: StateContext<RacesStateModel>) {
this.ngxsFirestoreConnect.connect(RacesActions.GetAll, {
to: () => this.racesFS.collection$()
});
}
@Action(StreamEmitted(RacesActions.GetAll))
getAllEmitted(ctx: StateContext<RacesStateModel>, { action, payload }: Emitted<RacesActions.Get, Race[]>) {
ctx.setState(patch({ races: payload }));
}
}
The firestore service
import { NgxsFirestore } from '@ngxs-labs/firestore-plugin';
//...
@Injectable({
providedIn: 'root'
})
export class RacesFirestore extends NgxsFirestore<Race> {
protected path = 'races';
}
And our component
//...
<button class="btn btn-primary"
(click)="getAll()">Get All</button>
//...
<div *ngFor="let race of races$ | async"
class="card mr-1 mb-1 d-inline-flex"
style="width: 12rem;">
<div class="card-body">
<h5 class="card-title">{{ race.id }}</h5>
<p class="card-text">{{ race.name }}</p>
<p class="card-text">{{ race.description }}</p>
<button (click)="update(race)"
class="mr-1">Update</button>
<button (click)="delete(race.id)">Delete</button>
</div>
</div>
@Component({
//...
})
export class ListComponent implements OnInit, OnDestroy {
races$ = this.store.select(RacesState.races);
constructor(private store: Store) {}
getAll() {
this.store.dispatch(new RacesActions.GetAll());
}
}
Let's recap what we have so far, we connected a query to get all items and list them.
When we click the "Get All" button, the query will be executed, getting all items in the collection. Every change on Firestore DB will be immediately streamed to the component.
Here's how it would look:

Now, let's add a "Create" button and a counter of all the items
//...
<button class="btn btn-primary mr-1"
(click)="create()">Create</button>
//...
<div>
<h5>Total: {{ total$ | async }}</h5>
</div>
//...
total$ = this.races$.pipe(map((races) => races.length));
//...
create() {
this.store.dispatch(new RacesActions.Create({
id: 'test-id',
name: 'Test',
title: 'Test Title',
description: 'Test description',
}));
}
We add the create action to the state
export namespace RacesActions {
// ...
export class Create {
public static readonly type = '[Races] Create';
constructor(public payload: RacesActionsPayloads.Create) {}
}
}
//...
@Action(RacesActions.Create)
create({ patchState, dispatch }: StateContext<RacesStateModel>, { payload }: RacesActions.Create) {
return this.racesFS.create$(payload.id, payload);
}
When we hit "Create", count goes up, and the item gets immediately streamed to the component.
Notice that all of this happened without us needing to re-fetch any data. Since we connected the query to the store, every emission is streamed automatically to the component

Now let's take a look at some other cool features.
Track all active connections
The plugin comes with a @Selector
to track all active connections in the app. This becomes very helpful for debugging and monitoring performance and how many connections are open on a given moment.
import { ngxsFirectoreConnections } from '@ngxs-labs/firestore-plugin';
//...
ngxsFirestoreState$ = this.store.select(ngxsFirectoreConnections);
The selector outputs all active connections and emissions as we can see in the following image.

Connecting with single-document based query
Let's say we want to connect to specific documents from a collection. We might retrieve some document ids initially and we now want to query those documents. Let's take a look at how we can do that. First we'll add a new button to get some specific items from the races collection.
<button [disabled]="gettingSingle$ | async"
class="btn btn-primary"
(click)="get()">Get Single</button>
get() {
const ids = [your ids go here];
ids.forEach((id) => this.store.dispatch(new RacesActions.Get(id)));
}
We add the new action
export namespace RacesActions {
// ...
export class Get {
public static readonly type = '[Races] Get';
constructor(public payload: string) {}
}
}
And setup query connection in the state
//...
ngxsOnInit(ctx: StateContext<RacesStateModel>) {
//...
this.ngxsFirestoreConnect.connect(RacesActions.Get, {
to: (action) => this.racesFS.doc$(action.payload),
trackBy: (action) => action.payload
});
}
@Action(StreamEmitted(RacesActions.Get))
getEmitted(ctx: StateContext<RacesStateModel>, { action, payload }: Emitted<RacesActions.Get, Race>) {
if (payload) {
ctx.setState(
patch<RacesStateModel>({
races: iif(
(races) => !!races.find((race) => race.id === payload.id),
updateItem((race) => race.id === payload.id, patch(payload)),
insertItem(payload)
)
})
);
}
}
Let's take a closer look how the connection is set up
this.ngxsFirestoreConnect.connect(RacesActions.Get, {
to: (action) => this.racesFS.doc$(action.payload),
trackBy: (action) => action.payload
});
As we saw before, to
takes a function that returns an observable Firestore query. In this case we can see the function takes the action
as param, this allows us to send the id
as the action payload and use it to query for a specific doc.
Another important thing here is trackBy
. This param allows us to tell the plugin how to track connection. If the trackBy id matches an existing connection, the plugin will not create a new subscription (because there's already an active one), otherwise it will start a new one.

StreamConnected and StreamDisconnected
In this tutorial we saw how StreamEmitted
action helper allows us to hook into every change that occurs in a connected query and update the state as we need to. But this is not the only hook the plugin supports.
We can also hook into the StreamConnected
and StreamDisconnected
events.
StreamConnected
will dispatch on first emission only andStreamDisconnected
when we explicitly disconnect from the stream. Both hooks allow us to respond to the origin action (with its payload, if present).
@Action(StreamConnected(RacesActions.Get))
getConnected(ctx: StateContext<RacesStateModel>, { action }: Connected<RacesActions.Get>) {
console.log('[RacesActions.Get] Connected');
}
@Action(StreamDisconnected(RacesActions.Get))
getDisconnected(ctx: StateContext<RacesStateModel>, { action }: Disconnected<RacesActions.Get>) {
console.log('[RacesActions.Get] Disconnected');
}
Configure when the action finishes
When we set up the "connection" in the @State
, there is another property we can configure. connectedActionFinishesOn
lets us define when the action will actually complete.
As we mentioned before, when we set up the connection it doesn't execute it immediately. The query is executed once the connected action is dispatched.
With connectedActionFinishesOn
we can control when the triggering Action completes, the options are FirstEmit
and StreamCompleted
. FirstEmit
is the default and is pretty much self explanatory, once the firestore query emits the first result, the action will complete. On the other hand, StreamCompleted
will cause the triggering action to complete when the connected stream completes (disconnecting a stream comes later in the article).
FirstEmit
is very helpful if we want to display a "Loading..." message or disable a button while data is being fetched.
Let's see an example of how we can achieve this using another NGXS plugin @ngxs-labs/actions-executing
.
First we set up the query connection:
this.ngxsFirestoreConnect.connect(RacesActions.GetAll, {
to: () => this.racesFS.collection$(),
connectedActionFinishesOn: 'FirstEmit'
});
Next, in our component we define the loading$
observable selecting actionsExecuting
.
import { actionsExecuting } from '@ngxs-labs/actions-executing';
//...
loading$ = this.store.select(actionsExecuting([RacesState.races]))
// we could also use @Select(RacesState.races) races$
races$ = this.store.select(RacesState.races);
this.store.dispatch(new RacesActions.GetAll());
Finally we display Loading... using | async
<div *ngIf="loading$ | async; else loaded">
//...
</div>
<ng-template #loaded>
//...
</ng-template>

Disconnect from stream
So far we've discussed how to connect and update the state. Once we dispatch the action, the stream gets subscribed and emits data as it comes in. But what if we want to stop receiving data from that stream? Well, we can dispatch a Disconnect
action to do that.


// Connect
this.store.dispatch(new RacesActions.Get({id}));
// Disconnect
this.store.dispatch(new Disconnect(new RacesActions.Get({id})));
As we can see, while the action is connected, data will be emitted and displayed in our component. Once we disconnect the action, we will no longer listen to changes, and therefore not reflect the changes in the component.
Note that this connection uses thetrackBy
option, therefore an action with the sameid
can be used to disconnect. Alternatively you could keep a reference to the original action and use it to disconnect.
If we want to connect back to the query, we just need to dispatch the Action
again.
Conclusion
In this article we covered how we can connect Firestore queries with NGXS using the @ngxs-labs/firestore-plugin
. We explained how the plugin works in detail and demonstrated some basic and more advanced examples of how we can use it.
We also showed some nice features the plugin includes such as tracking the active connections and various event hooks.
I hope that you enjoyed this article, and that you try out this plugin for yourself!
Please add your thoughts and feedback in the github project or share your experiences on the NGXS Slack.
Special thanks to Mark Whitfeld for reviewing and helping me with this article.