Angularfire2 function is returning infinite loop - javascript

I am trying to show in a table a flag icon if the person who made a booking has had an order canceled in the past.
When admin changes a booking status to cancelled it sets a 'hasCanceled' boolean to true on the user who made the booking in firebase.
in my HTML table I am using an *ngIF to run a function and show the icon if the function returns true passing the order key as a parameter to the function.
<td><i *ngIf="hasUserCanceled(order.$key)" class="fa fa-flag"></i></td>
and here is the function itself:
hasUserCanceled(key) {
//Get the userId from the order
this.order = this.af.object('/orders/' + key);
this.order.subscribe(res => {
this.userId = res.userId;
});
this.canceledUsers = this.af.object('/users/' + this.userId);
this.canceledUsers.subscribe((res) => {
this.hasCanceled = res.hasCanceled
});
if (this.hasCanceled == true) {
console.log('THIS IS TRUE')
return true
}
}
I can see the flag in the right places of the users who have canceled, so the functions purpose is working, But the issue is that in the console, the function is running over and over and not stopping. Is there anything noticeable that I am doing wrong?
Console log Image:

Related

EnableRule in ribbon cannot get boolean value from async function

I want to return true or false from the function showHideAddNewButton. I have an EnableRule in a ribbon button, which calls a custom rule that calls this function showHideAddNewButton. On passing either true, which will show the button, or false, which will hide the button.
I have to access statuscode (Status Reason) and statecode (Status) fields on the entity. I have created a query using the Xrm.WebApi.retrieveMultipleRecords, but cannot get it to return a flag. I want the retrieveMultipleRecords method to only execute on and never be called again but 'return true' below gets executed setting my button to true always.
function showHideAddNewCsrsRecalculation(primaryControl){
var fileNumber = primaryControl.getAttribute("ssg_filenumber").getValue();
Xrm.WebApi.retrieveMultipleRecords("rrg_csrsfile", "?$select=statuscode,statecode,rr_filenumber&$filter=rr_filenumber eq '" + fileNumber + "'").then(
function success(result) {
for (var i = 0; i < result.entities.length; i++) {
var statusCode = result.entities[i].statecode;
var statusReasonCode = result.entities[i].statuscode;
//if draft make button invisible
if (statusReasonCode == 8676725)
return false;
//if submitted make button invisible
if (statusReasonCode == 8676726)
return false;
//if inactive make button invisible
if (statusCode == 1)
return false;
}
},
function (error) {
console.log(error.message);
// handle error conditions
}
);
//if draft make button invisible
//if (primaryControl.getAttribute("statuscode").getValue() == 867670025)
// return false;
//if submitted make button invisible
//if (primaryControl.getAttribute("statuscode").getValue() == 867670026)
// return false;
//if inactive make button invisible
//if (primaryControl.getAttribute("statecode") != 'undefined' && primaryControl.getAttribute("statecode").getValue() == 1)
// return false;
//other options make button visible
return true; --> This keeps getting called as a result my button is always visible
}
Function Xrm.WebApi.retrieveMultipleRecords returns a promise, not an actual boolean value. The function is executed asynchronously, so immediately after the call to this function the next line is executed and that line always returns true.
In fact it is not possible to make an asynchronous call synchronous. Instead we can follow another approach by following these steps:
Do the query in the form's onload function and store the result in a variable.
Refresh the ribbon.
Create a ribbon button handler returning the variable's value.
let isRecalculationButtonVisible = false;
function onLoad(context) {
const formContext = context.getFormContext();
const filter = "$filter=rr_filenumber eq '"
+ formContext.getAttribute("ssg_filenumber").getValue()
+ "' and (statecode eq 1 or statuscode eq 8676725 or statuscode eq 8676726)";
Xrm.WebApi.retrieveMultipleRecords("rrg_csrsfile", "?$select=rrg_csrsfileid&$top=1&" + filter)
.then(function (result) {
isRecalculationButtonVisible = result.entities.length === 0;
})
.catch(function (error) {
console.log(error.message);
})
.finally(() => {
formContext.ui.refreshRibbon(false);
});
}
function showHideAddNewCsrsRecalculation() {
return isRecalculationButtonVisible;
}
As you probably already noticed I added a few improvements.
All your button needs to know is if there are any records meeting specific conditions, so there is no need to actually retrieve them. Therefore these conditions can simply be placed in the query's filter. I also added a $top=1, because the number of records meeting the conditions is not relevant here. As a consequence the only check that needs to be done is whether a record is returned or not.
As explained, retrieveMultipleRecords returns a promise. The recommended error handling for promises is adding a catch function at the end of the chain.

