firebase $add() .push() .set() - javascript

I am using firebase, and angularfire.
there are so many ways to do CRUD with the Firebase Api
actually, I still don't get what is specific difference for using
$add with $firebaseArray
.push() method
.set() method
I think they are technically same, I prefer to use .set method() without knowing the exact reason, why I'd using that. is there any specific reason to not use it? what is exactly $firebaseArray did? if we could just declare basic reference variable.
in this case:
var usersRef = Ref.child('users');
$scope.createUser = function() {
$scope.userRef.child($id).set({
name: name
});
};
or
$scope.data = $firebaseArray(Ref.child('users'));
$scope.createUser = function() {
$scope.data.child($id).$add({
name: name
});
};
thank you.

If I have the following data tree in Firebase:
{
users:
{
key: { name:"bob" }
}
}
When I do an $add, I will create a new item in the tree
$scope.data.child('users').$add({
name: name
});
Since $add uses the Push method in Firebase, new random Key will be used when pushing data to the child.
{
users:
{[
key: { name:"bob" },
key2: { name:"name" }
]}
}
If I do a set on the same Users object, I will overwrite the data that is already there. So, in your example, without specifying a key, you will overwrite the entire user object.
$scope.userRef.child('users').set({
name: name
});
};
This will result with this data
{
users:
{
name: "name"
}
}
This happens because any null values you pass to the Set method will delete any data that was originally there.
Passing null to set() will remove the data at the specified location.
https://www.firebase.com/docs/web/api/firebase/set.html

Related

Apollo fetchMore updates data globally

I have two TaskList components that use the same query GET_TASKS.
Both use a different filter query variable which is passed down to them in props as queryVars.
I defined a standard merge function in type policies to merge the incoming and existing data together.
The TaskList component uses
const { data, fetchMore } = useQuery<Response, Variables>(GET_TASKS, { variables: queryVars })
to retrieve the data.
A Fetch more button has () => fetchMore({ variables: queryVars }) in the onClick attribute.
When I click on the Fetch more button on the left, the tasks on the right get updated as well, however, without its filter applied, so the data that come with Assigned to me filter are also put to the Assigned by me task list and vice versa.
The merge function basically rewrites every data object that uses the given query.
How do I tell Apollo to only update the data that is bound to the component where fetchMore is defined?
You should be able to add filter to the keyArgs property. This should create different cache results based on the filter.
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
tasks: {
keyArgs: ["filter"],
merge(existing, incoming, { args: { offset = 0 }}) {
//Custom merge
},
},
},
},
},
});

Realtime database update or create functionality

I have a user object in firebases realtime database. I am querying realtime database and when successful I am wanting to write some new data to the user object under the users node.
My desired outcome: When a user has not fetched the information before, a new field called 'lastViewed' under the user object is created and if the field has already been created then we update the timeViewed keys value. A user can have multiple objects in the array corresponding to the uuid of the fetched data.
Please see the user object below
This may not need to be an array if using .push()
-N0X8VLHTw3xgvD2vJs- : { // this is the users unique key
name: 'myName',
lastViewed: {
[
{
timeViewed: 1651558791, // this is the field to update if exists
datasUniqueKey: 'N17ZmwIsbqaVSGh93Q0' // if this value exists update timeViewed else we create the entry.
},
{
timeViewed: 1651558952,
datasUniqueKey: 'N17ZmwIsbqaVSad3gad'
},
]
}
}
Please see my attempt below.
const getData = database()
.ref(`data/${uniqueKeyFromData}`)
.on('value', snapshot => {
if (snapshot.exists()) {
database()
.ref(`users/${currentFirebaseUserKey}/lastViewed`) // currentFirebaseUserKey = N0X8VLHTw3xgvD2vJs
.once('value', childSnapshot => {
if (childSnapshot.exists()) {
// update
database()
.ref(
`users/${currentFirebaseUserKey}/lastViewed`,
)
.update({
timeViewed: new Date(), // new data will not give us the corresponding date format in the user object above but don't worry about that
fetchedDatasUniqueKey: uniqueKeyFromData,
});
} else {
// create
database()
.ref(
`users/${currentFirebaseUserKey}/lastViewed`,
)
// Push creates a unique key which might not be required so maybe set?
.push({
timeViewed: new Date(),
fetchedDatasUniqueKey: uniqueKeyFromData,
});
}
});
}
});
Where I think I am going wrong
Above I am not creating an array, if I use push I would get a unique key generated from firebase but then would have to use that key when updating, something like
`users/${currentFirebaseUserKey}/lastViewed/${lastViewedUniqueKey}`
So the user object would look like so
-N0X8VLHTw3xgvD2vJs- : { // this is the users unique key
name: 'myName',
lastViewed: {
-N17i2X2-rKYXywbJGmQ: { // this is lastViewedUniqueKey
timeViewed: 1651558791,
datasUniqueKey: 'N17ZmwIsbqaVSGh93Q0'
},
}
}
then check for snapshot.key in the if?, any help would be appreciated.
Since you don't want a list of data, but a single set of properties for ``, you should just call set instead of push:
database()
.ref(`users/${currentFirebaseUserKey}/lastViewed`)
.set({ // 👈
timeViewed: new Date(),
fetchedDatasUniqueKey: uniqueKeyFromData,
});
I also don't think you need the two different cases here for create vs update, as both seem to do the exact same thing. If you do need both cases though, consider using a transaction.

