i have a problem in vuelidate that causing infinite loop. Here's a brief process of my project. I have a datatable, I used Firebase OnSnapShot function to paginate the table. The table have action column that will show a modal when clicked. When i'm updating the value came from the table, vuelidate isUnique functions fires an infinite loop.
P.S I'm detaching the listener before viewing the modal
Output of infinite loop :
Here's my function to load the datatable:
async loadData(firebasePagination) {
// query reference for the messages we want
let ref = firebasePagination.db;
// single query to get startAt snapshot
ref.orderBy(firebasePagination.orderColumn, 'asc')
.limit(this.pagination.rowsPerPage).get()
.then((snapshots) => {
// save startAt snapshot
firebasePagination.start = snapshots.docs[snapshots.docs.length - 1]
// create listener using endAt snapshot (starting boundary)
let listener = ref.orderBy(firebasePagination.orderColumn)
.endAt(firebasePagination.start)
.onSnapshot((datas) => {
if(!datas.empty){
datas.docs.forEach((data, index) => {
//remove duplicates
console.log("here")
firebasePagination.data = firebasePagination.data.filter(x => x.id !== data.id)
//push to the data
firebasePagination.data.push(Object.assign({id : data.id },data.data()))
if(datas.docs.length-1 === index){
//sort
firebasePagination.data.sort((a, b) => (a[firebasePagination.orderColumn] > b[firebasePagination.orderColumn]) ? 1 : -1)
//get the current data
firebasePagination.currentData = this.getCurrentData(firebasePagination)
}
})
}
})
// push listener
firebasePagination.listeners.push(listener)
})
return firebasePagination;
}
Here's my function when clicking the action (Modal):
switch(items.action) {
case 'edit':
//detaching listener
this.firebasePagination.listeners.forEach(d => {
d()
});
items.data.isEdit = true;
this.clickEdit(items.data);
break;
}
}
Here's my isUnique function:
validations: {
department: {
name: {
required,
async isUnique(value){
if(value.trim() === ''){
return false;
}
if(strictCompareStrings(this.departmentName, value)){
this.departmentError.isActive = true;
this.departmentError.isValid = true;
return true;
}
const result = await checkIfUnique(DB_DEPARTMENTS, {nameToLower : this.department.name.toLowerCase()});
console.log("GOES HERE")
if(!result.isValid){
result.errorMessage = result.isActive ?
'Department already exists.' : 'Department has been archived.';
}
this.departmentError = Object.assign({}, result);
return this.departmentError.isValid;
}
}
}
}
Here's my checkUnique function :
export const checkIfUnique = (db, nameObj, isTrim = true) => {
return new Promise(resolve => {
const nameObjKey = Object.keys(nameObj)[0];
const name = isTrim ? nameObj[nameObjKey].replace(/\s+/g,' ').trim().toLowerCase() : nameObj[nameObjKey].trim();
db().where(nameObjKey, '==', name).get()
.then((doc) => {
let result = {isActive: false, isValid: true, errorMessage: ''};
if(!doc.empty){
result.isActive = doc.docs[0].data().isActive;
result.isValid = false;
}
resolve(result);
})
});
};
Looked into another example of using isUnique from here and considered that you might have to return the Promise itself from the isUnique itself.
isUnique(value) {
if (value === '') return true
return new Promise((resolve, reject) => {
yourQueryMethod(`....`)
.then(result => resolve(result))
.catch(e => reject(false));
})
}
But then again, we still have an open issue regarding Infinite loop when using a promise-based validate #350.
Related
How could I improve this method of rendering only when both variables are met as true, to allow the renderFilters() method to be called:
These two variables are filled asynchronously through 2 API methods:
//getManager()
this.isLoadingManager = true;
//getPdiPOrganization()
this.isLoadingPdiOrganization = true;
promiseRender() {
let interval = setInterval(() => {
if (this.isLoadingManager && this.isLoadingPdiOrganization) {
clearInterval(interval);
this.renderFilters();
} else {
setTimeout(() => {
clearInterval(interval);
this.renderFilters();
}, 5000)
}
}, 500);
}
The problem is that it's very slow... it's calling long after the APIs are called...
Maybe some feature of angular itself, if anyone has a better solution...
const observable = forkJoin({
loading1:this.isLoadingManager,
loading2:this.isLoadingPdiOrganization
});
observable.subscribe({
next: (results) => {
const obs1Val = results[0];
const obs2Val = results[1];
if (obs1Val && obs2Val) {
this.renderFilters();
}
}
})
Or:
const myObservable = Observable.of(this.isLoadingManager && this.isLoadingPdiOrganization);
const myObserver = {
next: (result: Boolean) => this.renderFilters(),
};
myObserver.next(true);
myObservable.subscribe(myObserver);
Adding the methods:
getManager() {
if (this.fromAdminPage && localStorage.getItem('_receivers_pdi')) {
this.meetingService.getIsManager()
.subscribe(res => {
this.showPdiToastNotification = res;
this.isLoadingManager = true;
});
}
}
getPdiPOrganization() {
const url = this.publicEndpoint ? 'current/organization/pdi/configuration' : 'api/current/organization/pdi/configuration';
const requestOptions = {
params: new CustomHttpParams({ isPublicTokenUrl: this.publicEndpoint })
};
this.http.get<any>(url, requestOptions).subscribe(resp => {
this.isLoadingPdiOrganization = true;
this.pdiOrgConfig = resp || {};
this.updatePdiReferenceType(this.pdiOrgConfig);
});
}
You can use forkjoin to subscribe to two observables at the same time. I would stick with using RxJs operators for cases like these. You can read more about forkJoin here.
forkJoin([obs1, obs2]).subscribe({
next: (results) => {
const obs1Val = results[0];
const obs2Val = results[1];
if (obs1Val && obs2Val) {
this.renderFilters();
}
}
});
I am trying to write a function that takes into account 3 conditions whenever Stores/{storeId}/{departmentId}/{productId} gets triggered and write new data in ref.child('Home').child('Chiep').child(departmentId).child(productId).
1) When there is no data in firestore, I need to fill up all the fields in Realtime DB, by making queries in 2 different firestore's nodes: Stores and Products in order to take their images.
2) When a change is made in Stores node and it comes from the same {storeId}, I just need to update some data without making any additional query.
3) And finally, when a change is made in Stores node and it comes from other {storeId}, I need to make only one query in the Stores node.
exports.homeChiepest = functions.firestore
.document('Stores/{storeId}/{departmentId}/{productId}')
.onWrite((change, context) => {
const storeId = context.params.storeId;
const departmentId = context.params.departmentId;
const productId = context.params.productId;
const ref = admin.database().ref();
// Get an object with the current document value.
// If the document does not exist, it has been deleted.
const document = change.after.exists ? change.after.data() : null;
// Get an object with the previous document value (for update or delete)
const oldDocument = change.before.exists ? change.before.data() : null;
// Prevent infinite loops
if (!change.after.exists) {
console.log('DATA DELETED RETURN NULL');
return null;
}
const newPrice = document.price;
const newTimestamp = document.timestamp;
return ref.child('Home').child('Chiep')
.child(departmentId).child(productId)
.once('value')
.then(dataSnapshot => {
if (dataSnapshot.val() !== null) {
console.log('CHIEP DOES exist');
const oldPrice = dataSnapshot.val().price;
const storeKey = dataSnapshot.val().storeKey;
if (storeId === storeKey) {
console.log('SAME STORE - Change price and timestamp');
var newChiepest = {
timestamp: newTimestamp,
price: newPrice
};
return dataSnapshot.ref.update(newChiepest);
} else {
console.log('OTHER STORE - Verify if price is chieper...');
if (newPrice <= oldPrice) {
console.log('NEW PRICE: '+newPrice+' is chieper than the older one: '+oldPrice);
return change.after.ref.parent.parent.get().then(doc => { // HERE Avoid nesting promises
newStoreImg = doc.data().image;
var newStoreChiep = {
price: newPrice,
storeImg: newStoreImg,
storeKey: storeId,
timestamp: newTimestamp
};
return dataSnapshot.ref.update(newStoreChiep);
});
} else {
console.log('NEW PRICE: '+newPrice+' is mode EXPENSIVE than the older one: '+oldPrice);
}
return null;
}
} else {
console.log('data does NOT exist, so WRITE IT!');
let getStoreData = change.after.ref.parent.parent.get();
let getProductData = admin.firestore().collection('Products').doc('Departments').collection(departmentId).doc(productId).get();
return Promise.all([getStoreData, getProductData]).then(values => { // HERE Avoid nesting promises
const [store, product] = values;
var newHomeChiepest = {
depId: departmentId,
price: newPrice,
prodImg: product.data().image,
prodKey: productId,
storeKey: storeId,
storeImg: store.data().image,
timestamp: newTimestamp
};
return dataSnapshot.ref.set(newHomeChiepest);
});
}
})
.catch(error => {
console.log('Catch error reading Home: ',departmentId ,'/', productId,'; message: ',error);
return false;
});
});
The problem is: different possibilities of querying or not querying another firestore node led me to a warning while uploading the Clound Function, that is:
warning Avoid nesting promises promise/no-nesting
I appreciate any help to refactor this code.
You could use a variable to manage a "shunting", depending on the different cases, as follows (untested):
exports.homeChiepest = functions.firestore
.document('Stores/{storeId}/{departmentId}/{productId}')
.onWrite((change, context) => {
const storeId = context.params.storeId;
const departmentId = context.params.departmentId;
const productId = context.params.productId;
const ref = admin.database().ref();
const document = change.after.exists ? change.after.data() : null;
// Prevent infinite loops
if (!change.after.exists) {
console.log('DATA DELETED RETURN NULL');
return null;
}
const newPrice = document.price;
const newTimestamp = document.timestamp;
let shunting; // <-- We manage the shunting through this variable
let chiepRef;
return ref.child('Home').child('Chiep')
.child(departmentId).child(productId)
.once('value')
.then(dataSnapshot => {
chiepRef = dataSnapshot.ref;
if (dataSnapshot.val() !== null) {
console.log('CHIEP DOES exist');
const oldPrice = dataSnapshot.val().price;
const storeKey = dataSnapshot.val().storeKey;
if (storeId === storeKey) {
shunting = 1
console.log('SAME STORE - Change price and timestamp');
var newChiepest = {
timestamp: newTimestamp,
price: newPrice
};
return chiepRef.update(newChiepest);
} else {
console.log('OTHER STORE - Verify if price is chieper...');
if (newPrice <= oldPrice) {
console.log('NEW PRICE: ' + newPrice + ' is chieper than the older one: ' + oldPrice);
shunting = 2
return change.after.ref.parent.parent.get();
} else {
console.log('NEW PRICE: ' + newPrice + ' is mode EXPENSIVE than the older one: ' + oldPrice);
shunting = 3
return null;
}
}
} else {
console.log('data does NOT exist, so WRITE IT!');
shunting = 4;
let getStoreData = change.after.ref.parent.parent.get();
let getProductData = admin.firestore().collection('Products').doc('Departments').collection(departmentId).doc(productId).get();
return Promise.all([getStoreData, getProductData])
}
})
.then(result => {
if (shunting === 2) {
const newStoreImg = result.data().image;
var newStoreChiep = {
price: newPrice,
storeImg: newStoreImg,
storeKey: storeId,
timestamp: newTimestamp
};
return chiepRef.update(newStoreChiep);
} else if (shunting === 4) {
const [store, product] = result;
const newHomeChiepest = {
depId: departmentId,
price: newPrice,
prodImg: product.data().image,
prodKey: productId,
storeKey: storeId,
storeImg: store.data().image,
timestamp: newTimestamp
};
return chiepRef.set(newHomeChiepest);
} else {
return null;
}
})
.catch(error => {
console.log('may be adapted, function of shunting', error);
return null;
});
});
This question already has answers here:
How to use promise in forEach loop of array to populate an object
(5 answers)
Closed 2 years ago.
On the firebase functions, I have next code:
app.post('/licence', (req, res) => {
let { email, machine_id, product_id } = req.body
let arr_product_ids = product_id.split(",").map(function (val) { return {product_id: val}; });
let res_to_print = '';
return new Promise((resolve, reject) => {
arr_product_ids.forEach(function(n){
res_to_print = asyncGetLicences(n.product_id, email, machine_id)
console.log('res_to_print')
console.log(res_to_print)
});
}).then((state) => {
console.log(state)
})
.catch((error) => {
console.log(error)
});
I need to call query two times to firebase query! So I call it in foreach loop.
Here is the function that needs to be called twice:
function asyncGetLicences(product_id, email, machine_id) {
licences_to_print = []
db.collection('licences', 'desc').where('email', '==', email).where('product_id', '==', product_id).get()
.then(data => {
let licences = []
data.forEach(doc => {
console.log(doc.data().email);
licences.push({
id: doc.id,
email: doc.data().email,
product: doc.data().product,
createdAt: doc.data().createdAt
});
});
if(typeof this.licences !== 'undefined' && this.licences.length > 0){
let string = email+machine_id+product_id+'55';
let api = md5(string);
let uppercase = api.toUpperCase()+'-';
licences_to_print.push(uppercase);
return licences_to_print
//return res.send('"'+uppercase+'"');//res.json(this.licences);
} else {
return licences_to_print
//return res.status(200).send('nothing to find');
}
})
}
I'm struggling with this simple promise...I had this in PHP and it was very easy, but node.js and firebase I got stuck!
Add all the promises in an array and insert into Promise.all() and then return this in the main function. This will collectively get the return from each promises asynchronously and return a single collective response.
app.post('/licence', (req, res) => {
let { email, machine_id, product_id } = req.body
let arr_product_ids = product_id.split(",").map(function (val) { return {product_id: val}; });
let res_to_print = '';
const promises = [] // Empty array
arr_product_ids.forEach(function(n){
promises.push(asyncGetLicences(n.product_id, email, machine_id));
});
return Promise.all(promises).then(res_to_print => {
console.log('res_to_print')
console.log(res_to_print)
}).catch((error) => {
console.log(error)
});
The second function:
function asyncGetLicences(product_id, email, machine_id) {
licences_to_print = []
return new Promise((resolve, reject) => {
db.collection('licences', 'desc').where('email', '==', email).where('product_id', '==', product_id).get()
.then(data => {
let licences = []
data.forEach(doc => {
console.log(doc.data().email);
licences.push({
id: doc.id,
email: doc.data().email,
product: doc.data().product,
createdAt: doc.data().createdAt
});
});
if(typeof this.licences !== 'undefined' && this.licences.length > 0){
let string = email+machine_id+product_id+'55';
let api = md5(string);
let uppercase = api.toUpperCase()+'-';
licences_to_print.push(uppercase);
resolve(licences_to_print)
//return res.send('"'+uppercase+'"');//res.json(this.licences);
} else {
resolve(licences_to_print)
//return res.status(200).send('nothing to find');
}
}))
I have a list of objects, and I wanted to filter it based on a specific string property.
openTasks: Task[]; //Initial list
inProgressTasks: Task[] = []; //Filtered list
getTasks(): void {
this.activatedRoute.paramMap.subscribe(params => {
this.projectId = +params.get('projectId');
if (this.projectId === 0) {
this.taskService.getTasks().subscribe(tasks => this.openTasks = tasks);
// HERE I ACQUIRE LIST OF OPEN TASKS
} else {
this.taskService.getTaskByProjectId(this.projectId).subscribe(tasks => this.openTasks = tasks);
// HERE I ACQUIRE LIST OF OPEN TASKS
}
// FILTER
this.inProgressTasks = this.openTasks.filter(task => task.state === 'IN_PROGRESS');
});
}
I received this error:
ERROR TypeError: Cannot read property 'filter' of undefined
Could you, please, help me with this error?
Your probably having problems here because of the async nature with which you fetch openTasks.
Try waiting for it to definitely finish before trying to filter it.
getTasks(): void {
this.activatedRoute.paramMap.subscribe(params => {
this.projectId = +params.get('projectId');
if (this.projectId === 0) {
this.taskService.getTasks().subscribe(tasks =>
{this.openTasks = tasks},
error => {},
() => { this.filterTasks()}
);
}
});
}
filterTasks() {
this.inProgressTasks = this.openTasks.filter(task => task.state === 'IN_PROGRESS');
}
The this.openTasks is not set yet. it is only set after the this.openTasks = tasks in subscribe. this should work.
this.activatedRoute.paramMap.subscribe(params => {
this.projectId = +params.get('projectId');
if (this.projectId === 0) {
this.taskService.getTasks()
.subscribe(tasks => {
this.openTasks = tasks;
// your code that requirest openTasks
this.inProgressTasks = this.openTasks.filter(task => task.state === 'IN_PROGRESS');
});
} else {
this.taskService.getTaskByProjectId(this.projectId)
.subscribe(tasks => {
this.openTasks = tasks
// your code that requirest openTasks
this.inProgressTasks = this.openTasks.filter(task => task.state === 'IN_PROGRESS');
});
}
});
I'm using the web API for Firestore to perform a simple query ordered on a date property formatted as a string ('2017-12-30'). I use the onSnapshot() method to subscribe as a listener to document changes. The initial population of list of results works as expected - the order is correct.
As I make changes to the data, the callback then gets called with a change type of 'modified'. If any of the changes affects the date property, then I have no way of re-ordering the item in the list of results - unlike the old Realtime Database. That is, until I saw the newIndex and oldIndex properties of DocumentChange. They are undocumented for the Web API (https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentChange), but are documented as part of the Node.js API (https://cloud.google.com/nodejs/docs/reference/firestore/0.10.x/DocumentChange).
So, my problem seemed to be solved - except that in practice the values in newIndex and oldIndex seem to be largely random and bear no relation to the actual order if I refresh the query. I can't make out any pattern that would explain the index values I get back.
Has anyone used DocumentChange.newIndex and DocumentChange.oldIndex successfully? If not, how would you reorder results in subscribers as a result of changes?
const query = firestore.collection(`users/${uid}/things`).
orderBy('sortDate', 'desc').limit(1000)
query.onSnapshot(snapshot => {
snapshot.docChanges.forEach(change => {
if (change.type === "added") {
dispatch(addThing({
id: change.doc.id,
...change.doc.data()
}, change.newIndex)
}
if (change.type === "modified") {
dispatch(changeThing({
id: change.doc.id,
...change.doc.data()
}, change.oldIndex, change.newIndex))
}
if (change.type === "removed") {
dispatch(removeThing(change.doc.id, change.oldIndex))
}
})
})
The original problem I had with the DocumentChange indexes was due to a couple of bugs elsewhere in my code. As I didn't find any examples of this in use outside of the Node.js Firestore docs, here's the test code I used to verify its correct behaviour (ES6). It assumes firebase has been initialized.
cleanTestData = (firestore, path) => {
console.log("Cleaning-up old test data")
var query = firestore.collection(path)
return query.get().then(snapshot => {
const deletePromises = []
if (snapshot.size > 0) {
snapshot.docs.forEach(function(doc) {
deletePromises.push(doc.ref.delete().then(() => {
console.log("Deleted ", doc.id)
}))
});
}
return Promise.all(deletePromises)
}).then(() => {
console.log("Old test data cleaned-up")
})
}
createTestData = (firestore, path) => {
console.log("Creating test data")
const batch = firestore.batch()
const data = {
a: '2017-09-02',
b: '2017-12-25',
c: '2017-10-06',
d: '2017-08-02',
e: '2017-09-20',
f: '2017-11-17'
}
for (const id in data) {
batch.set(firestore.collection(path).doc(id), { date: data[id] })
}
return batch.commit().then(() => {
console.log("Test data created");
}).catch(error => {
console.error("Failed to create test data: ", error);
})
}
subscribe = (firestore, path) => {
const datesArray = []
return firestore.collection(path).orderBy('date', 'asc').onSnapshot(snapshot => {
snapshot.docChanges.forEach(change => {
console.log(change.type, "id:", change.doc.id,
"; date:", change.doc.data().date,
"; oldIndex:", change.oldIndex, "; newIndex:", change.newIndex,
"; metadata: ", change.doc.metadata)
if (change.oldIndex !== -1) {
datesArray.splice(change.oldIndex, 1);
}
if (change.newIndex !== -1) {
datesArray.splice(change.newIndex, 0, change.doc.data().date);
}
console.log(" -->", JSON.stringify(datesArray))
})
})
}
update = (firestore, path) => {
console.log("Updating test data")
return firestore.collection(path).doc('d').set({date: '2018-01-02'}).then(() => {
console.log("Test doc 'd' updated from '2017-08-02' to '2018-01-02'")
})
}
query = (firestore, path) => {
var query = firestore.collection(path).orderBy('date', 'asc')
return query.get().then(snapshot => {
const dates = []
if (snapshot.size > 0) {
snapshot.docs.forEach(function(doc) {
dates.push(doc.data().date)
});
}
console.log("Fresh query of data: \n -->", JSON.stringify(dates))
})
}
handleStartTest = e => {
console.log("Starting test")
const firestore = firebase.firestore()
const path = `things`
let unsubscribeFn = null
unsubscribeFn = this.subscribe(firestore, path)
this.cleanTestData(firestore, path).then(() => {
return this.createTestData(firestore, path)
}).then(() => {
return this.update(firestore, path)
}).then(() => {
return this.query(firestore, path)
}).then(() => {
unsubscribeFn()
console.log("Test complete")
}).catch((error) => {
console.error("Test failed: ", error)
})
}
This is the way it worked for me:
onSnapshot((ref) => {
ref.docChanges().forEach((change) => {
const { newIndex, oldIndex, doc, type } = change;
if (type === 'added') {
this.todos.splice(newIndex, 0, doc.data());
// if we want to handle references we would do it here
} else if (type === 'modified') {
// remove the old one first
this.todos.splice(oldIndex, 1);
// if we want to handle references we would have to unsubscribe
// from old references' listeners and subscribe to the new ones
this.todos.splice(newIndex, 0, doc.data());
} else if (type === 'removed') {
this.todos.splice(oldIndex, 1);
// if we want to handle references we need to unsubscribe
// from old references
}
});
});
source