firebase firestore adding new document inside a transaction - transaction.add is not a function - firebase

I was assuming that it was possible to do something like:
transaction.add(collectionRef,{
uid: userId,
name: name,
fsTimestamp: firebase.firestore.Timestamp.now(),
});
But apparently it is not:
transaction.add is not a function
The above message is displayed inside the chrome console.
I see that we can use the set method of the transaction to add a new document transactionally. see: https://firebase.google.com/docs/firestore/manage-data/transactions
The thing is if I use set instead of add(which is not supported anyways), the id of the document should be created by me manually, firestore won't create it.
see: https://firebase.google.com/docs/firestore/manage-data/add-data
Do you see any downside of this not having an add method that generates the id for you automatically?
For example, is it possible that the id generated by the firestore itself is somehow optimized considering various concerns including performance?
Which library/method do you use to create your document IDs in react-native while using transaction.set?
Thanks

If you want to generate a unique ID for later use in creating a document in a transaction, all you have to do is use CollectionReference.doc() with no parameters to generate a DocumentReference which you can set() later in a transaction.
(What you're proposing in your answer is way more work for the same effect.)
// Create a reference to a document that doesn't exist yet, it has a random id
const newDocRef = db.collection('coll').doc();
// Then, later in a transaction:
transaction.set(newDocRef, { ... });

after some more digging I found in the source code of the firestore itself the below class/method for id generation:
export class AutoId {
static newId(): string {
// Alphanumeric characters
const chars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let autoId = '';
for (let i = 0; i < 20; i++) {
autoId += chars.charAt(Math.floor(Math.random() * chars.length));
}
assert(autoId.length === 20, 'Invalid auto ID: ' + autoId);
return autoId;
}
}
see: https://github.com/firebase/firebase-js-sdk/blob/73a586c92afe3f39a844b2be86086fddb6877bb7/packages/firestore/src/util/misc.ts#L36
I extracted the method (except the assert statement) and put it inside a method in my code. Then I used the set method of the transaction as below:
generateFirestoreId(){
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let autoId = '';
for (let i = 0; i < 20; i++) {
autoId += chars.charAt(Math.floor(Math.random() * chars.length));
}
//assert(autoId.length === 20, 'Invalid auto ID: ' + autoId);
return autoId;
}
then,
newDocRef = db.collection("PARENTCOLL").doc(PARENTDOCID).collection('SUBCOLL').doc(this.generateFirestoreId());
transaction.set(newDocRef,{
uid: userId,
name: name,
fsTimestamp: firebase.firestore.Timestamp.now(),
});
Since I am using the same algo for the id generation as the firestore itself I feel better.
Hope this helps/guides someone.
Cheers.

Based on the answer from Doug Stevenson, this is how I got it worked with #angular/fire:
// Create a reference to a document and provide it a random id, e.g. by using uuidv4
const newDocRef = this.db.collection('coll').doc(uuidv4()).ref;
// In the transaction:
transaction.set(newDocRef, { ... });

To complete Stefan's answer. For those using Angularfire, earlier to version 5.2 using CollectionReference.doc() results in an error "CollectionReference.doc() requires its first argument to be of type non-empty string".
This workaround worked for me:
const id = this.afs.createId();
const ref = this.afs.collection(this.collectionRef).doc(id);
transaction.set(ref, { ... });
Credit: https://github.com/angular/angularfire/issues/1974#issuecomment-448449448

I'd like to add an answer solving the id problem. There's no need to generate your own ids. The documentReference is updated after the transaction.set() is called, so in order to access the Firestore's id you need to just do the following:
const docRef = collectionRef.doc();
const result = await transaction.set(docRef, input);
const id = docRef.id;

First of all, firestore transaction object has 4 (get,set,update,delete) methods and doesnt has "add" method. However, the "set" method can be used instead.
import { collection,doc,runTransaction } from "firebase/firestore";
On the other hand documentReference must be created for "set" method.
Steps :
1-) collection method create a collectionReference object.
const collectionRef = collection(FirebaseDb,"[colpath]");
2-) doc method create a documentReference object with unique random id for specified collectionReference.
const documentRef = doc(collectionRef);
3-) add operation can be performed with the transaction set method
try {
await runTransaction(FirebaseDb,async (transaction) => {
await transaction.set(documentRef, {
uid: userId,
name: name,
fsTimestamp: firebase.firestore.Timestamp.now(),
});
})
} catch (e) {
console.error("Error : ", e);
}

