I want to access to a collection inside another collection in a for loop.
Is it possible? I'm getting an error at the 4th line Error getting documents TypeError: cookUser is not a function
var mealsOnline = [];
return db.collection('users').get().then(function (snapshot) {
snapshot.forEach(cookUser => {
cookUser.collection('meals').get().then(function (snapshot2) {
snapshot2.forEach(meal => {
if (meal.data().portion > 0) {
var mealObject = meal.data();
mealObject.id = meal.id;
mealObject.address = cookUser.data().address;
mealObject.cookName = cookUser.data().displayName;
mealsOnline.push(mealObject);
}
});
});
});
return Promise.all(mealsOnline);
}).catch(err => {
console.log('Error getting documents', err);
});
With the forEach() method, your cookUser object is a QueryDocumentSnapshot.
As detailed in the documentation (link above), "QueryDocumentSnapshot offers the same API surface as a DocumentSnapshot". Therefore you should use the ref abstract type of a DocumentSnapshot, as follows:
snapshot.forEach(cookUser => {
cookUser.ref.collection('meals').get().then(snapshot2 => {
.....
})
})
https://firebase.google.com/docs/reference/js/firebase.firestore.QuerySnapshot#forEach
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 use some data from from Firestore. before it used to work, now in Vuetify I keep getting 'PENDING' if I try to access the $data.users
export default {
data() {
return {
users: [],
};
},
created() {
db.collection('users').get().then((snapshot) => {
snapshot.forEach((doc) => {
const user = doc.data();
user.id = doc.id;
this.users = user;
console.log(user.documents.selfie.url); // Here the log return the value correctly
});
});
},
methods: {
imageUrl(user) {
console.log(user.documents.selfie.url); // Here the log return "Pending";
},
Inside the template I run a v-for (user, index) in users :key='index'
ERROR:
Uncaught (in promise) TypeError: Cannot read property 'selfie' of undefined
It's difficult to be 100% sure without reproducing your problem, but I think the problem comes from the fact that the Promise returned by the asynchronous get() method is not yet fulfilled when you call the imageUrl() method. This is why you get the pending value.
One possibility to solve that is to check as follows:
methods: {
imageUrl(user) {
if (user) {
console.log(user.documents.selfie.url);
} else {
//...
}
},
Also, is seems you want to populate the users Array with the docs from the users collection. You should do as follows:
created() {
db.collection('users').get().then((snapshot) => {
let usersArray = [];
snapshot.forEach((doc) => {
const user = doc.data();
user.id = doc.id;
usersArray.push(user);
console.log(user.documents.selfie.url); // Here the log return the value correctly
});
this.users = usersArray;
});
},
With your current code you assign the last user in the loop, not the list of users.
I tried everything , I have this cloud function (that otherwise works) :
exports.contentServer = functions.https.onRequest((request, response) => {
admin.database().ref('/list/' + "abc").once('value').then(function(snapshot) {
console.log(snapshot.val() );
return null;
}).catch(function(error) {
console.log("Error getting document:", error);
return response.send(error);
});
});
or also this :
admin.database().ref('/list').once('value').then(function(snapshot) {
var event = snapshot.val();
app.tell('Result: '+event);
});
and this :
exports.contentServer = functions.https.onRequest((request, response) => {
var db = admin.database();
db.ref("list/abc").once("value").then(snap => {
var store = snap.val().description;
return store;
}).then(() => {
var store = snap.val().description;
return store;
}).then(snap => {
var store = snap.val().description;
return store;
}).catch(err => {
console.log(err);
response.send("error occurred");
});
});
and always get back the error :
"Could not handle the request"
Or I get error on deploy that :
Each then() should return a value or throw
I have a collection called list, inside I have a document named "abc".
Is there something I have to include ? something I have to setup in Firebase to make it work ? anything basic nobody write on the docs ?
Modified following the comments above explaining the OP uses Firestore and not the Realtime Database
You should do as follows. You have to wait that the promise returned by the get() method resolves before sending back the response. For this you need to use the then() method, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
exports.contentServer = functions.https.onRequest((request, response) => {
admin.firestore().collection('list').doc('abc').get()
.then(docSnapshot => {
console.log(docSnapshot.data());
return response.send(docSnapshot.data()); // or any other value, like return response.send( {result: "success"} );
})
.catch(error => {
console.log("Error getting document:", error);
return response.status(500).send(error);
});
});
As written in the comments above, I would suggest that you watch the 3 videos about "JavaScript Promises" from the Firebase video series: https://firebase.google.com/docs/functions/video-series/
Try this
Updated. Return the response inside then() as what #Renaud Tarnec pointed out.
Using realtime database
exports.contentServer = functions.https.onRequest((request, response) => {
var database = admin.database();
database.ref('list').child('abc').once("value", snapshot => {
const data = snapshot.val();
return response.send(data);
}).catch(error => {
return response.status(500).send(error);
});
});
If you are using firestore.
exports.contentServer = functions.https.onRequest((request, response) => {
const firestore = admin.firestore();
firestore.collection("list").doc('abc').get().then(doc => {
console.log(doc.data());
return response.send(doc.data());
}).catch(error => {
return response.status(500).send(error);
});
});
Important: Don't forget to terminate the request by calling response.redirect(), response.send(), or responses.end() so you can avoid excessive charges from functions that run for too long
I am converting the use of Firebase Storage to use the Angularfire2 library (currently v5.0.0-rc.5-next) which means I am now using observables rather than promises.
How can I catch error such as storage/object-not-found and react accordingly?
This is currently my code but I cannot add a catch to it as some examples I found.
const avatarRef = this.afStorage.ref('${userId}/avatar/${this.avatarThumbnail}${user.avatar}');
avatarRef.getDownloadURL()
.take(1)
.subscribe((avatarUrl) => {
resolve(avatarUrl);
});
At its most basic, observers take an error callback to receive any unhandled errors in an observable stream. getDownloadURL() returns Observable that is why you need to subscribe. If you get an error (file not found or other) you will invoke code from error callback only.
avatarRef.getDownloadURL()
.take(1)
.subscribe((avatarUrl) => {
// Do something with avatarUrl here
console.log(avatarUrl);
}, (error) => {
// Handle error here
// Show popup with errors or just console.error
console.error(error);
});
Also I suggest you to read articles about error handling using RxJS and difference between Observable and Promise: link1, link2
The following solution work for me
startUpload(file) {
// The storage path
const path = `image${new Date().getTime()}.jpg`;
// Reference to storage bucket
const ref = this.storage.ref(path);
let image = 'data:image/jpeg;base64,' + file;
// The main task
return new Promise((resolve, reject) => {
const upload = ref.putString(image, 'data_url');
const sub = upload.snapshotChanges().pipe(
finalize(async () => {
try {
const photoURL = await ref.getDownloadURL().toPromise();
this.message.senderUid = this.currentUser.uid;
this.message.receiverUid = this.selectedUser.uid;
this.message.text = this.inputText && this.inputText !== '' ? this.inputText : 'File';
this.message.senderName = this.currentUser.name;
this.message.chatId = this.chatId;
this.message.file = photoURL;
this.firebaseService.insertMessage(this.message)
.then(() => {
this.inputText = '';
this.message.file = null;
this.scrollToBottomOnInit();
});
resolve({photoURL})
} catch (err) {
this.inputText = '';
this.message.file = null;
reject(err)
}
sub.unsubscribe()
})
).subscribe((data) => {
console.log('storage: ', data)
})
})
}
Source: https://github.com/angular/angularfire/issues/1736#issuecomment-515798352
I am using Ionic2 with AngularFire2.
I am also making use of a rxjs Observable. I have the following code:
findChatsForUid(uid: string): Observable<any[]> {
return this.af.database.list('/chat/', {
query: {
orderByChild: 'negativtimestamp'
}
}).map(items => {
const filtered = items.filter(
item => (item.memberId1 === uid || item.memberId2 === uid)
);
return filtered;
});
}
and
deleteChatsAndMessagesForUid(uid: string): Promise<any> {
return new Promise<any>((resolve) => {
let promiseArray: Promise<any>[] = [];
this.findChatsForUid(uid).map(items => {
return items;
}).forEach((chatItems) => {
for (let i: number = 0; i < chatItems.length; i++) {
promiseArray.push(this.deleteChat(chatItems[i], true));
}
Promise.all(promiseArray).then(() => {
resolve(true);
});
});
});
}
In the second function, you can see I retrieve the Observable from the first, and the loop through each item using the forEach function.
My problem is, because this is an Observable, there is always a handle to the object. So when I do the following:
deleteChatsAndMessagesForUid(uid).then(() => {
user.delete().then(() => {
...
}
}
It results in the following error because the deleted user is still trying to observe the Observable.
Error: Uncaught (in promise): Error: permission_denied at /chat:
Client doesn't have permission to access the desired data. Error:
permission_denied at /chat: Client doesn't have permission to access
the desired data.
Question
Is there a way to retrieve the data, without still being attached to the Observable? So that I am free to delete the associated user? Or is there a better way to handle this?
Thanks
It sounds like you want to unsubsribe from the list observable after the first emitted list.
You can use the first operator to complete the list observable after the first emitted list. This will result in automatic unsubscription and the listeners will be removed from the internal Firebase ref.
import 'rxjs/add/operator/first';
findChatsForUid(uid: string): Observable<any[]> {
return this.af.database
.list('/chat/', {
query: {
orderByChild: 'negativtimestamp'
}
})
.first()
.map(items => {
const filtered = items.filter(
item => (item.memberId1 === uid || item.memberId2 === uid)
);
return filtered;
});
}