How to go through dynamic Firebase Realtime Database? - javascript

I am making a web app that accesses a Firebase Real-Time Database. The database will be of a dynamic size as new data is entered.
example:
Each element holds an irrelevant object.
I've hard-coded the size and am for-looping through it (js):
for (let i = 0; i < DB_SIZE; i++) {
let ref = firebase.database().ref(i.toString());
let snapshot = await ref.once('value');
let data = snapshot.val();
}
The data achieved is the object associated with the element.
This works when I know the exact, unchanging size of the database, but when a new object is added as a new element in the database, my loop wouldn't access it, since the database size is now larger.
How can I access the data in this dynamic database using Firebase's syntax? It's possible that try...catch will be involved, but I'm not so sure.

The Firebase DataSnapshot has a built-in forEach method precisely for this purpose. So you can load the entire parent node, and then loop over it client-side with:
const snapshot = await firebase.database().ref().once('value');
snapshot.forEach((child) => {
let data = child.val();
});
By the way, Firebase recommends against using sequential, numeric indexes as the keys. For an explanation why, see Best Practices: Arrays in Firebase.

Related

Firestore get document where subcollection exist

It this possible to do? After some research, I couldn't find a way to do it.
My "workaround" is to get all subCollection documents first, then retrieve their parent keys and retrieve again. But I believe there is a better way to do it.
let subDocs = await firestoreDb.collectionGroup('sub_collections').get()
let parentDocKeys = {};
subDocs.docs.forEach(async (element) => {
parentDocKeys[element.ref.parent.parent.id] = 1;
});
let result = await firestoreDb.collection('parentCollection').where(firestore.FieldPath.documentId(), 'in', Object.keys(parentDocKeys)).get();
return res.send(result.docs.map(x=>x.data()));
Firestore queries can only filter documents based on data in the document itself. There is no way to check data in any other documents, neither in the same collection, nor in other (sub)collections.
This means that it is not possible to retrieve only documents for which a subcollection exists without changing the data in the parent document. For example, if you update a field (say hasSubcollectionBla) in the parent document each time you add/remove to the subcollection, you can filter on the value of that field.
What you do with this is making the writing of data more complex, so that the read/query becomes easier and more scalable. This is a common trade-off when using NoSQL databases, such as Firestore.

Firestore JS - incrementing within transactions