Firebase adding item to array [duplicate]

I'm currently trying Firestore, and I'm stuck at something very simple: "updating an array (aka a subdocument)".
My DB structure is super simple. For example:
proprietary: "John Doe",
sharedWith:
[
{who: "first#test.com", when:timestamp},
{who: "another#test.com", when:timestamp},
],
I'm trying (without success) to push new records into shareWith array of objects.
I've tried:
// With SET
firebase.firestore()
.collection('proprietary')
.doc(docID)
.set(
{ sharedWith: [{ who: "third#test.com", when: new Date() }] },
{ merge: true }
)
// With UPDATE
firebase.firestore()
.collection('proprietary')
.doc(docID)
.update({ sharedWith: [{ who: "third#test.com", when: new Date() }] })
None works. These queries overwrite my array.
The answer might be simple, but I could'nt find it...
Firestore now has two functions that allow you to update an array without re-writing the entire thing.
Link: https://firebase.google.com/docs/firestore/manage-data/add-data, specifically https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array
Update elements in an array
If your document contains an array field, you can use arrayUnion() and
arrayRemove() to add and remove elements. arrayUnion() adds elements
to an array but only elements not already present. arrayRemove()
removes all instances of each given element.
Edit 08/13/2018: There is now support for native array operations in Cloud Firestore. See Doug's answer below.
There is currently no way to update a single array element (or add/remove a single element) in Cloud Firestore.
This code here:
firebase.firestore()
.collection('proprietary')
.doc(docID)
.set(
{ sharedWith: [{ who: "third#test.com", when: new Date() }] },
{ merge: true }
)
This says to set the document at proprietary/docID such that sharedWith = [{ who: "third#test.com", when: new Date() } but to not affect any existing document properties. It's very similar to the update() call you provided however the set() call with create the document if it does not exist while the update() call will fail.
So you have two options to achieve what you want.
Option 1 - Set the whole array
Call set() with the entire contents of the array, which will require reading the current data from the DB first. If you're concerned about concurrent updates you can do all of this in a transaction.
Option 2 - Use a subcollection
You could make sharedWith a subcollection of the main document. Then
adding a single item would look like this:
firebase.firestore()
.collection('proprietary')
.doc(docID)
.collection('sharedWith')
.add({ who: "third#test.com", when: new Date() })
Of course this comes with new limitations. You would not be able to query
documents based on who they are shared with, nor would you be able to
get the doc and all of the sharedWith data in a single operation.
Here is the latest example from the Firestore documentation:
firebase.firestore.FieldValue.ArrayUnion
var washingtonRef = db.collection("cities").doc("DC");
// Atomically add a new region to the "regions" array field.
washingtonRef.update({
regions: firebase.firestore.FieldValue.arrayUnion("greater_virginia")
});
// Atomically remove a region from the "regions" array field.
washingtonRef.update({
regions: firebase.firestore.FieldValue.arrayRemove("east_coast")
});
You can use a transaction (https://firebase.google.com/docs/firestore/manage-data/transactions) to get the array, push onto it and then update the document:
const booking = { some: "data" };
const userRef = this.db.collection("users").doc(userId);
this.db.runTransaction(transaction => {
// This code may get re-run multiple times if there are conflicts.
return transaction.get(userRef).then(doc => {
if (!doc.data().bookings) {
transaction.set({
bookings: [booking]
});
} else {
const bookings = doc.data().bookings;
bookings.push(booking);
transaction.update(userRef, { bookings: bookings });
}
});
}).then(function () {
console.log("Transaction successfully committed!");
}).catch(function (error) {
console.log("Transaction failed: ", error);
});
Sorry Late to party but Firestore solved it way back in aug 2018 so If you still looking for that here it is all issues solved with regards to arrays.
https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.htmlOfficial blog post
array-contains, arrayRemove, arrayUnion for checking, removing and updating arrays. Hope it helps.
To build on Sam Stern's answer, there is also a 3rd option which made things easier for me and that is using what Google call a Map, which is essentially a dictionary.
I think a dictionary is far better for the use case you're describing. I usually use arrays for stuff that isn't really updated too much, so they are more or less static. But for stuff that gets written a lot, specifically values that need to be updated for fields that are linked to something else in the database, dictionaries prove to be much easier to maintain and work with.
So for your specific case, the DB structure would look like this:
proprietary: "John Doe"
sharedWith:{
whoEmail1: {when: timestamp},
whoEmail2: {when: timestamp}
}
This will allow you to do the following:
var whoEmail = 'first#test.com';
var sharedObject = {};
sharedObject['sharedWith.' + whoEmail + '.when'] = new Date();
sharedObject['merge'] = true;
firebase.firestore()
.collection('proprietary')
.doc(docID)
.update(sharedObject);
The reason for defining the object as a variable is that using 'sharedWith.' + whoEmail + '.when' directly in the set method will result in an error, at least when using it in a Node.js cloud function.
#Edit (add explanation :) )
say you have an array you want to update your existing firestore document field with. You can use set(yourData, {merge: true} ) passing setOptions(second param in set function) with {merge: true} is must in order to merge the changes instead of overwriting. here is what the official documentation says about it
An options object that configures the behavior of set() calls in DocumentReference, WriteBatch, and Transaction. These calls can be configured to perform granular merges instead of overwriting the target documents in their entirety by providing a SetOptions with merge: true.
you can use this
const yourNewArray = [{who: "first#test.com", when:timestamp}
{who: "another#test.com", when:timestamp}]
collectionRef.doc(docId).set(
{
proprietary: "jhon",
sharedWith: firebase.firestore.FieldValue.arrayUnion(...yourNewArray),
},
{ merge: true },
);
hope this helps :)
addToCart(docId: string, prodId: string): Promise<void> {
return this.baseAngularFirestore.collection('carts').doc(docId).update({
products:
firestore.FieldValue.arrayUnion({
productId: prodId,
qty: 1
}),
});
}
i know this is really old, but to help people newbies with the issue
firebase V9 provides a solution using the arrayUnion and arrayRemove
await updateDoc(documentRef, {
proprietary: arrayUnion( { sharedWith: [{ who: "third#test.com", when: new Date() }] }
});
check this out for more explanation
Other than the answers mentioned above. This will do it.
Using Angular 5 and AngularFire2. or use firebase.firestore() instead of this.afs
// say you have have the following object and
// database structure as you mentioned in your post
data = { who: "third#test.com", when: new Date() };
...othercode
addSharedWith(data) {
const postDocRef = this.afs.collection('posts').doc('docID');
postDocRef.subscribe( post => {
// Grab the existing sharedWith Array
// If post.sharedWith doesn`t exsit initiated with empty array
const foo = { 'sharedWith' : post.sharedWith || []};
// Grab the existing sharedWith Array
foo['sharedWith'].push(data);
// pass updated to fireStore
postsDocRef.update(foo);
// using .set() will overwrite everything
// .update will only update existing values,
// so we initiated sharedWith with empty array
});
}
We can use arrayUnion({}) method to achive this.
Try this:
collectionRef.doc(ID).update({
sharedWith: admin.firestore.FieldValue.arrayUnion({
who: "first#test.com",
when: new Date()
})
});
Documentation can find here: https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array
Consider John Doe a document rather than a collection
Give it a collection of things and thingsSharedWithOthers
Then you can map and query John Doe's shared things in that parallel thingsSharedWithOthers collection.
proprietary: "John Doe"(a document)
things(collection of John's things documents)
thingsSharedWithOthers(collection of John's things being shared with others):
[thingId]:
{who: "first#test.com", when:timestamp}
{who: "another#test.com", when:timestamp}
then set thingsSharedWithOthers
firebase.firestore()
.collection('thingsSharedWithOthers')
.set(
{ [thingId]:{ who: "third#test.com", when: new Date() } },
{ merge: true }
)
If You want to Update an array in a firebase document.
You can do this.
var documentRef = db.collection("Your collection name").doc("Your doc name")
documentRef.update({
yourArrayName: firebase.firestore.FieldValue.arrayUnion("The Value you want to enter")});
Although firebase.firestore.FieldValue.arrayUnion() provides the solution for array update in firestore, at the same time it is required to use {merge:true}. If you do not use {merge:true} it will delete all other fields in the document while updating with the new value. Here is the working code for updating array without loosing data in the reference document with .set() method:
const docRef = firebase.firestore().collection("your_collection_name").doc("your_doc_id");
docRef.set({yourArrayField: firebase.firestore.FieldValue.arrayUnion("value_to_add")}, {merge:true});
If anybody is looking for Java firestore sdk solution to add items in array field:
List<String> list = java.util.Arrays.asList("A", "B");
Object[] fieldsToUpdate = list.toArray();
DocumentReference docRef = getCollection().document("docId");
docRef.update(fieldName, FieldValue.arrayUnion(fieldsToUpdate));
To delete items from array user: FieldValue.arrayRemove()
If the document contains a nested object in the form of an array, .dot notation can be used to reference and update nested fields.
Node.js example:
const users = {
name: 'Tom',
surname: 'Smith',
favorites: {
sport: 'tennis',
color: 'red',
subject: 'math'
}
};
const update = await db.collection('users').doc('Tom').update({
'favorites.sport': 'snowboard'
});
or Android sdk example:
db.collection("users").document("Tom")
.update(
'favorites.sport': 'snowboard'
);
There is a simple hack in firestore:
use path with "." as property name:
propertyname.arraysubname.${id}:
db.collection("collection")
.doc("docId")
.update({arrayOfObj: fieldValue.arrayUnion({...item})})

Function getting firebase object returns hard to use object

What I am trying to do
I am creating a social media app with react native and firebase. I am trying to call a function, and have that function return a list of posts from off of my server.
Problem
Using the return method on a firebase query gives me a hard to use object array:
Array [
Object {
"-L2mDBZ6gqY6ANJD6rg1": Object {
//...
},
},
]
I don't like how there is an object inside of an object, and the whole thing is very hard to work with. I created a list inside my app and named it items, and when pushing all of the values to that, I got a much easier to work with object:
Array [
Object {
//...
"key": "-L2mDBZ6gqY6ANJD6rg1",
},
]
This object is also a lot nicer to use because the key is not the name of the object, but inside of it.
I would just return the array I made, but that returns as undefined.
My question
In a function, how can I return an array I created using a firebase query? (to get the objects of an array)
My Code
runQ(group){
var items = [];
//I am returning the entire firebase query...
return firebase.database().ref('posts/'+group).orderByKey().once ('value', (snap) => {
snap.forEach ( (child) => {
items.push({
//post contents
});
});
console.log(items)
//... but all I want to return is the items array. This returns undefined though.
})
}
Please let me know if I'm getting your question correctly. So, the posts table in database looks like this right now:
And you want to return these posts in this manner:
[
{
"key": "-L1ELDwqJqm17iBI4UZu",
"message": "post 1"
},
{
"key": "-L1ELOuuf9hOdydnI3HU",
"message": "post 2"
},
{
"key": "-L1ELqFi7X9lm6ssOd5d",
"message": "post 3"
},
{
"key": "-L1EMH-Co64-RAQ1-AvU",
"message": "post 4"
}
...
]
Is this correct? If so, here's what you're suppose to do:
var items = [];
firebase.database().ref('posts').orderByKey().once('value', (snapshot) => {
snapshot.forEach((child) => {
// 'key' might not be a part of the post, if you do want to
// include the key as well, then use this code instead
//
// const post = child.val();
// const key = child.key;
// items.push({ ...post, key });
//
// Otherwise, the following line is enough
items.push(child.val());
});
// Then, do something with the 'items' array here
})
.catch(() => { });
Off the topics here: I see that you're using firebase.database().... to fetch posts from the database, are you using cloud functions or you're fetching those posts in your App, using users' devices to do so? If it's the latter, you probably would rather use cloud functions and pagination to fetch posts, mainly because of 2 reasons:
There might be too many posts to fetch at one time
This causes security issues, because you're allowing every device to connect to your database (you'd have to come up with real good security rules to keep your database safe)

Knockout JS not setting all members observable

What I am trying to do is to get data from the server and then putting it all in an observable and then make all the properties observable. The issue I am facing is that it does not make all my properties observable and I need them all to be observable as sometimes depending on the data it makes some properties observable and sometimes it doesn't.
var viewModel = this;
viewModel.Model = ko.observable();
viewModel.SetModel = function (data) {
viewModel.Model(ko.mapping.fromJS(data));
}
The data that I am receiving from the server is like this for example: normaldata,items(this is an array with unknown number of elements).
so if i try to access data like viewModel.Model().Items[0]().Layer() i sometimes have Layer as a function and sometimes it is a normal element with observable elements.I want all my objects inside Items to have Layer as a function.
Server data example:
Name: "test"
Items: [Layer[ID: 132]]
In this example Name,Items and ID are observable but Layer is not.
Fiddle example:
jsfiddle.net/98dv11yz/3
So the problem is that sometimes the layer is null resulting in ko making the property observable but sometimes that property has id and ko makes only the child elements observable. The problem is that i have if's in the code and i want it to be a function so i can always reffer to it as layer() because now it is sometimes layer or layer()
An explenation for what's happening:
When the ko.mapping plugin encounters an object in your input, it will make the object's properties observable, not the property itself.
For example:
var myVM = ko.mapping.fromJS({
name: "Foo",
myObject: {
bar: "Baz"
}
});
Will boil down to:
var myVM = {
name: ko.observable("Foo"),
myObject: {
bar: ko.observable("Baz")
}
}
and not to:
var myVM = {
name: ko.observable("Foo"),
myObject: ko.observable({
bar: ko.observable("Baz")
})
}
The issue with your data structure is that myObject will sometimes be null, and sometimes be an object. The first will be treated just as the name property in this example, the latter will be treated as the myObject prop.
My suggestion:
Firstly: I'd suggest to only use the ko.mapping.fromJS method if you have a well documented and uniform data structure, and not on large data sets that have many levels and complexity. Sometimes, it's easier to create slim viewmodels that have their own mapping logic in their constructor.
If you do not wish to alter your data structure and want to keep using ko.mapping, this part will have to be changed client-side:
Items: [
{ layer: {id: "0.2"} },
{ layer: null}
]
You'll have to decide what you want to achieve. Should the viewmodel strip out the item with a null layer? Or do you want to render it and be able to update it? Here's an example of how to "correct" your data before creating a view model:
var serverData = {
Name: "Example Name",
Id: "0",
Items: [
{layer: {id: "0.2"} },
{layer: null}
]
};
var correctedData = (function() {
var copy = JSON.parse(JSON.stringify(serverData));
// If you want to be able to render the null item:
copy.Items = copy.Items.map(function(item) {
return item.layer ? item : { layer: { id: "unknown" } };
});
// If you don't want it in there:
copy.Items = copy.Items.filter(function(item) {
return item.layer;
});
return copy;
}());
Whether this solution is acceptable kind of relies on how much more complicated your real-life use will be. If there's more complexity and interactivity to the data, I'd suggest mapping the items to their own viewmodels that deal with missing properties and what not...

Categories