I'm experiencing an issue with Firestore's onSnapshot listener not unsubscribing when a page is refreshed or closed. I'm using Angular and the Firestore SDK, and I've implemented the unsubscribe logic within the beforeunload event. However, the unsubscribe function provided by Firestore doesn't always seem to execute, as evidenced by inconsistent request logs in the Firestore emulator and chrome's network monitoring. And even when it does I see no "ListenHandler onClose" in the emulator logs. The unsubscribe not working is also evident by the multiplying requests fire on a single change in a firestore document on each refresh. This issue is not reproduced when calling the unsubscribe upon calling unsubscribe directly (upon user logout per example)
@HostListener('window:beforeunload', ['$event'])
unloadHandler(event: Event) {
this.unsubscribeFromFirestore();
}
ngOnDestroy() {
this.unsubscribeFromFirestore();
}
unsubscribeFromFirestore() {
if (this.unsubscribe) {
console.log('Unsubscribing from Firestore');
this.unsubscribe(); // Directly call the unsubscribe function
}
}
getMainTabNodes(userId: string): Observable<MenuNode[]> {
const nodesRef = collection(this.firestore, 'nodes');
let requestQuery: Query = query(nodesRef,
or(
where('access.public', '==', true),
where('access.owner', '==', userId),
where(`access.users.${userId}`, 'array-contains', 'read'),
)
);
return new Observable<MenuNode[]>(subscriber => {
this.unsubscribe = onSnapshot(requestQuery, {
next: snapshot => {
const nodes = snapshot.docs.map(doc => doc.data() as MenuNode);
subscriber.next(nodes);
},
error: error => {
subscriber.error(error);
}
});
return () => {
console.log('Cleaning up nodes subscription');
this.unsubscribe();
};
})
}
Question: How can I ensure that the Firestore onSnapshot unsubscribe function is reliably called during a page refresh or close? Are there any best practices or alternative approaches to handle this scenario effectively?
The error seems to be related to firebase snapshot listener create many LIST requests
My emulator logs looks exactly like that after few refreshes and one change to a document.
This is what my firebase console usage chart looks like :
And this is what my emulator logs looks like after 1 change in a document.
Edit: This is my usage chart after replicating the code almost 1 to 1 on stackblitz (the connections dies out eventually) :
According to the MDN docs:
The beforeunload event is not reliably fired, especially on mobile platforms.
Especially: you can't reliably make network calls in this situation anymore.
But this whole unsubscribe logic should not be needed here anyway. When the page unloads, the listeners automatically unregister - as the server won't be able to deliver messages to them anymore.
I can't reproduce the behavior you're describing, and in fact see the database behaving as I expect it to - which means my last paragraph above is accurate: you don't need to handle the unload scenario yourself (and in fact: can't reliably do that anyway).
I created a minimal repro consisting of two pages:
I've then let the writer page run for a while, and varied the number tabs with the listener page open.
This is what I see in the usage charts for the past hour in the Firebase console:
The blue line shows the read count per minute, the blue number is the total number of reads in the hour. Similarly, the orange line shows the number of reads per minute, while the orange number is the total number of reads in the hour.
The annotations in there:
In my repro above, Firestore works as I expected: once a client disappears the server (with a bit of lag) stops any listeners it may have had for that client, and thus stops charging reads for those listeners.
Since the behavior you see is differently, there must be a different explanation for it than the closed tabs still keeping their listeners open on the server. For example, do you have the data panel of the Firestore console open anywhere? Keep in mind: reads by the Firestore console are also measured and charged.
If you still can't explain the behavior you have, see if you can reproduce what I showed above (based on the minimal code samples I shared), and then from there see if you can get that minimal repro back to your situation.
I also wrote a separate article based on this answer, where I add some (even?) more context: Do you need to unregister Firestore listeners when a web page is closed?