$firebaseArray join with other table - firebase

I have a Firebase table named Orders:
{productID: xyz,
userID: abc,
quantity: 100}
There is another table Products containing product details. When showing a user's orders, I need to show some product details along with each order. How can I use $firebaseArray for this purpose?
$firebaseArray seems can only go with a table. Here, I'd need to query each ordered product's detail and add the detail to each order. Is the $extend the way to go? If so, any good example would be very appreciated.

Just saw this one. What you want to use is the NormalizedCollection. This way you can write your statement something like this:
var fb = new Firebase('https://<instance>.firebaseio.com');
var norm = new Firebase.util.NormalizedCollection(
fb.child('products'),
fb.child('productDetails')
);
var productsRef = $firebaseArray(norm.ref());

Related

vue + firestore: how to bind / merge data with vuefire?

I have a cloud firestore with data structured this way:
baskets
basket 1
basketID
userID
description
products
productID
productID
basket 2
basketID
userID
description
products
productID
productID
...
basket N
products
product A
productID
description
productImg
price
product B
productID
description
productImg
price
...
product Z
This structure in order to avoid duplicating detailed product data inside each basket document.
I created a vue component to display at once both the basket data and the products data associated to the basket, while trying to limit and optimize firebase queries. I have sucessfully binded the basket data like this:
data() {
return {
basket: [],
basket_products: []
};
},
created() {
this.$bind('basket', db.collection("baskets").where("basketID", "==", this.basketID))
}
However I'm struggling to bind the basket_products data for the current basket. I'm looking for something like this:
this.$bind('basket_products', db.collection("products").where("productID", 'in', ONE OF THE PRODUCT_IDS WITHIN CURRENT BASKET, EQUIVALENT TO this.basket[0].products))
Glad my pointer helped!
Just for completeness sake - the best solution as mentioned is to simply store references, vuefire automatically picks up on those and saves them accordingly as part of the baskets thanks to the maxRefsDepth option.
Aside from that, you could potentially play around with the serialize option of vuefire to modify (and query additional) data when getting the baskets.
But this would be fairly complex, you might be better off just manually subscribing using the firebase SDK instead.

How to query documents from a sub-collection, based on a main collection's field and a sub-collection's fields in cloud firestore?

I am new to firebase cloud firestore and need help understanding it a bit.
I am working on a project of the following structure.
Each "Restaurant" document contains its own "products" subcollection.
Here, I wanted to run a query to get all the products by different
restaurants that contain the tag "coffee" and are in a specific
Pincode "234144".
I tried Collection group queries by adding Pincode to each product owned by a particular restaurant but changing a Pincode would cost a lot, as all products would have to be edited, I guess.
Is there any efficient way of doing it or is it not possible in this
database in an efficient way?
Please let me know what do you think... Thank you.
If I understand correctly, you want to retrieve all products with a certain tag and pincode (I suppose this is similar to a postal zipcode?). There really are only two alternatives that I can think of:
Collection group query
As you mention, you can store both tag and pincode in product documents. Then perform a single collection group query along these lines (pardon the javascript, but I am not familiar with Dart, it should be very similar):
var products = await firestore.collectionGroup('products')
.where('tag', '==', 'coffee')
.where('pincode', '==', '234144').get();
As you have noted, with this solution you need to keep pincode in each product This piece of data is duplicated and it is normal to feel that it should be avoided because it is wasteful and dangerous (can go out of sync), but this is the way to go! It is called denormalization. This is well explained in this video by Todd Kerpelman.
You can then create a Cloud Function triggered by restaurant update to keep pincode in products in sync with the corresponding pincode in restaurants
Query restaurants then products
To keep the pincode in restaurants only, you have to do your query in two steps: first filter restaurants in the certain pincode, then filter products by tag:
// 1 - retrieve restaurants in specific pincode
var restaurants = await firestore.collection('restaurants').where('pincode', '==', '234144').get();
// 2 - For each retrieved restaurant, retrieve all products matching the tag
var products = [];
for(let i = 0; i < restaurants.docs.length; ++i) {
var p = await restaurants.docs[i].collection("products").where("tag", "==", "coffee");
products.push(p);
}
With this method, no need to duplicate pincode in each product, however your queries are less optimal, because you load potentially useless restaurants that do not serve coffee in your pincode.
I have a similar data structure, and Louis Coulet's answer helped me a lot. However, I ran into an issue, as my node/firebase environment complained that restaurants.docs[i].collection was not a function. Instead, I had to use restaurants.docs[i].ref.collection.
So, in my case, here's the code that wound up working (using the original poster's "restaurants/products" data model as an example):
// 1 - retrieve restaurants in specific pincode
const restaurants = await firestore.collection('restaurants').where('pincode', '==', '234144').get();
// 2 - For each retrieved restaurant, retrieve all products matching the tag 'coffee'
const products = [];
for (let i = 0; i < restaurants.docs.length; i++) {
await restaurants.docs[i].ref
.collection('products')
.where('tag', '==', 'coffee')
.get()
.then(s => Promise.all(s.docs.map(d => {
products.push(d);
})));
}
I have found that I need to return the sub-collection contents via some kind of promise, otherwise, after I try to get the data in the variable -- products, in this case -- it just shows up as undefined.
After I populate the products variable, I am then able to read its contents like this:
products.forEach(p => {
console.log(p.data().name);
console.log(p.data().tag);
}
I hope someone finds this useful, and many, MANY thanks to Louis Coulet for sending me/us down the correct path with this sub-collection headache :-)

Grabbing Keys with Firebase

