I'm trying to fetch some related data based on the values returned by an observable from Firestore.
getChampionshipResults(minimumCompetionCount: number): Observable<User[]> {
const usersWithResults = this.afs.collection<User>('users')
.valueChanges()
.map(users => {
return users.map(user => {
this.afs.collection<Result>('results', ref => ref
.where('userUid', '==', user.uid)
.orderBy('position', 'asc')
.limit(minimumCompetionCount))
.valueChanges()
.take(1)
.subscribe(results => {
user.results = results;
});
return user;
});
})
.do(el => console.log(el));
return usersWithResults;
}
this gives me the correct results:
output
But I want to do some advanced sorting / filtering on the usersWithResults observable. The problem is that the user.results property is undefined at the time I want to sort it:
getChampionshipResults(minimumCompetionCount: number): Observable<User[]> {
const usersWithResults = this.afs.collection<User>('users')
.valueChanges()
.map(users => {
return users.map(user => {
this.afs.collection<Result>('results', ref => ref
.where('userUid', '==', user.uid)
.orderBy('position', 'asc')
.limit(minimumCompetionCount))
.valueChanges()
.take(1)
.subscribe(results => {
user.results = results;
});
return user;
});
})
.do(el => console.log(el))
// add sorting
.map(users => {
return users.sort((a, b) => {
// this is undefined
console.log(a.results);
// simplified dummy sort
return 1;
});
});
return usersWithResults;
}
I think the issue is coming from the fact that the inner observable has not completed, but I can't find the correct rxjs operator to do what I want.
Any advice would be appreciated.
Thanks.
Related
I have a list of games that I'm able to add to without issue using UseEffect and onSnapshot. I can modify an item in the list without issue, and return one set of results (with the updated data properly displaying). When I try to modify another item (or the item same again), I get this error:
Could not update game: TypeError: undefined is not an object (evaluating '_doc.data().numPlayers') because the results/list of games are null. I'm sure I have something wrong with my code, but I can't figure it out.
Thanks in advance!
Here is my code:
useEffect(() => {
setIsLoading(true)
let results = [];
const unsubscribe = db
.collection('games')
.onSnapshot(
(querySnapshot) => {
querySnapshot.docChanges().forEach(change => {
const id = change.doc.id;
if (change.type === 'added') {
const gameData = change.doc.data();
gameData.id = id;
results.push(gameData);
}
if (change.type === 'modified') {
console.log('Modified game: ', id);
results = results.map(game => {
if (game.id === id) {
return change.doc.data()
}
return game
})
console.log(results)
}
if (change.type === 'removed') {
console.log('Removed game: ', id);
}
});
setIsLoading(false);
setGame(results);
return () => unsubscribe
},
(err) => {
setIsLoading(false);
console.log("Data could not be fetched", err);
}
);
}, []);
I forgot to add the doc ID to the gameData before adding it to the results. I did that in the "added" section, but not in the "modified" section (thinking that it was already included), forgetting that I hadn't added it as an actual field in the database (it just exists as the doc id).
I'm trying to subscribe to a query on firestore but I'm getting an error when I add a filter.
this works just fine
useEffect(() => {
if (dbChats && currentUser?.uid) {
const unsubscribe = dbChats
.orderBy('createdAt')
.limit(100)
.onSnapshot((querySnapshot) => {
const chats = firebaseLooper(querySnapshot);
setChats(chats);
});
return unsubscribe;
}
}, [dbChats]);
but this doesn't
useEffect(() => {
if (dbChats && currentUser?.uid) {
const unsubscribe = dbChats
.where('participants', 'array-contains', currentUser.uid)
.orderBy('createdAt')
.limit(100)
.onSnapshot((querySnapshot) => {
const chats = firebaseLooper(querySnapshot);
setChats(chats);
});
return unsubscribe;
}
}, [dbChats]);
whenever I add
where('participants', 'array-contains', currentUser.uid)
It throws the error
null is not an object (evaluating 'snapshot.forEach')
Note that this also works
dbChats.where('participants', 'array-contains', currentUser.uid).get()
The snapshot is null because you haven't supplied any error handling and the query is throwing an error. Simply supply an error handler as a second param to onSnapshot.
windowsill is correct. Once I properly supplied error handling I received the error from firebase [Error: [firestore/failed-precondition] The query requires an index. You can create it here: https://console.firebase.google.com/...
In my case I needed to add an index.
Change this:
firestore().collection("cities").where("state", "==", "CA")
.onSnapshot((querySnapshot) => {
var cities = [];
querySnapshot.forEach((doc) => {
cities.push(doc.data().name);
});
console.log("Current cities in CA: ", cities.join(", "));
});
To something like this:
firestore().collection('cities').where('state', '==', 'CA')
.onSnapshot({
error: (e) => console.error(e),
next: (querySnapshot) => {
var cities = [];
querySnapshot.forEach((doc) => {
cities.push(doc.data().name);
});
console.log('Current cities in CA: ', cities.join(', '));
}
});
I was following the official firebase tutorial on promises (https://www.youtube.com/watch?v=7IkUgCLr5oA) but in my case, I cannot make it work.
const promise = userRef.push({text:text});
const promise2 = promise.then((snapshot) => {
res.status(200).json({message: 'ok!'});
});
promise2.catch(error => {
res.status(500).json({message: 'error'});
});
What am I doing wrong? Each then() should have its response in case something goes wrong, but that is why I am writing the promise2 catch.
Just add the return before sending the response.
const promise = userRef.push({text:text});
const promise2 = promise.then((snapshot) => {
return res.status(200).json({message: 'ok!'});
});
promise2.catch(error => {
return res.status(500).json({message: 'error'});
});
Also you can chain the promises as follows:
return userRef.push({text:text})
.then((snapshot) => {
return res.status(200).json({message: 'ok!'});
}).catch(error => {
return res.status(500).json({message: 'error'});
});
I'm working on a simple app, where I need to fetch the data from a specific user. Heres's the code I have in my service/provider
constructor(private afs: AngularFirestore, private afAuth: AngularFireAuth){
this.afAuth.authState.subscribe(
user => {
this.notesCollection = this.afs.collection<Note>('notes', ref =>
ref.orderBy('created', "desc")
.where('trashed', '==', false)
.where('user', '==', user.email));
}
);
}
fetchNotes(): Observable<NoteId[]> {
this.notes = this.notesCollection.snapshotChanges().map(actions =>{
return actions.map(a => {
const data = a.payload.doc.data() as Note;
const id = a.payload.doc.id;
return {id, ...data};
})
});
return this.notes;
}
But, when I call the fetchNotes method from my page, I get an error basically saying that the notes variable is undefined. I know it's due to the user observable executing in the constructor, but, I don't know any other way to get the user info and making the queries, so, if you could help to find another way to do this, I'd really appreciate it. Thank you!
I'd get this working like so:
notes: Observable<NoteId[]>;
constructor(private afs: AngularFirestore, private afAuth: AngularFireAuth){
this.notes = this.afAuth.authState.switchMap(user => {
if (user) {
return this.afs.collection<Note>('notes', ref =>
ref.orderBy('created', "desc")
.where('trashed', '==', false)
.where('user', '==', user.email)).snapshotChanges()
} else {
return of([]);
}
}).map(actions =>{
return actions.map(a => {
const data = a.payload.doc.data() as Note;
const id = a.payload.doc.id;
return {id, ...data};
})
});
}
So the key concepts are A) embrace Observables (fetchNotes() could get you in trouble, if you need the current value for some reason use a BehaviorSubject) B) switchMap the authState C) map right on the assigns
I am trying to get route params and then get data from the service
this.route.params
.switchMap(params => this.service.getData(params['id']))
.subscribe(value => this.value = value,
error => console.log(error));
This works fine, until first error. After the error this line doesn't calls no more params => this.noteService.GetParams(params['id']).
I can write something like this, but i think there is a better way
this.route.params.subscribe(
params => {
this.service.getData(params['id']).subscribe(
result => console.log(result),
error => console.log(error))
});
My service
public getData(id): Observable<any> {
return this.http.get('api/data/' + id)
.map(data => data.json())
.catch(error => Observable.throw(error));
}
Update
This answer helped me a lot to understand what is going on.
When I call Observable.throw(error) subscription to route params stops with an error. So instead of throwing error I just need to return empty observable.
my.component.ts
this.route.params
.switchMap(params => this.service.GetData(params['id']))
.subscribe(result => {
if (result) this.data = result;
else console.log('error');
});
my.service.ts
public GetData(id): Observable<any> {
let url = 'api/data' + id;
return this.http.get(url)
.map(data => data.json())
.catch(error => Observable.of(null));
}
I'm building a github users application right now and had the same problem.
Here is a solution that works for me:
users.service.ts
public getByUsername(username: string): Observable<any[]> {
return this.http
.get(`${this.url}/${username}`)
.map((res: Response) => res.json());
}
user.component.ts
ngOnInit() {
this.sub = this.route.params
.flatMap((v: any, index: number) => {
return this.usersService.getByUsername(v.name);
})
.subscribe(data => this.user = data);
}
So, basically the flatMap operator does the trick.
Here is link to another question,
helping me to figure out how things work with chaining RxJS Observables