Updating object in firebase using angular - angularjs

I am using angular firebase application , a client has updated the purchase request of some commodity , using the below method in firebase ...
addPurchaseRequest(data, uid: string) {
this.db.database.ref("/purchaserequests/").push(data);
}
I got the below 2 purchases in firebase
purchaserequests
-LXZVHEpbxfpJzt_el10
actualbonus: 12312,
uid:1,
status:,
approvedon:''
-LXFSADSpbxfpJzt_el11
actualbonus: 12,
uid:2,
status:0,
approvedon:''
now i want to update the status and date for uid = 1
updatepurchaserequest(rid : string , amount : number , reason: string) {
const dataObj = {
status: 1
approvedon: new Date().toLocaleDateString()
};
this.db.database.ref("/purchaserequests/").set(dataObj);
But this is not working and replacing the complete purchase request object.
I need something like where clause of Mysql , which says update dataObj where uid =1

I think that you probably must be edit by $key or make your uid like your $key
When you get your data, you could do this:
yourData.forEach( u => {
const _data = u.payload.toJSON();
_data['$key'] = u.key;
this.inactiveUsers.unshift(_data);
})
With this, the $key is part of your data and if you want to update it, you could do this:
this.db.database.ref(purchaserequests).child($key).update(item);
And, if you want to update one or more parameters:
this.db.database.ref(`purchaserequests/${$key}`).update({ [atribute]: value });

Related

Updating state when database gets updated

I got a schema looking something like this:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
//Create Schema
const PhoneNumbersSchema = new Schema({
phone_numbers: {
phone_number: 072382838232
code: ""
used: false
},
});
module.exports = PhoneNumbers = mongoose.model(
"phonenumbers",
PhoneNumbersSchema
);
And then I got an end-point that gets called from a 3rd party application that looks like this:
let result = await PhoneNumbers.findOneAndUpdate(
{ country_name: phoneNumberCountry },
{ $set: {"phone_numbers.$[elem1].services.$[elem2].sms_code": 393} },
{ arrayFilters: [ { "elem1.phone_number": simNumberUsed }, { "elem2.service_name": "steam" } ] },
Basically the end-point updates the "code" from the phone numbers in the database.
In react this is how I retrieve my phone numbers from the state:
const phonenumbers_database = useSelector((state) => {
console.log(state);
return state.phonenumbers ? state.phonenumbers.phone_numbers_details : [];
});
Every time the code gets changed in my database from the API call I would like to update "phonenumbers_database" in my state automatically.
How would I be able to do that?
MongoDB can actually watch for changes to a collection or a DB by opening a Change Stream.
First, you would open up a WebSocket from your React app to the server using something like Socket.io, and then watch for changes on your model:
PhoneNumbers
.watch()
.on('change', data => socket.emit('phoneNumberUpdated', data));
Your third party app will make the changes to the database to your API, and then the changes will be automatically pushed back to the client.
You could do a polling and check the Database every N secs or by using change streams
After that, to notify your frontend app, you need to use WebSockets, check on Socket IO

Storing current logged in userId in a variable using React SPFx

I am new to SPFx and wrapping my head around getting the current logged in user on SPFx using React , I did refer to a few posts here , However couldn't find the resolution I've been looking for.
My end goal is to get the current logged in userID and store the value in a variable to be used in RestAPI later
Below is the code sample
public componentdidmount() {
var u;
sp.web.currentUser.get().then(
(user) => {
console.log(user);
u = user.UserId;
},
(errorResponse) => {
debugger;
console.log(errorResponse);
}
); //Get Current User*/
sp.web.lists
.getByTitle("SampleList")
.items.select(
"*",
"CourseName/Title",
"Attendee/Title",
"Attendee/ID",
"Status/Title",
"Completed",
"AttachmentFiles"
)
.expand("CourseName", "Attendee", "Status", "AttachmentFiles")
.filter("Status/Title eq 'Not Started' and Attendee eq '" + u + "'")
.top(4999)
.orderBy("Created", false)
.get()
.then((response) => {
let getdetails = response.map((item) => new EntityListItems(item));
});
}
While debugging , I get an undefined value on the variable u.
I would be grateful if someone could help me with storing the current logged in userID to a variable so that I can pass the value to the Rest call.
Any help would be appreciated.
The request of getting user info is Asynchronous, it cannot promise the second request is called after the variable u has set a value. Hence we'd like to suggest you put the second request in the callback, this should make sure the user info has already been acquired.
And there is a quick method to get current user id:
this.context.pageContext.legacyPageContext["userId"]
legacyPageContext is similar to _spPageContextInfo. Refer to below blog to get more details:
https://medium.com/#rjesh/access-sppagecontextinfo-details-in-sharepoint-framework-8daa3479bf27
BR
I use this: this.context.pageContext.user.email

How to get a count of number of documents in a collection with Cloud Firestore [duplicate]

This question already has answers here:
Cloud Firestore collection count
(29 answers)
Closed 10 months ago.
In Firestore, how can I get the total number of documents in a collection?
For instance if I have
/people
/123456
/name - 'John'
/456789
/name - 'Jane'
I want to query how many people I have and get 2.
I could do a query on /people and then get the length of the returned results, but that seems a waste, especially because I will be doing this on larger datasets.
You currently have 3 options:
Option 1: Client side
This is basically the approach you mentioned. Select all from collection and count on the client side. This works well enough for small datasets but obviously doesn't work if the dataset is larger.
Option 2: Write-time best-effort
With this approach, you can use Cloud Functions to update a counter for each addition and deletion from the collection.
This works well for any dataset size, as long as additions/deletions only occur at the rate less than or equal to 1 per second. This gives you a single document to read to give you the almost current count immediately.
If need need to exceed 1 per second, you need to implement distributed counters per our documentation.
Option 3: Write-time exact
Rather than using Cloud Functions, in your client you can update the counter at the same time as you add or delete a document. This means the counter will also be current, but you'll need to make sure to include this logic anywhere you add or delete documents.
Like option 2, you'll need to implement distributed counters if you want to exceed per second
Aggregations are the way to go (firebase functions looks like the recommended way to update these aggregations as client side exposes info to the user you may not want exposed) https://firebase.google.com/docs/firestore/solutions/aggregation
Another way (NOT recommended) which is not good for large lists and involves downloading the whole list: res.size like this example:
db.collection("logs")
.get()
.then((res) => console.log(res.size));
If you use AngulareFire2, you can do (assuming private afs: AngularFirestore is injected in your constructor):
this.afs.collection(myCollection).valueChanges().subscribe( values => console.log(values.length));
Here, values is an array of all items in myCollection. You don't need metadata so you can use valueChanges() method directly.
Be careful counting number of documents for large collections with a cloud function. It is a little bit complex with firestore database if you want to have a precalculated counter for every collection.
Code like this doesn't work in this case:
export const customerCounterListener =
functions.firestore.document('customers/{customerId}')
.onWrite((change, context) => {
// on create
if (!change.before.exists && change.after.exists) {
return firestore
.collection('metadatas')
.doc('customers')
.get()
.then(docSnap =>
docSnap.ref.set({
count: docSnap.data().count + 1
}))
// on delete
} else if (change.before.exists && !change.after.exists) {
return firestore
.collection('metadatas')
.doc('customers')
.get()
.then(docSnap =>
docSnap.ref.set({
count: docSnap.data().count - 1
}))
}
return null;
});
The reason is because every cloud firestore trigger has to be idempotent, as firestore documentation say: https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees
Solution
So, in order to prevent multiple executions of your code, you need to manage with events and transactions. This is my particular way to handle large collection counters:
const executeOnce = (change, context, task) => {
const eventRef = firestore.collection('events').doc(context.eventId);
return firestore.runTransaction(t =>
t
.get(eventRef)
.then(docSnap => (docSnap.exists ? null : task(t)))
.then(() => t.set(eventRef, { processed: true }))
);
};
const documentCounter = collectionName => (change, context) =>
executeOnce(change, context, t => {
// on create
if (!change.before.exists && change.after.exists) {
return t
.get(firestore.collection('metadatas')
.doc(collectionName))
.then(docSnap =>
t.set(docSnap.ref, {
count: ((docSnap.data() && docSnap.data().count) || 0) + 1
}));
// on delete
} else if (change.before.exists && !change.after.exists) {
return t
.get(firestore.collection('metadatas')
.doc(collectionName))
.then(docSnap =>
t.set(docSnap.ref, {
count: docSnap.data().count - 1
}));
}
return null;
});
Use cases here:
/**
* Count documents in articles collection.
*/
exports.articlesCounter = functions.firestore
.document('articles/{id}')
.onWrite(documentCounter('articles'));
/**
* Count documents in customers collection.
*/
exports.customersCounter = functions.firestore
.document('customers/{id}')
.onWrite(documentCounter('customers'));
As you can see, the key to prevent multiple execution is the property called eventId in the context object. If the function has been handled many times for the same event, the event id will be the same in all cases. Unfortunately, you must have "events" collection in your database.
Please check below answer I found on another thread. Your count should be atomic. Its required to use FieldValue.increment() function in such case.
https://stackoverflow.com/a/49407570/3337028
firebase-admin offers select(fields) which allows you to only fetch specific fields for documents within your collection. Using select is more performant than fetching all fields. However, it is only available for firebase-admin and firebase-admin is typically only used server side.
select can be used as follows:
select('age', 'name') // fetch the age and name fields
select() // select no fields, which is perfect if you just want a count
select is available for Node.js servers but I am not sure about other languages:
https://googleapis.dev/nodejs/firestore/latest/Query.html#select
https://googleapis.dev/nodejs/firestore/latest/CollectionReference.html#select
Here's a server side cloud function written in Node.js which uses select to count a filtered collection and to get the IDs of all resulting documents. Its written in TS but easily converted to JS.
import admin from 'firebase-admin'
// https://stackoverflow.com/questions/46554091/cloud-firestore-collection-count
// we need to use admin SDK here as select() is only available for admin
export const videoIds = async (req: any): Promise<any> => {
const id: string = req.query.id || null
const group: string = req.query.group || null
let processed: boolean = null
if (req.query.processed === 'true') processed = true
if (req.query.processed === 'false') processed = false
let q: admin.firestore.Query<admin.firestore.DocumentData> = admin.firestore().collection('videos')
if (group != null) q = q.where('group', '==', group)
if (processed != null) q = q.where('flowPlayerProcessed', '==', processed)
// select restricts returned fields such as ... select('id', 'name')
const query: admin.firestore.QuerySnapshot<admin.firestore.DocumentData> = await q.orderBy('timeCreated').select().get()
const ids: string[] = query.docs.map((doc: admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData>) => doc.id) // ({ id: doc.id, ...doc.data() })
return {
id,
group,
processed,
idx: id == null ? null : ids.indexOf(id),
count: ids.length,
ids
}
}
The cloud function HTTP request completes within 1 second for a collection of 500 docs where each doc contains a lot of data. Not amazingly performant but much better than not using select. Performance could be improved by introducing client side caching (or even server side caching).
The cloud function entry point looks like this:
exports.videoIds = functions.https.onRequest(async (req, res) => {
const response: any = await videoIds(req)
res.json(response)
})
The HTTP request URL would be:
https://SERVER/videoIds?group=my-group&processed=true
Firebase functions detail where the server is located on deployment.
Following Dan Answer: You can have a separated counter in your database and use Cloud Functions to maintain it. (Write-time best-effort)
// Example of performing an increment when item is added
module.exports.incrementIncomesCounter = collectionRef.onCreate(event => {
const counterRef = event.data.ref.firestore.doc('counters/incomes')
counterRef.get()
.then(documentSnapshot => {
const currentCount = documentSnapshot.exists ? documentSnapshot.data().count : 0
counterRef.set({
count: Number(currentCount) + 1
})
.then(() => {
console.log('counter has increased!')
})
})
})
This code shows you the complete example of how to do it:
https://gist.github.com/saintplay/3f965e0aea933a1129cc2c9a823e74d7
Get a new write batch
WriteBatch batch = db.batch();
Add a New Value to Collection "NYC"
DocumentReference nycRef = db.collection("cities").document();
batch.set(nycRef, new City());
Maintain a Document with Id as Count and initial Value as total=0
During Add Operation perform like below
DocumentReference countRef= db.collection("cities").document("count");
batch.update(countRef, "total", FieldValue.increment(1));
During Delete Operation perform like below
DocumentReference countRef= db.collection("cities").document("count");
batch.update(countRef, "total", FieldValue.increment(-1));
Always get Document count from
DocumentReference nycRef = db.collection("cities").document("count");
I created an NPM package to handle all counters:
First install the module in your functions directory:
npm i adv-firestore-functions
then use it like so:
import { eventExists, colCounter } from 'adv-firestore-functions';
functions.firestore
.document('posts/{docId}')
.onWrite(async (change: any, context: any) => {
// don't run if repeated function
if (await eventExists(context)) {
return null;
}
await colCounter(change, context);
}
It handles events, and everything else.
If you want to make it a universal counter for all functions:
import { eventExists, colCounter } from 'adv-firestore-functions';
functions.firestore
.document('{colId}/{docId}')
.onWrite(async (change: any, context: any) => {
const colId = context.params.colId;
// don't run if repeated function
if (await eventExists(context) || colId.startsWith('_')) {
return null;
}
await colCounter(change, context);
}
And don't forget your rules:
match /_counters/{document} {
allow read;
allow write: if false;
}
And of course access it this way:
const collectionPath = 'path/to/collection';
const colSnap = await db.doc('_counters/' + collectionPath).get();
const count = colSnap.get('count');
Read more: https://code.build/p/9DicAmrnRoK4uk62Hw1bEV/firestore-counters
GitHub: https://github.com/jdgamble555/adv-firestore-functions
Use Transaction to update the count inside the success listener of your database write.
FirebaseFirestore.getInstance().runTransaction(new Transaction.Function<Long>() {
#Nullable
#Override
public Long apply(#NonNull Transaction transaction) throws FirebaseFirestoreException {
DocumentSnapshot snapshot = transaction
.get(pRefs.postRef(forumHelper.getPost_id()));
long newCount;
if (b) {
newCount = snapshot.getLong(kMap.like_count) + 1;
} else {
newCount = snapshot.getLong(kMap.like_count) - 1;
}
transaction.update(pRefs.postRef(forumHelper.getPost_id()),
kMap.like_count, newCount);
return newCount;
}
});

Retrieve data from the new Firebase

I'm using the new Firebase at firebase.google.com for my website.
My database structure:
root
___ [auto-generated ID]
______ key1 : value
______ key2 : value
I followed the method in their document to get value of key1 as below:
database.ref('root/').on('value', function(snapshot) {
var obj = snapshot.val();
alert(obj[0].key1);
});
But I always get the error says that obj[0] is undefined and I can't read its properties. What did I do wrong and how to properly retrieve data from firebase?
Note: I'm using Angularjs for my website
Got it! alert(obj[0].key1); gives you the error!
instead, try using the below code:
database.ref('root/').on('value', function(snapshot) {
var obj = snapshot.val();
obj.forEach(function(data){
alert(data.key());
}
});
Also the usage of /root is not advised. Instead, use the url of the data.
The normal format of this url is: <uniqueAppName>.firebaseio.com
var url = "dazzling-inferno-2613.firebaseio.com";
database.ref(url).on('value', function(snapshot) {
//concepts
});

Firebase remove function do not work

I'm building an app with Firebase and AngularJS and I have a table with my users.
From one of my view I want to create a form permit to delete users from the Firebase table .
So I have a drop down menu with my users names and submit button.
I wrote a function to retrive the name of the user from the form and combine it with my url location of the user table, in fact the table has user name as id :
$scope.Delete_user = function(name) {
var testRef = new Firebase("https://alex-jpcreative.firebaseio.com/users")
var newRef = testRef + '/' + name;
$scope.removeUser(newRef);
}
In this function I called the removeUser one that is a function I found in Firebase doc to delete a item from the table :
$scope.removeUser = function(ref) {
ref.remove(function(error) {
alert(error ? "Uh oh!" : "Success!");
});
}
I can see the first function working fine pass the right name of users and combine it with the URL but then I have this error and it doesn't work:
TypeError: Object https://alex-jpcreative.firebaseio.com/users/Alex_dev_JPC has no method 'remove'
You need to use the child method to get the reference to the user object, rather than just appending the string to the end:
$scope.Delete_user = function(name) {
var testRef = new Firebase("https://alex-jpcreative.firebaseio.com/users");
var newRef = testRef.child(name);
$scope.removeUser(newRef);
}
See the Firebase documentation for more details.

Resources