Get value out of firestore promise function

I'm trying to integrate the Paypal Smart Button and Firestore. Everything was working fine until I tried to add in a check to see if the user document already existed in Firestore to throw an error so users can't register twice.
The next Paypal action (onApproval) requires the subscription id, which is created by the actions.subscription.create() function.
Just to make sure it wasn't a problem with the way the function fired, I also ommitted it, and just had paypalCreate = 'success'. But when logging the result of paypalCreate, at different levels of the nested functions, these were the results and their order:
On Fail (user exists):
Third Creation (undefined)
First Creation error
Second Creation error
On Success (user doesn't exist):
Third Creation (undefined)
First Creation success
So why is the 'Third Creation' always logged first? I'm guessing because the promise hasn't completed. Do I need to add a wait somewhere? And why is it undefined?
And on success, why isn't the 'Second Creation' returned? How is it any different to the failed result?
I need paypalCreate returned at the end of this createSubscription function, so the next step can use the result of it.
The code simplified:
createSubscription: function(data, actions) {
// Get snapshot
db.collection('users').doc(email).get().then((querySnapshot) => {
let paypalCreate;
// Check if email exists
if (querySnapshot.exists) {
// Register as error if user exists <- This works
paypalCreate = 'error';
console.log('First Creation ' + paypalCreate);
} else {
console.log("user doesn't exists");
// Saving new user to Firestore <- This works
db.collection('users').doc(email).set({
name: name,
email: email,
created: firebase.firestore.Timestamp.now()
}),
// Choose plan to subscribe to in PayPal
paypalCreate = actions.subscription.create({
'plan_id': 'XXXX'
});
console.log('First Creation ' + paypalCreate);
return paypalCreate;
};
console.log('Second Creation ' + paypalCreate);
return paypalCreate;
}),
console.log('Third Creation ' + paypalCreate);
return paypalCreate;
},

Firebase Function admin.database() null parameter in a transaction

I'm using Firebase Functions with the "Spark Plan" (free). This is part of my function:
return query.once("value").then(function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var childData = childSnapshot.val();
if (childData.displayName === ally) {
existAlly = true;
console.log('uid: '+uid)
var ref = admin.database().ref('users/'+uid).transaction(function (current_value) {
console.log('current_value: '+uid)
current_value.mainAlly = ally;
current_value.coins = (current_value.coins || 0) + 10
return current_value;
}).then(() => {
console.log('New Ally added');
return true;
});
...
Here the logs, you can see "'current_value: null'
But, ss you can see in the next picture, the "ref" is correct:
So, is it a billing issue? The "admin.database()" stops working after a while? Or is it something else?
Thanks!
EDIT: I just did another test, and now the error is in "coins", with the same code:
Everything is working as expected. When working with transactions, you can expect that your handler function will get called the first time with null (which you will have to check for), then again with the actual contents of the database. You should review the documentation, and pay special attention to the note that says:
Transaction Function is Called Multiple Times
Your transaction handler is called multiple times and must be able to
handle null data. Even if there is existing data in your database it
may not be locally cached when the transaction function is run.

Wait for firebase.auth initialization before reading another function

I am very new with firebase and javascript.
My project: Build a private messaging app. To do that, I want to define a sub collection in firestore for private messaging using the current user id and the destination user id.
Here is the function that allows this:
// generate the right SubCollection depending on current User and the User he tries to reach
function dmCollection(toUid) {
if (toUid === null) {
// If no destination user is definer, we set it to the below value
toUid = 'fixed_value';
};
const idPair = [firebase.auth().currentUser.uid, toUid].join('_').sort();
return firebase.firestore().collection('dms').doc(idPair).collection('messages');
};
My problem: I want to use the firebase.auth().currentUser.uid attribute, but it looks like the function is not waiting for firebase.auth initialization. How can I fix this problem?
Additional information:
I have two functions that are calling the first one (dmCollection):
// retrieve DMs
function messagesWith(uid) {
return dmCollection(uid).orderBy('sent', 'desc').get();
};
// send a DM
function sendDM(toUid, messageText) {
return dmCollection(toUid).add({
from: firebase.auth().currentUser.uid,
text: messageText,
sent: firebase.firestore.FieldValue.serverTimestamp(),
});
};
If I correctly understand your problem ("it looks like the function is not waiting for firebase.auth initialization"), you have two possible solutions:
Solution 1: Set an observer on the Auth object
As explained in the documentation, you can set an observer on the Auth object with the onAuthStateChanged() method:
By using an observer, you ensure that the Auth object isn't in an
intermediate state—such as initialization—when you get the current
user.
So you would modify your code as follows:
// retrieve DMs
function messagesWith(uid) {
return dmCollection(uid).orderBy('sent', 'desc').get();
};
// send a DM
function sendDM(toUid, messageText) {
return dmCollection(toUid).add({
from: firebase.auth().currentUser.uid,
text: messageText,
sent: firebase.firestore.FieldValue.serverTimestamp(),
});
};
// generate the right SubCollection depending on current User and the User he tries to reach
function dmCollection(toUid) {
if (toUid === null) {
// If no destination user is definer, we set it to the below value
toUid = 'fixed_value';
};
const idPair = [firebase.auth().currentUser.uid, toUid].join('_').sort();
return firebase.firestore().collection('dms').doc(idPair).collection('messages');
};
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
var messageText = '....';
sendDM(user.uid, messageText)
} else {
// No user is signed in.
}
});
Solution 2: Use the currentUser property
You could also "get the currently signed-in user by using the currentUser property" as explained in the same doc. "If a user isn't signed in, currentUser is null".
In this case you would do:
var user = firebase.auth().currentUser;
if (user) {
var messageText = '....';
sendDM(user.uid, messageText);
} else {
// No user is signed in.
// Ask the user to sign in, e.g. redirect to a sign in page
}
Which solution to choose?
It depends how you want to call the function(s) based on the user uid.
If you want to call the function(s) immediately after the user is signed in, use Solution 1.
If you want to call the function(s) at another specific moment (e.g. following a user action), use Solution 2.

