How join two Vuefire collection? - firebase

I have two queries in Vue.js with Firebase (vuefire). This queries have similar datas. I want somehow join it, for later iterate.
watch: {
searchQuery: {
handler(searchQuery) {
if (searchQuery) {
this.$bind('logos1', logosCollection
.where('tags', 'array-contains-any', [this.searchQuery]))
this.$bind('logos2', logosCollection
.where("name", '>=', this.searchQuery)
.where("name", '<=', this.searchQuery+'\uf8ff')
.orderBy('name')
);
this.logos= ====> SOMEHOW LOGOS1+LOGOS2
}
}
},
Is there any method to do this?

I've built a little helper for that using $watch.
export function bindCombineArrays(that, combined, props, sort) {
console.log(`bindCombineArrays "${combined}" <== ${props}`);
let combine = () => {
let arr = props.reduce((res, p) => res.concat(that[p]), []);
if (sort)
arr = sort(arr);
console.log(`refreshing combined "${combined}" =`, arr);
that.$set(that, combined, arr);
};
// watches each property
props.forEach((p) => { that.$watch(p, combine); });
}
Example:
this.$bind("convA", db.collection(`conversations`).where("u1.id", "==", this.eventUser.id));
this.$bind("convB", db.collection(`conversations`).where("u2.id", "==", this.eventUser.id));
bindCombineArrays(this, "conversations", ["convA", "convB"]);

Related

vue3 composition API - watch

I am trying to get to grips with the composition API. Struggling with watch:
const totalValuation = ref(0);
const values = ref([1, 2, 3]);
totalValuation.value = watch(finalTasksFiltered.value, () => {
return values.value.reduce((prev, curr) => {
console.log(prev + curr);
return prev + curr;
}, 0);
});
return {
finalTasksFiltered,
totalValuation,
};
The console.log works exactly like it should (1,3,6) but I cannot seem to render it to the DOM.
When I check to console it is fine but in the DOM like so {{ totalValuation }} it returns:
() => { (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_0__.stop)(runner); if (instance) { (0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.remove)(instance.effects, runner); } }
Oh, I am using Quasar - not sure if that makes a difference.
I am sure its something small.
I have imported ref and watch from vue.
I have a computed function working fine too.
watch is meant to execute a function when some value changes. It is not meant to be assigned as a ref. What you're looking to do seems like a better fit for computed
watch: (no assignment)
watch(finalTasksFiltered.value, () => {
totalValuation.value = values.value.reduce((prev, curr) => {
console.log(prev + curr);
return prev + curr;
}, 0);
});
computed: (uses assignment)
const totalValuation = computed(() => {
return values.value.reduce((prev, curr) => {
console.log(prev + curr);
return prev + curr;
}, 0);
});

Get data from a compound query in firestore with cloud function?

First I search a specific document with a document. In this function I use different filters for getting specific values from the data. The result of this filters is an array that will use it form a "foreach". The result is concatenated in elementosaux and next is equal to elementos. The problem is that i print o get the value elementos the result is undefined
This is the code. Thanks for your help.
exports.autentifyday = functions.https.onRequest((req, res) => {
cors(req, res, () => {
var db = admin.firestore();
const key=req.query.key;
const dia=req.query.dia;
db.collection("/servicios_disponibles/").doc(key).get().then(function(doc){
var Ddr;
var Str;
var horario;
var horas;
var horasaux;
var horasselected;
var elementos: any[] = [];
var elementosaux: any[] = [];
var elementoselement;
if( doc.exists){
var diaselected;
Ddr = JSON.parse(JSON.stringify(doc.data()));
horario=Ddr.horario;
horario.filter((dias: { dia: any; disponibilidad: boolean; })=>{
if(dias.dia==dia){
if(dias.disponibilidad==true){
diaselected=dias;
}
}
});
horas=JSON.parse(JSON.stringify(diaselected));
horasaux=horas.horas;
horasselected=horasaux.filter((hora: { disponibilidad: boolean; })=>hora.disponibilidad==true);
horasselected.forEach(function(hora: { hora: any; }){
db.collection("pedidos_servicios").where("key","==",key)
.where("horas","array-contains",hora.hora).get().then(snapshot=>{
snapshot.forEach(doc3=>{
elementoselement = {
"horas": doc3.data().horas
}
elementosaux = elementosaux.concat(elementoselement);
console.log(elementosaux)//return an array with the answer query
});
elementos=[];
elementos=elementosaux;
console.log(elementos)//is undefined in this point
}).catch((e)=>console.log(e))
})
}
console.log(elementosaux)//is undefined in this point
res.send(Str);
return Str;
}).catch(reason => {
res.send(reason);
console.log(reason);
return reason;
});
});
});
This might be metter of asynchronous code. I took part of your code and created simple example:
const db = new Firestore()
var elementos: any[] = [];
var elementosaux: any[] = [];
var elementoselement;
let colRef = db.collection("collection1");
if (true) {
console.log("start")
colRef.get().then((snapshot) => {
snapshot.forEach(doc3=>{
elementoselement = {
"horas": doc3.data().horas
}
console.log("elementoselement",elementoselement)
elementosaux = elementosaux.concat(elementoselement);
console.log("elementosaux inside snapshot", elementosaux)//return an array with the answer query
});
elementos=[];
elementos=elementosaux;
console.log("elementos inside snapshot", elementos)//is undefined in this point
});
console.log("elementosaux outside snapshot", elementosaux)
console.log("elementos outside snapshot", elementos)
}
console.log("elementosaux outside if", elementosaux)//is undefined in this point
console.log("end")
I added to my firestore collection collection1 with one document containing field horas: horas_value - to make it working.
The example contains more log information about which part of code particular log is. When you run it like ts-node test_main.ts you get following result:
start
elementosaux outside snapshot []
elementos outside snapshot []
elementosaux outside if []
end
elementoselement { horas: 'horas_value' }
elementosaux inside snapshot [ { horas: 'horas_value' } ]
elementos inside snapshot [ { horas: 'horas_value' } ]
As get is asynchronous you can see that logs inside it are at the end.
There is a lot of possibilities to deal with that. For example you can put whole if in 'async' function and add 'await' to get:
async function main() {
if (true) {
....
await colRef.get().then((snapshot) => {
....
}
main()
and then result is:
start
elementoselement { horas: 'horas_value' }
elementosaux inside snapshot [ { horas: 'horas_value' } ]
elementos inside snapshot [ { horas: 'horas_value' } ]
elementosaux outside snapshot [ { horas: 'horas_value' } ]
elementos outside snapshot [ { horas: 'horas_value' } ]
elementosaux outside if [ { horas: 'horas_value' } ]
end
Only I don't know why you say this is undefined, it shouldn't be...
I have empty arrays... Maybe it's a matter of way how to run the code.
I hope it will help!

How do I combine multiple http requests and merge results

I need to handle a situation where I have 3 endpoints to call and would like to get the data in the most convenient/efficient way. The first call can be handled independently and returns a single result. The second endpoint returns a collection but will need to initiate 0-* subsequent calls, where a given key is present.
Ideally would like to receive the collection (from the 2nd endpoint call) as a mutated/new collection that includes the result from the 3rd endpoint call.
I am currently using forkJoin(observableA$, observableB$) to handle the first 2 calls in parallel but I cannot work out how to include the sequential calls and have the data included in observableB$
//Customer observable
const customer$ = this._customerManagementService.getCustomer(
accountNumber
);
return forkJoin({
customer: customer$,
saleCycles: saleCyclesWithVehicle$
}).pipe(finalize(() => this._loaderFactoryService.hide()));
getSalesWithVehicle(accountNumber: string, dealerKey: string) {
return this._salesCycleService
.getCyclesForCustomer({
customerNumber: accountNumber,
dealerKey: dealerKey
})
.pipe(
concatMap((results: ISaleCycle[]) => {
return results.map(cycle => {
return this._purchaseVehicleService.getPurchaseVehicle(
cycle.vehicleKey
);
});
})
);
}
I expect the collection to include further data as a new property on the original collection
UPDATE
After a bit more thought maybe I should be using reduce somewhere in the solution. This way I can be in control of what's getting push into the array and it could be dynamic?
getSalesWithVehicle(accountNumber: string, dealerKey: string) {
return this._salesCycleService
.getCyclesForCustomer({
customerNumber: accountNumber,
dealerKey: dealerKey
})
.pipe(
switchMap((results: ISaleCycle[]) => {
return results.map(cycle => {
if (cycle.vehicleKey) {
return this._purchaseVehicleService
.getPurchaseVehicle(cycle.vehicleKey)
.pipe(
reduce((acc, vehicle) => {
return { cycle: cycle, vehicle: vehicle };
}, []),
toArray()
);
}
else {
///No extra data to be had
}
});
}),
concatAll()
);
}
I would use concatMap() to merge the responses of HTTP requests 2 and 3.
import { of } from 'rxjs';
import { map, concatMap } from 'rxjs/operators';
const pretendGetCustomer = of({accountNumber: 123, name:"John Doe"});
const pretendGetVehiculeHttpRequest = (customerNumber) => {
return of([{custNum: 123, vehicleId:"2"}, {custNum: 123, vehicleId:"1"}]);
}
const pretendGetCyclesHttpRequest = (cycleIds) => {
return of([{id:"1", name:"yellow bike", retailPrice:"$10"}, {id:"2", name:"red bike", retailPrice:"$20"}]);
}
const yourFunction = () => {
pretendGetCustomer.subscribe(customer => {
// Assuming you do other things here with cust, reason why we are subscribing to this separately
// isHappy(customer)
// Your second & third calls
pretendGetVehiculeHttpRequest(customer.accountNumber).pipe(
// Need to use concatMap() to subscribe to new stream
// Note: use mergeMap() if you don't need the 1st stream to be completed
// before calling the rest
concatMap(purchases => {
const cyclesIds = purchases.map(p => p.vehicleId);
// concatMap() requires an Observable in return
return pretendGetCyclesHttpRequest(cyclesIds).pipe(
// Use map() here because we just need to use the data,
// don't need to subscribe to another stream
map(cycles=>{
// Retrun whatever object you need in your subscription
return {
customerNumber: customer.accountNumber,
customerName: customer.name,
purchases: purchases.map(p => cycles.find(c => p.vehicleId === c.id))
}
})
);
})
).subscribe(resultof2and3 => {
// Do something with the new/mutated Object which is a result of
// your HTTP calls #2 and #3
console.log(resultof2and3);
});
});
}
yourFunction();
I made a stackblitz if you want to see the above run (see console): https://stackblitz.com/edit/rxjs-nqi7f1
This is the solution I eventually came up with. I've taken the advice from BoDeX and used concatMap(). In my mind it was clear that I wanted to use forkJoin and be able to reference the results by object key, I.e customer or saleCycles.
In the scenario where a vehicleKey was present I needed to return the results in a defined data structure, using map(). Likewise, if no vehicle was found then I just needed the outer observable.
const customer$ = this._customerManagementService.getCustomer(accountNumber);
const saleCyclesWithVehicle$ = this.getSalesWithVehicle(accountNumber,dealerKey);
getSalesWithVehicle(accountNumber: string, dealerKey: string) {
return this._salesCycleService
.getCyclesForCustomer({
customerNumber: accountNumber,
dealerKey: dealerKey
})
.pipe(
concatMap(cycles => {
return from(cycles).pipe(
concatMap((cycle: ISaleCycle) => {
if (cycle.vehicleKey) {
return this._purchaseVehicleService
.getPurchaseVehicle(cycle.vehicleKey)
.pipe(
map(vehicle => {
return { cycle: cycle, vehicle: vehicle };
})
);
} else {
return of({ cycle: cycle });
}
}),
toArray()
);
})
);
}
return forkJoin({
customer: customer$,
saleCycles: saleCyclesWithVehicle$
}).pipe(finalize(() => this._loaderFactoryService.hide()));

Populate the cloud firestore document containing an array of document references

I have a collection named campgrounds in which every document contains an array of document reference to the documents in the comments collections.
It looks like this Campground
I'm trying to figure out a way to populate this comments array before sending it to my ejs template.
My code looks like this
app.get("/campgrounds/:docId", function(req, res) {
var docRef = firestore.collection("campgrounds").doc(req.params.docId);
try {
docRef.get().then(doc => {
if (!doc.exists) {
res.send("no such document");
} else {
// res.send(doc.data());
res.render("campground", {
doc: doc.data(),
title: doc.data().title,
id: req.params.docId
});
}
});
} catch (error) {
res.send(error);
}
});
In your array you store DocumentReferences. If you want to get the data of the corresponding documents in order to include this data in your object you should use Promise.all() to execute the variable number (1 or more) of get() asynchronous operations.
The following should work (not tested at all however):
app.get("/campgrounds/:docId", function(req, res) {
var docRef = firestore.collection("campgrounds").doc(req.params.docId);
try {
var campground = {};
docRef.get()
.then(doc => {
if (!doc.exists) {
res.send("no such document");
} else {
campground = {
doc: doc.data(),
title: doc.data().title,
id: req.params.docId
};
var promises = [];
doc.data().comments.forEach((element, index) => {
promises.push(firestore.doc(element).get());
});
return Promise.all(promises);
}
})
.then(results => {
var comments = {};
results.forEach((element, index) => {
comments[index] = element.data().title //Let's imagine a comment has a title property
});
campground.comments = comments;
res.render("campground", campground);
})
} catch (error) {
res.send(error);
}
});
Note that with this code you are doing 1 + N queries (N being the length of the comments array). You could denormalize your data and directly store in the campground doc the data of the comments: you would then need only one query.

Firestore query with multiple where clauses based on parameters

I want to query a Firestore database with multiple where clauses based on the parameters that are passed in. The following block of code works:
getProducts2(accountId: string, manufacturer?: string, materialType?: string): Promise<Product[]> {
return new Promise<Product[]>((resolve, reject) => {
const productCollection2: AngularFirestoreCollection<FreightRule> = this.afs.collection('products');
const query = productCollection2.ref
.where('materialType', '==', materialType)
.where('manufacturer', '==', manufacturer);
query.get().then(querySnapshot => {
if (querySnapshot.size > 0) {
const data = querySnapshot.docs.map(documentSnapshot => {
return documentSnapshot.data();
}) as Product[];
resolve(data);
} //todo else...
});
});
}
But what I really want to do is conditionally include the where clauses based on the optional parameters. The following is what I want, but it doesn't work:
getProducts2(accountId: string, manufacturer?: string, materialType?: string): Promise<Product[]> {
return new Promise<Product[]>((resolve, reject) => {
const productCollection2: AngularFirestoreCollection<FreightRule> = this.afs.collection('products');
const query = productCollection2.ref;
if (manufacturer) {
query.where('manufacturer', '==', manufacturer);
}
if (materialType) {
query.where('materialType', '==', materialType);
}
query.get().then(querySnapshot => {
if (querySnapshot.size > 0) {
const data = querySnapshot.docs.map(documentSnapshot => {
return documentSnapshot.data();
}) as Product[];
resolve(data);
} //todo else...
});
});
}
While valid, this code just returns all of the products with no filtering.
Is there a way to structure this so I can filter based on the optional parameters?
edit: I realize I can do something like:
let query;
if (manufacturer && materialType) {
query = productCollection2.ref.where(....).where(....)
} else if (manufacturer) {
query = productCollection2.ref.where(....)
} else if (materialType) {
query = productCollection2.ref.where(....)
}
I was just hoping for something a little more elegant.
Build upon the prior query, don't repeat the prior query:
let query = collection // initial query, no filters
if (condition1) {
// add a filter for condition1
query = query.where(...)
}
if (condition2) {
// add a filter for condition2
query = query.where(...)
}
// etc
If using different query structure, You can try below ways:
db.collection("subscriptions").where("email", '==', req.body.email,"&&","subscription_type","==","free").get();
OR
db.collection("subscriptions").where("email", '==', req.body.email).where("subscription_type", '==', 'free111').get();

Resources