I have some code that looks like the following:
export const createTable = async (data) => {
const doc = db.collection("tables").doc();
const ref = db
.collection("tables")
.where("userId", "==", data.userId)
.orderBy("number", "desc").limit(1);
db.runTransaction(async transaction => {
const query = await transaction.get(ref);
let number = 1;
if (!query.empty) {
const snapshot = query.docs[0];
const data = snapshot.data();
const id = snapshot.id;
number = data.number + 1;
}
data = {number, ...data};
transaction.set(doc, data);
});
Basically I have a tables collection and each table has an auto generated number like #1, #2, #3
When creating new tables, I want to fetch the latest table number and create the new table with that number incremented by 1.
I wanted to wrap it in a transaction so that if a table created while running the transaction, it will restart so that I don't end up with duplicate numbers.
However, this errors out on the .get(), and from googling I've read that Firestore can't monitor a whole collection within transactions, but instead it requires a specific doc passed to it. Which I obviously can't do because I need to monitor for new docs created in that collection, not changes in a particular doc.
If so, what's the correct way to implement this?
The Firestore transaction API for client apps requires that you get() each individual document that you want to participate in the transaction. So, if you have a query whose results you want to transact with, you will need to:
Perform the query (outside of the transaction)
Collect document references for each document in the result set
In the transaction, get() them all individually.
You will be limited to 500 documents per transaction.
If you want to dynamically look for new documents to modify, you will probably much better off implementing that on the backend using a Firestore trigger in Cloud Functions to automatically handle each new document as they are created, without requiring any code on the client.
Because you're updating just one document, you probably don't need to use transactions for incrementing values.
You can use Firestore Increment to achieve this.
Here is an example taken from here:
const db = firebase.firestore();
const increment = firebase.firestore.FieldValue.increment(1);
// Document reference
const storyRef = db.collection('stories').doc('hello-world');
// Update read count
storyRef.update({ reads: increment });
This is the easiest way to increment values in Firestore.

How to query the list in a better way using AngularFireDatabase?

"#angular/fire": "^5.2.3",
I am using AngularFireDatabase to manipulate data in firebase. I need to retrieve and update the table below in Firebase. I am currently doing it in the below method. Is there any way which I can use it to query better? Because, I am downloading the entire table and then uploading it again. I don't want to download the entire list of submissions instead just want to push an object to the submission array directly. Thanks in advance.
// Get form submissions for a specific form
getFormSubmissions(key: string) {
this.userSubmissionsList = this.db.list('/form-submissions', ref => ref.orderByChild('formId').equalTo(key));
return this.userSubmissionsList;
}
getSingleFormForSubmission(key: string) {
this.submission = this.db.object('/form-submissions/' + key);
return this.submission;
}
this.formService.getFormSubmissions(this.formId).snapshotChanges().subscribe(response => {
let key = null;
response.forEach(item => {
const a: any = item.payload.toJSON();
key = item.key;
});
this.formService.getSingleFormForSubmission(key).valueChanges().subscribe(resp => {
if (resp.submission === undefined) { resp.submission = []; }
this.formSubs = resp;
});
});
Pushing the data be like:
this.formSubs.submission.push(data);
this.formService.submitUserValues(this.formSubs);
You're storing a JavaScript array in Firebase, which unfortunately means it becomes hard to manipulate. To add an item to an array, you must know how many items already exist in that array, which requires that you first read it.
This is one of the many reasons why the Firebase documentation recommends against using arrays, but instead uses so-called push IDs to add items to lists. From that documentation:
Use the push() method to append data to a list in multiuser applications. The push() method generates a unique key every time a new child is added to the specified Firebase reference. By using these auto-generated keys for each new element in the list, several clients can add children to the same location at the same time without write conflicts. The unique key generated by push() is based on a timestamp, so list items are automatically ordered chronologically.
I also recommend checking out this old-but-still-very-true blog post Best Practices: Arrays in Firebase.

How not to set data alphabetically sorted in Firebase in JavaScript?

I am trying to set an object in my Firebase data. While adding in database its added alphabetically sorted.
Doing something like,
const dbCon = props.db.database().ref('/pages/page');
dbCon.set({dataForFirebase});
It's setting data in the database sorted already in https://console.firebase.google.com/project/someProject/data.
How do I prevent this? Or how do get the data as the object has the order?
To fetch the data I am doing:
static async getIndividualCompoenetDetails () {
const app = await firebase.database().ref('pages/page');
const snapshot = await app.once('value');
const { dataForFirebase } = snapshot.val();
return dataForFirebase;
}
You have no control over how the database stores the data. The fact that you add the data in a certain order has no influence on how it's stored. If the order is important, store it under a key or with a property that records this order, e.g. by calling ref.push() to add new child nodes.
The Firebase console displays the data lexicographically ordered by key. This is not configurable, but sounds like a useful feature. I'd recommend filing a feature request for it, although I'll admit that I haven't heard the request frequently enough that I think it's going to be implemented.
Whenever you call snapshot.val() the data from the snapshot is converted to a JSON object, which is by definition not ordered.

Match value of array from database object in Firebase Cloud Functions

This is my first app project using Google Cloud Functions & Firebase. I'm trying to find away to get a single value of the array that I'm returning and compare it to a set variable and if it matches, update another child's value in that same account.
My App users can add records to the database under their login/user_id that is stored in the database. I'm trying to get a list of the "RecordName" that is a child under that login/user_id that every user has stored in their account.
So basically every "RecordName" in the entire database. When I want to run specials for those records, I need to match the name of that record to the name of the record I have on special and if there is a match, update another child value under that user's account ("special" = true.). This way, when they load their app next time, I have it highlighting that record so they know it's on special.
When I use..
const ref = admin.database().ref(`/store`);
...with the following code...
ref.on('value', function(snapshot) {
// puts ALL items of the object into array using function ..
console.log(snapshotToArray(snapshot));
});
... and the function...
function snapshotToArray(snapshot) {
var returnArr = [];
snapshot.forEach(function(childSnapshot) {
var item = childSnapshot.val();
item.key = childSnapshot.key;
returnArr.push(item);
});
return returnArr;
};
... I get the entire array just as it is in the database:
-store
-{ones_users_id}
-recordname: value1
-special: false
-{anothers_users_id}
-recordname: value2
-special: false
ect. ect.
If my record on special is called, "Newbie Record", what would be the best way to take out every individual value for the key: "recordname" from the array, compare each one to var = "Newbie Record" and if they match, update the value of the key: "special" to be true?
I'm new to JSON and NodeJS, I've been searching on here for answers and can't find exactly what I'm looking for. Your feedback would be very helpful.
It sounds like you're looking to query your database for nodes that have "recordname": "Newbie Record" and update them.
An easy way to do this:
const ref = admin.database().ref(`/store`);
const query = ref.orderByChild("recordname").equalTo("Newbie Record");
query.once('value', function(snapshot) {
snapshot.forEach(function(child) {
child.ref.update({ special: true })
});
});
Main differences with your code:
We now use a query to read just the nodes that we want to modify.
We now use once() to read the data only once.
We loop over the children of the snapshot, since a query may result in multiple nodes.
We use the reference of each child and then update its special property.
I recommend reading a bit more about Firebase queries in the documentation.

Categories