Reputation: 52837
I am running into a firestore issue that I hope someone can help me out with.
I have an active document snapshot listener that seems to be breaking sorting behavior, and I'm not sure why.
In the constructor of my component, I initialize the document snapshot listener once:
this.listen = this.fs.collection('users').doc('0EwcWqMVp9j0PtNXFDyO').ref.onSnapshot(t => {
console.log(t)
})
Whenever I click the Sort button it initializes a new collection query which is sorted by first name.
async onSort() {
if (this.first) {
this.first();
}
this.sort = this.sort === 'asc'? 'desc' : 'asc';
alert(this.sort)
let arr = []
let lastDoc = null
let queryRef = this.fs.firestore.collection('users').orderBy('firstName', this.sort as any).limit(20);
this.users$ = this._users$.asObservable()
// initialize a blank list
this._users$.next([])
// subscribe to user list snapshot changes
const users1: { users: any, last: any } = await new Promise((resolve, reject) => {
this.first = queryRef.onSnapshot((doc) => {
console.log("Current data: ", doc);
const users = doc.docs.map(t => {
return {
id: t.id,
...t.data() as any
}
})
console.log(users)
resolve({
users: users,
last: doc.docs[doc.docs.length -1]
})
});
})
this._users$.next(this._users$.value.concat(users1.users))
}
However, this is not working as expected. The sort is returning one record (there should be 20 records, based on the limit).
Interestingly, unsubscribing from the document snapshot, fixes the sorting. 20 records are being returned and the sorting is behaving correctly.
Can someone please explain what I am doing wrong?
Here is a minimal Stackblitz that reproduces the issue.
[Edit]
Getting closer to a solution and an understanding... Thanks to @ZackReam for the explanation and both @Robin and @Zack for the the canonical answers.
It seems that this._firestore.firestore.collection('users').orderBy('firstName', this.sort as any).limit(20).onSnapshot(...)
is indeed emitting twice - once for the active document listener (which has 1 record in the cache), and a second time for the sorted collection (which is not in the cache since switchMap
will unsubscribe everytime that the sort
is executed).
We can see that briefly in the functional demo that Zack posted - it flashes with one record corresponding to the document listener, then an instant later, the list is populated with the sorted collection.
The more active listeners you have, the weirder the flashes of results will be. Is this by design? seems flawed to me...
Intuitively, I would expect snapshotChanges()
or valueChanges()
from the this._firestore.firestore.collection('users').orderBy('firstName', this.sort as any).limit(20)
query to return 20 results (the first 20 when sorted), independent of any other document listeners. It's counter-intuitive that the first snapshot from this query is the initial active document from the document query - which should have nothing to do with the sort query.
Upvotes: 4
Views: 755
Reputation: 3076
I played around with your sample data in a simplified environment, and I think I see what's happening.
Here is my playground, just update AppModule
to point at your Firebase project.
When you call onSort()
, you are making a fresh subscription to queryRef.onSnapshot
. This callback is firing twice:
In the playground, make sure Document is subscribed, then subscribe to the Sorted Collection. In the console, you'll see both times it fires:
Unfortunately, since you are wrapping this listener in a Promise, you are only ever getting the first fire, and thus only getting the single record that was in the cache.
It seems that the local cache is populated based on what subscriptions to onSnapshot
are currently open. So in your case, you have one listener to snapshots of a single record, thus that's the only thing in your cache.
In the playground, try hitting Log Cache with various subscription states. For instance, while only
Document Subscribed
is true, the cache only has the one record. While both subscriptions are active, you'll see around 20 records in the cache.If you unsubscribe from everything and hit Log Cache, the cache is empty.
It turns out that if there is no data to return from the cache in step 1 above, it skips that step entirely.
In the playground, make sure Document is UNsubscribed, then subscribe to the Sorted Collection. In the console, you'll see it fired only once:
This happens to actually work with your Promise, since the first and only firing contains the data you were hoping for.
This comment in the firebase-js-sdk
repo describes this behavior as expected, and the reasoning why.
The Promise paired with .onSnapshot
is the major thing that's messing up the data flow, since .onSnapshot
is expected to fire more than once. You could switch to .get
instead and it would technically work.
However, as Robin pointed out, focusing on a more reactive RxJS approach taking advantage of the @angular/fire
functionality) would go a long way.
Upvotes: 4
Reputation: 19278
I can't find the problem with your code, because you reached your firebase quota. To me, it seems your sorting is a bit too complex and introduces racing problems. I think you can make it way less complex by using RxJS. Something like this would work: https://stackblitz.com/edit/angular-ivy-cwthvf?file=src/app/app.component.ts
I couldn't really test it because you reached your quota.
Upvotes: 1