I'm currently creating an app using ionic2 and firebase for a class and I've run into an issue.
I"m not posting code because I don't have anything relevant enough to the question.
In my database I have a 'group' that people can create. I need the 'group' to be able to store a list of users. The way I'm currently doing it is that users will click an addGroup button, and the currentUser will be added to the list of users in that group simply by typing in the name of the group. (yeah i know that's going to need a password or something similar later, people obviously shouldn't just be able to join by group name)
*Names of groups will all be different so I should be able to query the database with that and get a single reference point.
The problem is that I don't know how to properly query the database and get the key value of the group object I want.
I keep looking things up and cant find a simple way to get the key of an object. I feel like this is a dumb question but, I'm unfamiliar with typescript, firebase, and ionic2 so I'm pretty lost in everything.
Answer for the question in comment.
you can query the database like this
this.group_val:any="group1"
let group=db.list('/groups', {
query: {
orderByChild: GroupName, //here specify the name of field in groups
equalTo: this.group_val //here specify the value u want to search
}
});
group.subscribe(grp=> {
//you can use the grup object here
})
First, define your database structure. The below is probably best:
groups
<first group> (Firebase-generated ID)
name: "Cat lovers"
users:
user1ID: true
user2ID: true
<second group>
...
I assume that when the user clicks on the group he wants to join, you know the group ID. For instance, it could be the value of an option in a select.
Get the list of groups:
groups$ = this.angularFireDatabase.list('groups');
To populate a list of groups into a select:
<select>
<option *ngFor="let group of groups$ | async" [value]="group.$key">
{{group.name}}
</option>
</select>
To create a new group:
groups$.push({name: "Dog lovers"})
The select (drop-down) will update itself.
To add a user to a group:
addUserToGroup(groupId, userId) {
this.angularFireDatabase.list(`groups/${groupID}/users`).update(userID, true);
}
To find a group based on its name:
// Define the shape of a group for better type checking.
interface Group { name: string; users: {[userId: string]: boolean;}; }
// Keep local list of groups.
groups$: FirebaseListObservable<Group>;
groups: Group[];
// Keep an updated local copy of groups.
ngOnInit() {
this.angularFireDatabase.list('groups').subscribe(groups => this.groups = groups);
}
function findGroupByName(name) {
return this.groups.find(group => group.name === name);
}

Firebase,iOS: Appending key-value pair into a child node

I have the following User Table structure in Firebase
As you can see in the user that I have opened, I have a Posts section, inside this post section holds the Id's all articles which have been posted by this user.
The issue I am facing is as follows:
When the user creates a new article it's saved within the Posts Table, after the save I return the newly generated ID which I then pass on to the user table, I trying to insert the newly created ID into the post section of the user, so I assumed the URL would be something like this:
Users/{UserId}/Posts
However all this does it create a new section called posts, it doesn't actually insert the record into the given area.
My code which isn't working is as follows:
let linkPost = [childautoID: true]
FIRDatabase.database().reference().child("Users/\(UserId)/Posts").child(UserId).setValue(linkPost)
FYI the two id's that are currently inside Posts I added manually.
I've also tried the following:
FIRDatabase.database().reference().child("Users/\(UserId)/Posts").setValue(linkPost)
However all this does it remove all existing Id's and then inserts the new id.
I prefer something like this. This automatically append the data without fetching first
FIRDatabase.database().reference().child("Users/\(UserId)/Posts").child(UserId).setValue(true)
To append a key-value pair in Firebase Database child node use this :-
Make a Firebase Database Reference to the Posts node of that currentUser FIRDatabase.database().reference().child("Users").child(FIRAuth.auth()!.currentUser!.uid).child("Posts")
Check if Posts node exists in your user's DB, If not then create one by :- parentRef.setValue([postId : "True"]) in else block.
But if Posts node does exist retrieve it as a NSMutableDictionary , set the new object to it, and then store the updated Dictionary to that node.
func storePostsToDB(postID :String!, userID : String! = FIRAuth.auth()!.currentUser!.uid){
let parentRef = FIRDatabase.database().reference().child("Users").child(userID).child("Posts")
parentRef.observeSingleEventOfType(.Value, withBlock: {(friendsList) in
if friendsList.exists(){
if let listDict = friendsList.value as? NSMutableDictionary{
listDict.setObject("True", forKey: postID)
parentRef.setValue(listDict)
}
}else{
parentRef.setValue([postID : "True"])
}
})
}
Calling the function:-
storePostsToDB("yourPostID")// If you want to store in the currentUser DB
storePostsToDB("yourPostID", userID : otherUserID)//If you want to store the post in some other users database with uid `otherUserID`

Firebase dynamic object key

So I want the key in my firebase database to be dynamically generated.
At the moment, I have something like this
whatever.$add({
title: $scope.formData.title
})
UPDATE: The whatever is actually a $firebaseArray, and yes, returns an id.
Actually, in the above example, I wanted to do something like:
whatever.$add({
$scope.formData.type: $scope.formData.title
})
I basically want the key set to something that'll come from a form. Any way?
I'm supposing that whatever is actually a $firebaseArray and if i'm right your $add will always result in a new child with random id.
If you want to create a new child with a custom id you should be working with .child().set():
var ref = new Firebase(yourFirebaseUrl);
ref.child(customId).set({
type: $scope.formData.title
});
Update:
To have the $scope.formData.title as the id you should do:
var ref = new Firebase(yourFirebaseUrl);
ref.child($scope.formData.title).set({
type: $scope.formData.title
anotherData: $scope.formData.anotherFormData
});

Resources