From the Firebase note:
Given a single key path like alanisawesome, updateChildren() only updates data at the first child level, and any data passed in beyond the first child level is a treated as a setValue() operation. Multi-path behavior allows longer paths (like alanisawesome/nickname) to be used without overwriting data. This is why the first example differs from the second example.
I am trying to use a single function createOrUpdateData(object) in my code. In case of update, it updates first level children properly, but if I have nested object passed, then it deletes all other properties of that nested object.
Here's the code:
function saveUserDetails(email,object){
var hashedEmail = Utilities.getHashCode(email);
var userRef = ref.child(hashedEmail);
return $q(function(resolve,reject){
return userRef.update(object, function(error){
if(error){
reject(error);
}else{
resolve("Updated successfully!");
}
});
});
}
So if I pass:
{
name: 'Rohan Dalvi',
externalLinks: {
website: 'mywebsite'
}
}
Then it will delete other properties inside externalLinks object. Is there a cleaner and simpler way of avoiding this?
In short, how do I make sure nested objects are only updated and that data is not deleted.
You can use multi-path updates.
var userRef = ref.child(hashedEmail);
var updateObject = {
name: 'Rohan Dalvi',
"externalLinks/website": 'mywebsite'
};
userRef.update(updateObject);
By using the "externalLinks/website" syntax in the object literal it will treat the nested path as an update and not a set for the nested object. This keeps nested data from being deleted.
This question provides a more recent solution that works with cloud firestore.
Rather than using "/", one may use "." instead:
var userRef = ref.child(hashedEmail);
var updateObject = {
name: 'Rohan Dalvi',
"externalLinks.website": 'mywebsite'
};
userRef.update(updateObject);
To update nested object/map/dictionary in firebase database, you can use Firestore.Encoder to class/struct that is Codable.
Here is a Swift code example:
Models:
import FirebaseFirestore
import FirebaseFirestoreSwift
// UserDetails Model
class UserDetailsModel: Codable {
let name: String,
externalLinks: ExternalLinkModel
}
// UserDetails Model
class ExternalLinkModel: Codable {
let website: String
}
Calling Firebase:
import FirebaseFirestore
import FirebaseFirestoreSwift
let firestoreEncoder = Firestore.Encoder()
let fields: [String: Any] = [
// using firestore encoder to convert object to firebase map
"externalLinks": try! firestoreEncoder.encode(externalLinkModel)
]
db.collection(path)
.document(userId)
.updateData(fields, completion: { error in
...
})
Related
Is there a way to store a document then get an updated value of it at a later date without needing to query and populate again?
const someDocument = await SomeModel.findOne({...}).populate(...);
// store a reference to it for use
const props = {
document: someDocument,
...
}
// at a later time
await props.document.getLatest() <-- update and populate in place?
As describe here,
You can define your own custom document instance methods by add functions on the schema level.
example taken from mongoose doc:
// define a schema
const animalSchema = new Schema({ name: String, type: String });
// assign a function to the "methods" object of our animalSchema
animalSchema.methods.findSimilarTypes = function(cb) {
return mongoose.model('Animal').find({ type: this.type }, cb);
};
const Animal = mongoose.model('Animal', animalSchema);
const dog = new Animal({ type: 'dog' });
dog.findSimilarTypes((err, dogs) => {
console.log(dogs); // woof
});
This is the only way I can think of doing what you want - add a function that populate.
another thing that might help: you can create a pre hook on 'find' and add populate in it
I have a basic database that essentially stores an array of product id's underneath a user. The user can select products to add to the array so it makes sense to use 'arrayUnion' so I avoid reading and re-writing the array constantly, however, I keep getting the error *"Property 'firestore' does not exist on type 'FirebaseNamespace'."
I've followed the documentation found at: https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array but I fear I'm still using it incorrectly.
My code for updating the array is:
addToPad(notepadName: string){
const updateRef = this.db.collection('users').doc(this.activeUserID).collection('notepads').doc(notepadName);
updateRef.update({
products: firebase.firestore.FieldValue.arrayUnion(this.productId)
});
}
First you need to import firestore:
import { firestore } from 'firebase/app';
Then you will be able to use arrayUnion:
addToPad(notepadName: string){
const updateRef = this.db.collection('users').doc(this.activeUserID).collection('notepads').doc(notepadName);
updateRef.update({
products: firestore.FieldValue.arrayUnion(this.productId)
});
}
import { arrayUnion } from '#angular/fire/firestore'
const path = `ai/${videoId}/panel-operation/${id}`
const myDoc: AngularFirestoreDocument<any> = this.afs.doc<any>(path)
const promise: Promise<void> = myDoc.update({ auxPanelSelections: arrayUnion({auxPanel: 'clip', operation: 'replace'}) }).catch((err: any) => {
console.error(`oopsie - ${err.message}`)
return null
})
auxPanelSelections is an array within the myDoc document
Note that the above code also works perfectly fine with arrayRemove
I cannot find the #angular/fire docs for arrayUnion but the generic docs are here
I am very new to react and Firebase and I really struggle with arrays and objects I'm guessing that you can't use .map with the way my data is formatted (or type). I've looked through stack but nothing has helped (at least in my poor efforts to implement fixes).
I am trying to use .map to map through a result from firebase but I get the error TypeError: this.state.firebasedata.map is not a function.
getting the data:
componentWillMount(){
this.getVideosFromFirebase()
}
getVideosFromFirebase(){
var youtubeVideos = firebase.database().ref('videos/');
youtubeVideos.on('value', (snapshot) => {
const firebasedata = snapshot.val();
this.setState({firebasedata});
});
}
relevant states:
constructor(props){
super(props);
this.state = {
firebasedata: []
}
};
.map in render:
render(){
return(
<div>
{this.state.firebasedata.map((item) =>
<div key="{item}">
<p>{item.video.name}</p>
</div>
)}
</div>
);
}
This is because firebase does not store data as arrays, but instead as objects. So the response you're getting is an object.
Firebase has no native support for arrays. If you store an array, it really gets stored as an "object" with integers as the key names.
Read this for more on why firebase stores data as objects.
To map over objects you can do something like
Object.keys(myObject).map(function(key, index) {
console.log(myObject[key])
});
For anyone coming here now, you can simply type snapshot.docs to get an array of all the documents in the relevant collection.
As an example, if you want to get all user objects from the collection users you can do the following:
const getAllUsers = async () => {
const usersSnapshot = await db.collection('users').get()
const allUsers = usersSnapshot.docs.map(userDoc => userDoc.data())
return allUsers
}
As noted above Firebase snap.val() - is an object.
When you have to go through object you can also simply use for each:
for(var key in this.state.firebasedata){
<div key="{key}">
<p>{this.state.firebasedata[key].video.name}</p>
</div>
}
I also recommend creating a separate method for it, and call it in render.
Hope this helps
I just updated to normalizr version 3.1.x so I can utilize the denormalization. Though they've significantly changed their API. I'm having trouble transferring my schemas over.
import { normalize, Schema, arrayOf, valuesOf } from 'normalizr';
const usersSchema = new Schema('users')
const photosSchema = new Schema('photos')
const phonesSchema = new Schema('phones')
photosSchema.define({
users: arrayOf(usersSchema)
})
phonesSchema.define({
users: arrayOf(usersSchema)
})
usersSchema.define({
photos: valuesOf(photosSchema),
phones: valuesOf(phonesSchema)
})
That was my existing schema for users. I was also using the redux-normalizr middleware inside my redux action, so I connected the schema to my action like this:
import { usersSchema } from '../normalizrSchemas/usersSchemas.js'
import { arrayOf } from 'normalizr'
export function getUsers(data) {
return {
type: 'GET_USERS',
payload: data,
meta: {
schema : arrayOf(usersSchema)
}
}
}
This was my first attempt to convert the schema over. It doesn't seem you can call schema.Array the same way you could using arrayOf, so I thought I needed to move the array call into the schema.
import { schema } from 'normalizr';
const photos = new schema.Entity('photos')
const phones = new schema.Entity('phones')
const user = new schema.Entity('user', {
photos: [photos],
phones: [phones]
})
const users= new schema.Array('users', user)
export { users }
the action is the same, but i've removed wrapping the schema in arrayOf. All of the users data is just getting dumped into results without any normalization. The data is a list of user object, and each object contains an id, which normalizr should pick up. I'm struggling to figure out how to get normalizr the identify that it's an array of object I think.
schema.Array does not accept a key string name (docs). The first argument should be the schema definition. So instead of
const users= new schema.Array('users', user)
You should use:
const users = new schema.Array(user)
Or, you could just use the shorthand for an array of a single entity type:
const users = [ user ];
I'm new to Ember.js and I'm trying to add an object to the store after an ajax request.
The problem is that it does not reflect on template if I use ember-cli-pagination.
If I use this.store.findAll in model, it works, but when I use this.findPaged it does not.
I'm using ember-inspector and the object appears in the store, just don't in the browser.
My code:
import Ember from 'ember';
import RouteMixin from 'ember-cli-pagination/remote/route-mixin';
export default Ember.Route.extend(RouteMixin, {
actions: {
create: function(email) {
let adapter = Ember.getOwner(this).lookup('adapter:application');
let url = adapter.buildURL('billing/delivery-files');
let store = this.get('store');
let modelName = 'billing/delivery-file';
return adapter.ajax(url, 'POST', {
email: email
}).then(function(data) {
var normalized = store.normalize(modelName, data.object);
store.push(normalized);
});
}
},
model(params) {
return this.findPaged('billing/delivery-file',params); //does not work
// return this.store.findAll('billing/delivery-file'); //works
}
});
Tried the solutions from this issue, and did not work at all.
What am I missing?
Figured out!
After pushing to the store, I needed to push to the paginated array a reference to the store object. Don't know exactly why but it worked like a charm!
model.content.pushObject(pushedRecord._internalModel);
In my case I wanted it at the first position of the array, so I did:
model.content.insertAt(0, pushedRecord._internalModel);