Reputation: 7324
I'm just exploring the new Firebase Firestore and it contains a data type called reference
. It is not clear to me what this does.
reference
is an actual reference, can I use it for queries? For example can I have a reference that points directly to the user, instead of storing the userId in a text field? And can I use this user reference for querying?Upvotes: 316
Views: 165179
Reputation: 4689
I wrote a blog post explaining all the uses of a Firestore Reference Type.
Automatic JOINS:
DOC
expandRef<T>(obs: Observable<T>, fields: any[] = []): Observable<T> {
return obs.pipe(
switchMap((doc: any) => doc ? combineLatest(
(fields.length === 0 ? Object.keys(doc).filter(
(k: any) => {
const p = doc[k] instanceof DocumentReference;
if (p) fields.push(k);
return p;
}
) : fields).map((f: any) => docData<any>(doc[f]))
).pipe(
map((r: any) => fields.reduce(
(prev: any, curr: any) =>
({ ...prev, [curr]: r.shift() })
, doc)
)
) : of(doc))
);
}
COLLECTION
expandRefs<T>(
obs: Observable<T[]>,
fields: any[] = []
): Observable<T[]> {
return obs.pipe(
switchMap((col: any[]) =>
col.length !== 0 ? combineLatest(col.map((doc: any) =>
(fields.length === 0 ? Object.keys(doc).filter(
(k: any) => {
const p = doc[k] instanceof DocumentReference;
if (p) fields.push(k);
return p;
}
) : fields).map((f: any) => docData<any>(doc[f]))
).reduce((acc: any, val: any) => [].concat(acc, val)))
.pipe(
map((h: any) =>
col.map((doc2: any) =>
fields.reduce(
(prev: any, curr: any) =>
({ ...prev, [curr]: h.shift() })
, doc2
)
)
)
) : of(col)
)
);
}
Simply put this function around your observable and it will automatically expand all reference data types providing automatic joins.
Usage
this.posts = expandRefs(
collectionData(
query(
collection(this.afs, 'posts'),
where('published', '==', true),
orderBy(fieldSort)
), { idField: 'id' }
)
);
Note: You can also now input the fields you want to expand as a second argument in an array.
['imageDoc', 'authorDoc']
This will increase the speed!
Add .pipe(take(1)).toPromise();
at the end for a promise version!
See here for more info. Works in Firebase 8 or 9!
Simple!
J
Upvotes: 1
Reputation: 1
If you don't use Reference data type, you need to update every document.
For example, you have 2 collections "categories" and "products" and you stored the category name "Fruits" in categories to every document of "Apple" and "Lemon" in products as shown below. But, if you update the category name "Fruits" in categories, you also need to update the category name "Fruits" in every document of "Apple" and "Lemon" in products:
collection | document | field
categories > 67f60ad3 > name: "Fruits"
collection | document | field
products > 32d410a7 > name: "Apple", category: "Fruits"
58d16c57 > name: "Lemon", category: "Fruits"
But, if you store the reference of "Fruits" in categories to every document of "Apple" and "Lemon" in products, you don't need to update every document of "Apple" and "Lemon" when you update the category name "Fruits" in categories:
collection | document | field
products > 32d410a7 > name: "Apple", category: categories/67f60ad3
58d16c57 > name: "Lemon", category: categories/67f60ad3
This is the goodness of Reference data type.
Upvotes: 5
Reputation: 3375
Adding below what worked for me using references in Firestore.
As the other answers say, it's like a foreign key. The reference attribute doesn't return the data of the reference doc though. For example, I have a list of products, with a userRef reference as one of the attributes on the product. Getting the list of products, gives me the reference of the user that created that product. But it doesn't give me the details of the user in that reference. I've used other back end as a services with pointers before that have a "populate: true" flag that gives the user details back instead of just the reference id of the user, which would be great to have here (hopefully a future improvement).
Below is some example code that I used to set the reference as well as get the collection of products list then get the user details from the user reference id given.
Set a reference on a collection:
let data = {
name: 'productName',
size: 'medium',
userRef: db.doc('users/' + firebase.auth().currentUser.uid)
};
db.collection('products').add(data);
Get a collection (products) and all references on each document (user details):
db.collection('products').get()
.then(res => {
vm.mainListItems = [];
res.forEach(doc => {
let newItem = doc.data();
newItem.id = doc.id;
if (newItem.userRef) {
newItem.userRef.get()
.then(res => {
newItem.userData = res.data()
vm.mainListItems.push(newItem);
})
.catch(err => console.error(err));
} else {
vm.mainListItems.push(newItem);
}
});
})
.catch(err => { console.error(err) });
Upvotes: 207
Reputation: 752
2022 UPDATE
let coursesArray = [];
const coursesCollection = async () => {
const queryCourse = query(
collection(db, "course"),
where("status", "==", "active")
)
onSnapshot(queryCourse, (querySnapshot) => {
querySnapshot.forEach(async (courseDoc) => {
if (courseDoc.data().userId) {
const userRef = courseDoc.data().userId;
getDoc(userRef)
.then((res) => {
console.log(res.data());
})
}
coursesArray.push(courseDoc.data());
});
setCourses(coursesArray);
});
}
Upvotes: 0
Reputation: 843
A lot of answers mentioned it is just a reference to another document but does not return data for that reference but we can use it to fetch data separately.
Here is an example of how you could use it in the firebase JavaScript SDK 9, modular
version.
let's assume your firestore have a collection called products
and it contains the following document.
{
name: 'productName',
size: 'medium',
userRef: 'user/dfjalskerijfs'
}
here users have a reference to a document in the users
collection. we can use the following code segment to get the product and then retrieve the user from the reference.
import { collection, getDocs, getDoc, query, where } from "firebase/firestore";
import { db } from "./main"; // firestore db object
let productsWithUser = []
const querySnaphot = await getDocs(collection(db, 'products'));
querySnapshot.forEach(async (doc) => {
let newItem = {id: doc.id, ...doc.data()};
if(newItem.userRef) {
let userData = await getDoc(newItem.userRef);
if(userData.exists()) {
newItem.userData = {userID: userData.id, ...userData.data()}
}
productwithUser.push(newItem);
} else {
productwithUser.push(newItem);
}
});
here collection, getDocs, getDoc, query, where
are firestore related modules we can use to get data whenever necessary. we use user reference returned from the products
document directly to fetch the user document for that reference using the following code,
let userData = await getDoc(newItem.userRef);
to read more on how to use modular ver SDK refer to official documentation to learn more.
Upvotes: 11
Reputation: 5927
Belatedly, there are two advantages from this blog:
if I expect that I'll want to order restaurant reviews by rating, or publish date, or most upvotes, I can do that within a reviews subcollection without needing a composite index. In the larger top level collection, I'd need to create a separate composite index for each one of those, and I also have a limit of 200 composite indexes.
I wouldn't have 200 composite indices but there are some constraints.
Also, from a security rules standpoint, it's fairly common to restrict child documents based on some data that exists in their parent, and that's significantly easier to do when you have data set up in subcollections.
One example would be restricting to insert a child collection if the user doesn't have the privilege in the parent's field.
Upvotes: 0
Reputation: 3007
According to the #AskFirebase https://youtu.be/Elg2zDVIcLo?t=276 the primary use-case for now is a link in Firebase console UI
Upvotes: 11
Reputation: 5278
For those looking for a Javascript solution to querying by reference - the concept is that, you need to use a 'document reference' object in the query statement
teamDbRef = db.collection('teams').doc('CnbasS9cZQ2SfvGY2r3b'); /* CnbasS9cZQ2SfvGY2r3b being the collection ID */
//
//
db.collection("squad").where('team', '==', teamDbRef).get().then((querySnapshot) => {
//
}).catch(function(error) {
//
});
(Kudos to the answer here: https://stackoverflow.com/a/53141199/1487867)
Upvotes: 35
Reputation: 7870
References are very much like foreign keys.
The currently released SDKs cannot store references to other projects. Within a project, references can point to any other document in any other collection.
You can use references in queries like any other value: for filtering, ordering, and for paging (startAt/startAfter).
Unlike foreign keys in a SQL database, references are not useful for performing joins in a single query. You can use them for dependent lookups (which seem join like), but be careful because each hop will result in another round trip to the server.
Upvotes: 140