Currently, I am stuck in a problem with Firebase Observable joins.
I do not really know which is the best way to get my data from different objects and join them together.
My data structure:
users {
userid1 {
conversationid: id
...
},
userid2 {
...
}
}
conversations {
conversationid {
...
}
}
Now I want to get all conversations of the current user.
To get the current user id I'll subscribe to the auth Observable like this:
this.af.auth.subscribe(auth => {
console.log(auth.uid);
});
As next I need the user's child object to get the conversation id. I'm doing that like this:
//needs the userid from Observable on top
this.af.database.object('/users/' + auth.uid)
.map(
user => {
console.log(user.conversationid);
}
)
.subscribe();
And the same for the conversations:
//needs the conversationid from the second Observable
this.af.database.list('/conversations/' + user.conversationid)
.subscribe();
As you can see, there are 3 Observables. I know it's possible to nest them, but in my project could this happen up to 5 times.
Is it possible to get the conversations without nesting 3 Observables?
You could do something like this:
let combined = this.af.auth
// Filter out unauthenticated states
.filter(Boolean)
// Switch to an observable that emits the user.
.switchMap((auth) => this.af.database.object('/users/' + auth.uid))
// Switch to an observable that emits the conversation and combine it
// with the user.
.switchMap((user) => this.af.database
.list('/conversations/' + user.conversationid)
.map((conversation) => ({ user, conversation }))
);
// The resultant observable will emit objects that have user and
// conversation properties.
combined.subscribe((value) => { console.log(value); });
Related
My Firestore data structure looks like this:
db.firestore.FieldPath.documentId(), '==', '20210106.0' does not work, but I am not sure why. I need to read it as a float, so I can use => or =< as Start Date and End Date in my query.
In the console I get this error message: TypeError: Cannot read property 'FieldPath' of undefined'
Here is my code:
actions: {
getFireBaseOrders(state){
db.collection(`ordersOptimized`).where(
db.firestore.FieldPath.documentId(),
'==',
'20210106.0').onSnapshot((res) => {
const changes = res.docChanges();
changes.forEach((change) => {
if (change.type === "added") {
let payload = change.doc.data();
state.commit("firebaseOrders", payload);
}
});
});
},
What am I missing? How do I make the condition work?
If you want to listen to changes occuring to the Firestore document with ID 20210106.0, just do as follows:
db.collection("ordersOptimized").doc("20210106.0").get()
.onSnapshot(function(doc) {
// ....
// Based on your database screenshot you should loop over the
// JavaScript object returned by doc.data()
// Something like
for (const [key, value] of Object.entries(doc.data())) {
console.log(`${key}: ${value}`);
}
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
});
Since 20210106.0 is the ID of a document in the ordersOptimizedcollection, only one document with this ID can exist in this collection. Therefore you should not use a Query (i.e. db.collection('...').where('...')) in order to listen to changes to this document.
On this other hand, if you want to listen to ALL the documents of the ordersOptimized collection, see the corresponding doc.
I have two collections. (applyJobs and Jobs and users). When users apply for a job, I store that record inside applyJobs collection. Like this:
applyId:****,
jobId:*****,
userId:*****
Now, I want to show all apply for jobs by a user.
First: Get logged user id, I store locally logged user id. So, I can get loggged user id.
Second: I filter Apply Jobs by that id. like this, var ref = _db.collection('applyJobs').where('userId',isEqualTo: uid);. I here I didn't call users collection to get uid. because I already store uid on locally. Is it best practice?
Third: I store result here List<ApplyJobsModelClass>. I want to get all jobs by a list of id. How do I filter it?
This is way I tried it. But this is not list of IDs. only one id.
streamApplyJob(List<String> jobId) {
Collection('jobs').document(jobId);
}
And I tried this way too.
Stream<List<JobModel>> streamApplyJob(List<String> jobId) {
var ref = _db.collection('jobs').where('jobId',isEqualTo: jobId);
return ref.snapshots().map((list) =>
list.documents.map((doc) => JobModel.fromFirestore(doc)).toList());
}
tried to get length, but result is 0
db.streamApplyJob(jobIds).listen((v)=>{
print(v.length)
});
Full Code
Database side
///Get a stream of apply jobs
Stream<List<ApplyJobModel>> streamApplyJobs(String uid) {
var ref = _db.collection('applyJobs').where('userId',isEqualTo: uid);
return ref.snapshots().map((list) =>
list.documents.map((doc) => ApplyJobModel.fromFirestore(doc)).toList());
}
///Get a stream of a single document
Stream<List<JobModel>> streamApplyJob(List<String> jobId) {
var ref = _db.collection('jobs').where('jobId',isEqualTo: jobId);
return ref.snapshots().map((list) =>
list.documents.map((doc) => JobModel.fromFirestore(doc)).toList());
}
calling
List<String> jobIds = [];
void getData() {
db.streamApplyJobs(widget.uid).listen((listApplies) => {
for (int i = 0; i < listApplies.length; i++)
{jobIds.add(listApplies[i].jobId)},
});
db.streamApplyJob(jobIds).listen((v)=>{
print(v.length)
});
}
Solution(It's working now)- Is it best practice or are there other best way to do this?
Future<List<JobModel>> getJobs() async {
await db.streamJobs(true).listen((jobs) {
setState(() {
jobModel = jobs;
});
});
return jobModel;
}
Future getData() async {
await getJobs();
db.streamApplyJobs(widget.uid).listen((apply) => {
for (int j = 0; j < jobModel.length; j++)
{
for (int i = 0; i < apply.length; i++)
{
if (apply[i].jobId == jobModel[j].jobId)
{
jobModelNew.add(jobModel[j]),
}
}
}
});
}
I want to get all jobs by a list of id. How do I filter it?
There currently is no way to pass in a list of IDs to a Firestore query and get documents matching all those IDs. See Google Firestore - how to get document by multiple ids in one round trip? (which talks about doing this with document IDs), and Firebase Firestore - OR query (which talks about filtering for multiple values on a single field).
Unless your use-case happens to match the workaround mentioned in that second answer, you'll have to perform a separate query for each value, and merge the results in your application code.
Not sure if it is documented anywhere officially, but this is possible now!
.where(admin.firestore.FieldPath.documentId(), "in", [array, of, ids])
Found here: https://stackoverflow.com/a/52252264/10562805
Please take a look at this example. It binds a CollectionReference to a List.
Let me know if this is helpful.
The snippet is part of a bigger code. Generally I have an object on firebase database called users (it's not a list). I need to get some of them and then convert into Array or FirebaseListObservable.
Observable.merge(...[
this.db.object('users/user1'),
this.db.object('users/user2'),
this.db.object('users/user3'),
this.db.object('users/user4'),
this.db.object('users/user5')
]).subscribe(user => {
console.log(user);
});
This return me user by user, however I need to get all users together. I need to do it in sync. Any ideas?
I have a similar problem and this is how I'm solving it for the moment:
getUsers(): Observable<any> {
let observables = [];
for (let user of users) {
observables.push(this.db.object(user))
}
return Observable.combineLatest(...observables, (...results) => { return results });
}
What I did not manage to do is to return it as FirebaseListObservable.
sorry for my english, i have this observable:
this.productos = af.database.list('/productos', {
query: {
orderByChild: 'categoria',
equalTo: this.catnombre
}
});
I need extract all id from here and set in a array but i dont know how, thanks.
Edit:
I can extract the id but I use de key, now i need extract other data, but snapshot.val, dont work.
this.productos = af.database.list('/productos/', {
query: {
orderByChild: 'categoria',
equalTo: this.catnombre
}, preserveSnapshot:true
});
this.productos.subscribe(snapshot => {
snapshot.forEach(snapshot => {
console.log(snapshot.key);
this.idproductos.push(snapshot.key);
});
console.log(this.idproductos);
});
All you need to do is
this.productos = af.database.list('/productos/', {
query: {
orderByChild: 'categoria',
equalTo: this.catnombre
})
.map(products => products.map(product => product.$key));
The result will be an observable of arrays of keys. Now you can subscribe it to or do whatever else you want to.
this.productos.subscribe(keys => console.log("keys are", keys));
If AngularFire, and things like FirebaseListObservable, are used correctly, you don't need to worry about snapshots, or taking their val(), or doing forEach on them, or taking elements and putting them onto your own array. The FirebaseListObservable is an observable of arrays. Simply map it to create other observables, as we have done above to create an observable of arrays of keys, or subscribe to it to get the underlying data.
I have written my app with ionic 2 and followed the tutorial of Josh Morony, but I don't know how I can get a specific element from my firebase database.
For example I have this tree :
user
|__ (user_id)
|_ name : 'toto'
And so on...
I tried this way:
elt: FirebaseListObservable<any[]>;
this.elt = af.database.list('/user');
But how can I work with the selected data?
I found this solution which is similar than yours :
af.database.list('/user', { preserveSnapshot: true})
.subscribe(snapshots=>{
snapshots.forEach(snapshot => {
console.log(snapshot.key, snapshot.val());
this.items.push({
id: snapshot.key,
name: snapshot.val().name
});
});
});
In order to get the data as an array from a provider you need to return a promise which will be returned once the firebaseListObservable event is triggered with return data.
In your provider .ts
getData(fbPath): Promise<any> {
return new Promise(resolve => {
this.db.list(fbPath).subscribe(data => {
resolve(data);
})
})
}
Here the promise resolves once the data is populated and returns an array with easy access to the $value and $key properties. Which is ideal for creating conditionals or complex queries or a provider service with generic properties ( as opposed to querying the snapshot of the firebaseListObservable directly )
In your controller you can then write something like
this.providerName.getData('users').then(data => {
console.log('data',data);
})
This will return an object literal with the values
$exists
$key
$value
So now if you want a match conditional you can loop through the data with the match condition on the $key of the table users
if(myUserIdVar === data.$key){ // do something here };
A tidier syntax can be found using a library like lodash Where for example if you want a condition to match a stored id, say firebase.auth().currentUser.uid you can do a simple _.find
import { find } from 'lodash';
import * as firebase from 'firebase'; // Or just the firebase auth stuff
...
let filteredUser = find(data, ['$key', firebase.auth().currentUser.uid])
The $key value will be equal to the |__ (user_id) value
I think, af.database.list('/user') returns an Observable. You need to subscribe to it. Like this:
af.database.list('/user')
.subscribe(data => {
console.log("Data is : ",data);
},
(ex) => {
console.log('Found exception: ', ex);
});
Also, if this is in your provider and you want to return the data, you can create a new Observable and return it. If need help, could edit my answer to that also.