Related

How can I update map data that's in a array in firebase? (Flutter)

I'm using flutter web and firebase for a project and got stuck on a problem. I'm trying to update a map in an array in firestore.
using this:
var val = [];
val.add({'groupUID': groupUID, 'inviteStatus': status});
var userInviteUID;
await users
.document(uid)
.get()
.then((value) => userInviteUID = value.data['inviteUID']);
await invites
.document(userInviteUID)
.updateData({'invites': FieldValue.arrayUnion(val)});
I got this result:
firestore structure
What I want to do is just change the 1 to a 2 in the map. I thought that it would update since its the same value but it just adds it to the array.
I looked around on stack and saw some ways to do it like copying the entire array and changing it where I need to, then adding it back.
But I wanted to know if there was a way to avoid that by adding some modifications to my code. Also let me know if there's a better structure I should use. Appreciate the help!
UPDATE:
var ref = invites.document(userData.inviteUID);
ref.get().then((value) async {
var invitesList = value.data['invites'];
switch (status) {
case 1:
break;
case 2:
var index;
invitesList.asMap().forEach((key, value) {
if (value['groupUID'] == groupUID) index = key;
});
invitesList.removeAt(index);
await invites
.document(userData.inviteUID)
.updateData({'invites': FieldValue.arrayUnion(invitesList)});
break;
default:
}
So I looked at some print statements and seen that the elements with the matching group uid is removed, but looking at firebase, the array isn't overwritten anything...any ideas?
FINAL UPDATE:
var ref = invites.document(userData.inviteUID);
ref.get().then((value) async {
var invitesList = value.data['invites'];
switch (status) {
case 1:
break;
case 2:
var index;
invitesList.asMap().forEach((key, value) {
if (value['groupUID'] == groupUID) index = key;
});
invitesList.removeAt(index);
await invites
.document(userData.inviteUID)
.setData({'invites': FieldValue.arrayUnion(invitesList)});
break;
default:
}
Fixed it by changing updateData to setData.
I looked around on stack and saw some ways to do it like copying the entire array and changing it where I need to, then adding it back.
That's exactly how you are supposed to modify the contents of arrays in Firestore documents. Firestore doesn't support updating array elements by index.
setData creates a new document if it already doesn't exist but if the document exists, the data will be overwritten. To prevent this from happening, you could use SetOptions(merge: true) if you wish to append the data:
set(someData, SetOptions(merge: true))
You can also use update method and provide it the updated data, the pseudo code could look like this:
List<Map<String, dynamic>> updatedList = [...];
Map<String, dynamic> updatedData = {
'existing_map': updatedList,
};
var collection = FirebaseFirestore.instance.collection('collection');
collection
.doc('doc_id')
.update(updatedData);

Firestore get value of Field.increment after update without reading the document data

Is there a way to retrieve the updated value of a document field updated using firestore.FieldValue.increment without asking for the document?
var countersRef = db.collection('system').doc('counters');
await countersRef.update({
nextOrderCode: firebase.firestore.FieldValue.increment(1)
});
// Get the updated nextOrderCode without asking for the document data?
This is not cost related, but for reliability. For example if I want to create a code that increases for each order, there is no guaranty that if >= 2 orders happen at the same time, will have different codes if I read the incremental value right after the doc update resolves, because if >= 2 writes happen before the first read, then at least 2 docs will have the same code even if the nextOrderCode will have proper advance increment.
Update
Possible now, check other answer.
It's not possible. You will have to read the document after the update if you want to know the value.
If you need to control the value of the number to prevent it from being invalid, you will have to use a transaction instead to make sure that the increment will not write an invalid value. FieldValue.increment() would not be a good choice for this case.
We can do it by using Firestore Transactions, like incremental worked before Field.increment feature:
try {
const orderCodesRef = admin.firestore().doc('system/counters/order/codes');
let orderCode = null;
await admin.firestore().runTransaction(async transaction => {
const orderCodesDoc = await transaction.get(orderCodesRef);
if(!orderCodesDoc.exists) {
throw { reason: 'no-order-codes-doc' };
}
let { next } = orderCodesDoc.data();
orderCode = next++;
transaction.update(orderCodesRef, { next });
});
if(orderCode !== null) {
newOrder.code = orderCode;
const orderRef = await admin.firestore().collection('orders').add(newOrder);
return success({ orderId: orderRef.id });
} else {
return fail('no-order-code-result');
}
} catch(error) {
console.error('commitOrder::ERROR', error);
throw errors.CantWriteDatabase({ error });
}
Had the same question and looks like Firestore Python client
doc_ref.update() returns WriteResult that has transform_results attribute with the updated field value

Firebase Functions onUpdate circular problem

I've this situation with a circular function, having trouble finding a solution.
Have a collection where I have a flag that tells if the data has changed. Also want to log the changes.
export async function landWrite(change, context) {
const newDocument = change.after.exists ? change.after.data() : null
const oldDocument = change.before.data()
const log = {
time: FieldValue.serverTimestamp(),
oldDocument: oldDocument,
newDocument: newDocument
}
const landid = change.after.id
const batch = db.batch()
const updated = newDocument && newDocument.updated === oldDocument.updated
if (!updated) {
const landRef = db.collection('land').doc(landid)
batch.update(landRef, {'updated': true })
}
const logRef = db.collection('land').doc(landid).collection('logs').doc()
batch.set(logRef, log)
return batch.commit()
.then(success => {
return true
})
.catch(error => {
return error
})
}
The problem is that this writes the log twice when the UPDATED flag is false.
But also cannot put the log write in the ELSE statement because the flag can already be UPDATED and a new document update be made so a new log has to be written.
Trigger:
import * as landFunctions from './lands/index'
export const landWrite = functions.firestore
.document('land/{land}')
.onWrite((change, context) => {
return landFunctions.landWrite(change, context)
})
If I understand correctly, the problem here is that the updated flag does not specify which event the update is in response to (as you can't really do this with a boolean). In other words - you may have multiple simultaneous "first-stage" writes to lands, and need a way to disambiguate them.
Here are a few possible options that I would try - from (IMHO) worst to best:
The first option is not very elegant to implement
The first and second options both result in your function being
called twice.
The third option means that your function is only
called once, however you must maintain a separate parallel
document/collection alongside lands.
Option 1
Save some sort of unique identifier in the updated field (e.g. a hash of the stringified JSON event - e.g. hash(JSON.stringify(oldDocument)), or a custom event ID [if you have one]).
Option 2
Try checking the updateMask property of the incoming event, and discard any write events that only affect that property.
Option 3
Store your update status in a different document path/collection (e.g. a landUpdates collection at the same level as your lands collection), and configure your Cloud Function to not trigger on that path. (If you need to, you can always create a second Cloud Function that does trigger on the landUpdates path and add either the same logic or different logic to it.)
Hope this helps!
The main problem here is the inability of differentiating changes that are made by this server function or by a client. Whenever you are in this situation, you should try to explicitly differentiate between them. You can even consider having an extra field like fromServer: true that goes with server's updates and helps the server ignore the related trigger. Having said that, I think I have identified the issue and provided a clear solution below.
This line is misleading:
const updated = newDocument && newDocument.updated === oldDocument.updated
It should be named:
const updateStatusDidNotChange = newDocument && newDocument.updated === oldDocument.updated
I understand that you want the updated flag to be managed by this function, not the client. Let me know if this is not the case.
Therefore, the update field is only changed in this function. Since you want to log only changes made outside of this function, you want to log only when updated did not change.
Here's my attempt at fixing your code in this light:
export async function landWrite(change, context) {
const newDocument = change.after.exists ? change.after.data() : null
const oldDocument = change.before.data()
const updateStatusDidNotChange = newDocument && newDocument.updated === oldDocument.updated
if (!updateStatusDidNotChange) return true; //this was a change made by me, ignore
const batch = db.batch()
if (!oldDocument.updated) {
const landid = change.after.id
const landRef = db.collection('land').doc(landid)
batch.update(landRef, {'updated': true })
}
const log = {
time: FieldValue.serverTimestamp(),
oldDocument: oldDocument,
newDocument: newDocument
}
const logRef = db.collection('land').doc(landid).collection('logs').doc()
batch.set(logRef, log)
return batch.commit()
.then(success => {
return true
})
.catch(error => {
return error
})
}
Edit
I had the exact problem and I had to differentiate changes by the server and the client, and ignore the ones that were from the server. I hope you give my suggestion a try.

Add timestamp in Firestore documents

I'm newbie to Firestore. Firestore docs says...
Important: Unlike "push IDs" in the Firebase Realtime Database, Cloud Firestore auto-generated IDs do not provide any automatic ordering. If you want to be able to order your documents by creation date, you should store a timestamp as a field in the documents.
Reference: https://firebase.google.com/docs/firestore/manage-data/add-data
So do I have to create key name as timestamp in document? Or created is suffice to fulfill above statement from Firestore documentation.
{
"created": 1534183990,
"modified": 1534183990,
"timestamp":1534183990
}
firebase.firestore.FieldValue.serverTimestamp()
Whatever you want to call it is fine afaik. Then you can use orderByChild('created').
I also mostly use firebase.database.ServerValue.TIMESTAMP when setting time
ref.child(key).set({
id: itemId,
content: itemContent,
user: uid,
created: firebase.database.ServerValue.TIMESTAMP
})
Use firestore Timestamp class, firebase.firestore.Timestamp.now().
Since firebase.firestore.FieldValue.serverTimestamp() does not work with add method from firestore. Reference
For Firestore
ref.doc(key).set({
created: firebase.firestore.FieldValue.serverTimestamp()
})
REALTIME SERVER TIMESTAMP USING FIRESTORE
import firebase from "firebase/app";
const someFunctionToUploadProduct = () => {
firebase.firestore().collection("products").add({
name: name,
price : price,
color : color,
weight :weight,
size : size,
createdAt : firebase.firestore.FieldValue.serverTimestamp()
})
.then(function(docRef) {
console.log("Document written with ID: ", docRef.id);
})
.catch(function(error) {
console.error("Error adding document: ", error);
});
}
All you need is to import 'firebase' and then call
firebase.firestore.FieldValue.serverTimestamp() wherever you need it. Be careful with the spelling though, its "serverTimestamp()". In this example it provides the timestamp value to 'createdAt' when uploading to the firestore's product's collection.
That's correct, like most database, Firestore doesn't store creation times. In order to sort objects by time:
Option 1: Create timestamp on client (correctness not guaranteed):
db.collection("messages").doc().set({
....
createdAt: firebase.firestore.Timestamp.now()
})
The big caveat here is that Timestamp.now()uses the local machine time. Therefore, if this is run on a client machine, you have no guarantee the timestamp is accurate. If you're setting this on the server or if guaranteed order isn't so important, it might be fine.
Option 2: Use a timestamp sentinel:
db.collection("messages").doc().set({
....
createdAt: firebase.firestore.FieldValue.serverTimestamp()
})
A timestamp sentinel is a token that tells the firestore server to set the time server side on first write.
If you read the sentinel before it is written (e.g., in a listener) it will be NULL unless you read the document like this:
doc.data({ serverTimestamps: 'estimate' })
Set up your query with something like this:
// quick and dirty way, but uses local machine time
const midnight = new Date(firebase.firestore.Timestamp.now().toDate().setHours(0, 0, 0, 0));
const todaysMessages = firebase
.firestore()
.collection(`users/${user.id}/messages`)
.orderBy('createdAt', 'desc')
.where('createdAt', '>=', midnight);
Note that this query uses the local machine time (Timestamp.now()). If it's really important that your app uses the correct time on the clients, you could utilize this feature of Firebase's Realtime Database:
const serverTimeOffset = (await firebase.database().ref('/.info/serverTimeOffset').once('value')).val();
const midnightServerMilliseconds = new Date(serverTimeOffset + Date.now()).setHours(0, 0, 0, 0);
const midnightServer = new Date(midnightServerMilliseconds);
The documentation isn't suggesting the names of any of your fields. The part you're quoting is just saying two things:
The automatically generated document IDs for Firestore don't have a natural time-based ordering like they did in Realtime Database.
If you want time-based ordering, store a timestamp in the document, and use that to order your queries. (You can call it whatever you want.)
This solution worked for me:
Firestore.instance.collection("collectionName").add({'created': Timestamp.now()});
The result in Cloud Firestore is:
Cloud Firestore Result
Try this one for Swift 4 Timestamp(date: Date())
let docData: [String: Any] = [
"stringExample": "Hello world!",
"booleanExample": true,
"numberExample": 3.14159265,
"dateExample": Timestamp(Date()),
"arrayExample": [5, true, "hello"],
"nullExample": NSNull(),
"objectExample": [
"a": 5,
"b": [
"nested": "foo"
]
]
]
db.collection("data").document("one").setData(docData) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
The way it worked with me, is just taking the timestamp from the snapshot parameter snapshot.updateTime
exports.newUserCreated = functions.firestore.document('users/{userId}').onCreate(async (snapshot, context) => {
console.log('started! v1.7');
const userID = context.params['userId'];
firestore.collection(`users/${userID}/lists`).add({
'created_time': snapshot.updateTime,
'name':'Products I ♥',
}).then(documentReference => {
console.log("initial public list created");
return null;
}).catch(error => {
console.error('Error creating initial list', error);
process.exit(1);
});
});
I am using Firestore to store data that comes from a Raspberry PI with Python. The pipeline is like this:
Raspberry PI (Python using paho-mqtt) -> Google Cloud IoT -> Google Cloud Pub/Sub -> Firebase Functions -> Firestore.
Data in the device is a Python Dictionary. I convert that to JSON.
The problem I had was that paho-mqtt will only send (publish) data as String and one of the fields of my data is timestamp. This timestamp is saved from the device because it accurately says when the measurement was taken regardless on when the data is ultimately stored in the database.
When I send my JSON structure, Firestore will store my field 'timestamp' as String. This is not convenient. So here is the solution.
I do a conversion in the Cloud Function that is triggered by the Pub/Sub to write into Firestore using Moment library to convert.
Note: I am getting the timestamp in python with:
currenttime = datetime.datetime.utcnow()
var moment = require('moment'); // require Moment
function toTimestamp(strDate){
return parsedTime = moment(strDate, "YYYY-MM-DD HH:mm:ss:SS");
}
exports.myFunctionPubSub = functions.pubsub.topic('my-topic-name').onPublish((message, context) => {
let parsedMessage = null;
try {
parsedMessage = message.json;
// Convert timestamp string to timestamp object
parsedMessage.date = toTimestamp(parsedMessage.date);
// Get the Device ID from the message. Useful when you have multiple IoT devices
deviceID = parsedMessage._deviceID;
let addDoc = db.collection('MyDevices')
.doc(deviceID)
.collection('DeviceData')
.add(parsedMessage)
.then ( (ref) => {
console.log('Added document ID: ', ref.id);
return null;
}).catch ( (error) => {
console.error('Failed to write database', error);
return null;
});
} catch (e) {
console.error('PubSub message was not JSON', e);
}
// // Expected return or a warning will be triggered in the Firebase Function logs.
return null;
});
Firestone method does not work. Use Timestamp from java.sql.Timestamp and don't cast to string.. Then firestone formats it properly. For example to mark a now() use:
val timestamp = Timestamp(System.currentTimeMillis())
multiple ways to store time in Firestore
firebaseAdmin.firestore.FieldValue.serverTimestamp() method. The actual timestamp will be computed when the doc is written to the Firestore.
while storing it looks like this:
firebaseAdmin.firestore.Timestamp.now() method.
while storing it looks like this:
For both the methods, next time you fetch data it will return Firestore Timestamp object:
So, you first need to convert it to native js Date object and then you can perform methods on it like toISOString().
export function FStimestampToDate(
timestamp:
| FirebaseFirestore.Timestamp
| FirebaseFirestore.FieldValue
): Date {
return (timestamp as FirebaseFirestore.Timestamp).toDate();
}
Store as unix timestamp Date.now, it'll be stored as number i.e. 1627235565028 but you won't be able to see it as readable Date in firestore db.
To query on this Firestore field, you need to convert the date to timestamp and then query.
Store as new Date().toISOString() i.e. "2021-07-25T17:56:40.373Z" but you won't be able to perform date range query on this.
I prefer the 2nd or 3rd way.
According to the docs, you can "set a field in your document to a server timestamp which tracks when the server receives the update".
Example:
import { updateDoc, serverTimestamp } from "firebase/firestore";
const docRef = doc(db, 'objects', 'some-id');
// Update the timestamp field with the value from the server
const updateTimestamp = await updateDoc(docRef, {
timestamp: serverTimestamp() // this does the trick!
});
Sharing what worked for me after googling for 2 hours, for firebase 9+
import { serverTimestamp } from "firebase/firestore";
export const postData = ({ name, points }: any) => {
const scoresRef = collection(db, "scores");
return addDoc(scoresRef, {
name,
points
date: serverTimestamp(),
});
};
Swift 5.1
...
"dateExample": Timestamp(date: Date()),
...
The newest version from Firestore you should use it as follow
import { doc, setDoc, Timestamp } from "firebase/firestore";
const docData = {
...
dateExample: Timestamp.fromDate(new Date("December 10, 1815"))
};
await setDoc(doc(db, "data", "one"), docData);
or for sever timestamp
import { updateDoc, serverTimestamp } from "firebase/firestore";
const docRef = doc(db, 'objects', 'some-id');
const updateTimestamp = await updateDoc(docRef, {
timestamp: serverTimestamp()
});

Is Firebase Database searchable using objects instead of references, with AngularFire $firebaseArray $keyAt(recordOrIndex)?

After a user logs in using $firebaseAuth, Google sends the user's displayName, email, and photoURL. I then want to look up the user's account in my Firebase database. I can't use $getRecord(key) because Google doesn't tell me the user's key. It appears that I should use $keyAt(recordOrIndex), and then use $getRecord(key). $keyAt(recordOrIndex) works fine with an index. $keyAt(recordOrIndex) works fine with a record that I retrieved with $getRecord(key). I can't get $keyAt(recordOrIndex) to work with an object that I made from the user data that Google returned using $firebaseAuth.
I tried both the complete object (displayName, email, photoURL) and an object consisting of only the email address. The latter is what I would prefer to use. Neither worked.
app.controller('LoginModalInstanceCtrl', ['$scope', '$location', '$uibModalInstance', '$firebaseArray', '$firebaseObject', '$firebaseAuth', function($scope, $location, $uibModalInstance, $firebaseArray, $firebaseObject, $firebaseAuth) {
// Create Firebase3 reference
var ref = firebase.database().ref();
// Set up Firebase Auth
$scope.authObj = $firebaseAuth();
var authData = $scope.authObj.$getAuth();
$scope.authData = authData;
// Google OAuth login handler
$scope.loginGoogle = function() {
$scope.authData = null;
$scope.error = null;
$scope.authObj.$signInWithPopup("google")
.then(function(authData) {
$scope.authData = authData;
console.log(authData);
console.log("Your displayName is:", authData.user.displayName);
console.log("Your email is:", authData.user.email);
console.log("Your photoURL is:", authData.user.photoURL);
var record = {
displayName: authData.user.displayName,
email: authData.user.email,
photoURL: authData.user.photoURL
};
var emailObject = {
email: authData.user.email
};
// look up account
var users = $firebaseArray(ref.child('users'));
users.$loaded()
.then(function() {
console.log("Array loaded!");
var key1 = users.$keyAt(1);
console.log(key1); // -Khi6OxAo339ye6xoG3i
var record = users.$getRecord(key1);
console.log(record); // Object with displayName, email, and photoURL
var key1 = users.$keyAt(record);
console.log(key1); // -Khi6OxAo339ye6xoG3i
var objectKey = users.$keyAt(object);
console.log(objectKey); // null
var emailKey = users.$keyAt(emailObject);
console.log(emailKey); // null
});
$uibModalInstance.close(); // close modal window
$location.path('/languagetwo/'); // return to the homepage
}).catch(function(error) {
console.error("Authentication failed:", error);
});
};
Should I use $firebaseObject instead of $firebaseArray:
var user = $firebaseObject(ref.child('users').child( SOMETHING HERE? ));
The answer appears to be no, you can't search Firebase Database using AngularFire. (Maybe AngularFire 2 has search, I didn't look.) What I did instead was to use "plain vanilla" Firebase:
var users = firebase.database().ref('users');
users.orderByChild('email').equalTo(authData.user.email).once('value').then(function(snapshot) {
console.log(snapshot.val());
});
The first line sets up the Firebase ref and is the similar to as before, except that I'm going straight to the users array, instead of using $FirebaseArray to get to the users array.
The second line is a completely different syntax. First, you have to specify the order that you want the returned object to be in. Yes, it returns an object, not an array. I tried snapshot.val().length() and found that it's not an array. What orderByChild('email') does is to access the 'email' property of the objects in the 'users' array.
Next we do the query. equalTo(authData.user.email) returns only the objects in which the email address from $FirebaseAuth equals the email address in our 'users' array.
Next, once('value') creates a promise and waits for the async data. I tried using on() but couldn't get it to work, too many arguments or something. once() requires an argument, which can be value, child_added, child_changed, child_removed, or child_moved. The value argument is for getting data from a location without changing the child nodes.
We can then set up our then promise fulfillment. You can call the returned data anything. Here it's called snapshot.
Lastly snapshot.val() provides the data from the database, looking just like it does in the Firebase Console.

Resources