Calling then function after fetching data from firebase in ionic 3

I want fetch data from firebase after that I want to execute another function. Second function have to wait until first one is complete .
this.oAngularFireDatabase.database.ref('Users').orderByKey()
.on('value', snapshot => {
if (snapshot.hasChildren()) {
snapshot.forEach(innerSnap => {
if (innerSnap.hasChild(user.uid)) {
//User role key
this.loggedInUserUserRoleKey = innerSnap.key;
//User id
this.loggedInUserId = user.uid;
//User name
this.loggedInUserName = innerSnap.child(user.uid).child("user_name").val();
if (innerSnap.child(user.uid).hasChild("user_image")) {
//User Image
this.loggedInUserImage = innerSnap.child(user.uid).child("user_image").val();
}
return false;
}
})
}
})
I can't call then function after on it gives me an error.
In my above code, I want call another function after all data are fetch from firebase.
The Firebase on() method can fire multiple times: once when it initially loads the data, and again whenever the data changes. Since a promise (the thing you call then() on) can only resolve once, on() can't return a promise.
There are two options here:
You want to only load the data once.
If this is the case, you should use Firebase's once() method, which does return a promise.
this.oAngularFireDatabase.database.ref('Users').orderByKey()
.once('value').then(snapshot => {
if (snapshot.hasChildren()) {
snapshot.forEach(innerSnap => {
if (innerSnap.hasChild(user.uid)) {
//User role key
this.loggedInUserUserRoleKey = innerSnap.key;
//User id
this.loggedInUserId = user.uid;
//User name
this.loggedInUserName = innerSnap.child(user.uid).child("user_name").val();
if (innerSnap.child(user.uid).hasChild("user_image")) {
//User Image
this.loggedInUserImage = innerSnap.child(user.uid).child("user_image").val();
}
return false;
}
})
}
}).then(value => {
// TODO: perform subsequent action on boolean value
})
You want to listen for changes on the data too.
If this is the case, you should put the subsequent action you want to take into the on() callback:
this.oAngularFireDatabase.database.ref('Users').orderByKey()
.on('value', snapshot => {
if (snapshot.hasChildren()) {
snapshot.forEach(innerSnap => {
if (innerSnap.hasChild(user.uid)) {
//User role key
this.loggedInUserUserRoleKey = innerSnap.key;
//User id
this.loggedInUserId = user.uid;
//User name
this.loggedInUserName = innerSnap.child(user.uid).child("user_name").val();
if (innerSnap.child(user.uid).hasChild("user_image")) {
//User Image
this.loggedInUserImage = innerSnap.child(user.uid).child("user_image").val();
}
}
})
// TODO: perform subsequent action on data
}
})
Note that both of these operations look pretty expensive for what they're trying to accomplish: scanning a JSON tree for a specific value is an anti-pattern in Firebase, and typically means you should modify/augment your JSON to allow a direct lookup or query.
For example, I suspect you now have a structure like /Users/$randomkey/$uid: { ..user data... }. For better performance, consider storing the user data directly under their UID: /Users/$uid: { ..user data... }. This removes the need for a query, and allows you to directly load the data for a user from this.oAngularFireDatabase.database.ref('Users').child(user.uid).

Categories