Get data from Firebase in Ionic 2 - firebase

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.

Related

Firebase query `TypeError` using `firestore.FieldPath.documentId()`

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.

Convert multiple FirebaseObjectObservable into a FirebaseListObservable

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.

RxJs Compose a list of Observables and then combine (Firebase)

I'm attempting to combine a list of observables, but haven't had luck with zip or other maps.
What I want to do is get a list of Genres for a given Artist Id. I'm using firebase, so it's a nosql database. I have a 'genresPerArtist' list that is a list of Artist keys, and each Artist key has a list of Genre keys.
Structure:
{
genresPerArtist: {
artist1: {
genre1: true,
genre2: true,
genre3: true
}
},
genres: {
genre1: {
name: 'Alternative'
},
genre2: {
name: 'Jazz'
}
}
}
Here's the start of the function that I have:
getGenresByArtistId(artistId: string): Observable<any> {
return this.db.list(`/genresPerArtist/${artistId}`)
.flatMap((genres: IDictionary[]) => {
return genres.map(genre => this.db.list(`/genres/${genre.$key}`));
// return Observable.zip(obvs);
});
I'm not really sure what do do with the list that I get back from genresPerArtist. What I want to do, is take each genre.key (genre1, genre2, genre3), and use each one to fetch the value from 'genres'. I thought I could return a .zip, and then map over that, but I was not getting a response from zip.
#cartant, thanks, that included what I needed, forkJoin.
// Compose an observable based on the genresPerArtist:
return this.db.list(`/genresPerArtist/${artistId}`)
// Each time the genresPerArtist emits, switch to unsubscribe/ignore any pending user queries:
.switchMap(genres => {
// Map the genres to the array of observables that are to be joined.
let genreObservables = genres.map(genre => this.db.object(`/genres/${genre.$key}`).first());
return Observable.forkJoin(...genreObservables);
});
You could try with the following, asumming that the return of genresPerArtist returns a Map<string, boolean>:
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/mergeMap';
import { zip } from 'rxjs/observable/zip';
getGenresByArtistId(artistId: string): Observable<Genre[]>{
return this.db.list(`/genresPerArtist/${artistId}`)
.mergeMap((genres: Map<string, boolean>) =>
zip(Array.from(genres.keys()).map(genre => this.db.list(`/genres/${genre}`))))
}
EDIT: Another option would be
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/toArray';
getGenresByArtistId(artistId: string): Observable<Genre[]>{
return this.db.list(`/genresPerArtist/${artistId}`)
.mergeMap((genres: Map<string, boolean>) => Array.from(genres.keys())
.mergeMap(genres=> genres)
.mergeMap(genre => this.db.list(`/genres/${genre}`))
.toArray();
}
Basically Im flattening the results into a single array manually. I took the long way to make it easier to follow

Firebase data join with Observable

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); });

Using pipe in *ngFor, the page sometimes updates, sometimes not

I am using angular2-meteor, I already use pure: false. But the pipe sometimes run, sometimes not. See my comments in the code for details of the problem.
Thanks
<div *ngFor="#user of (users|orderByStatus)">
{{user.status.online}}
</div>
users:Mongo.Cursor<Meteor.User>;
ngOnInit()
{
this.subscribe('users', () => {
this.autorun(() => {
this.users = Meteor.users.find();
});
}, true);
}
import {Pipe} from 'angular2/core';
#Pipe({
name: 'orderByStatus',
pure: false
})
export class OrderByStatusPipe {
transform(usersCursor:Mongo.Cursor<Meteor.User>):Array<Meteor.User> {
console.log("OrderByStatusPipe runs");
// (1) If I only do these two lines, the change of other users' status can show on the screen immediately.
// let users = usersCursor.fetch();
// return users;
// (2) If sort users by status, the page sometimes updates, sometimes not when user status change.
// If not update automatically, I click that part of screen, it will update then.
let users:Array<Meteor.User> = usersCursor.fetch();
users.sort((a, b) => {
return (a.status.online === b.status.online) ? 0 : (a.status.online ? -1 : 1);
});
return users;
}
}
UPDATE: The bug seems fixed.
I think the problem is related with angular2-meteor.
At last I found a working way using sort in when you try to get data from Mongo. So not using sort pipe any more.
But you cannot use users:Mongo.Cursor<Meteor.User> with *ngFor, need fetch() first and use Array<Meteor.User>, otherwise it will show this error when the order of list changes:
Cannot read property 'status' of undefined
But then the list won't update automatically in UI. So you need use NgZone.
So the final working code is like this:
<div *ngFor="#user of users)">
{{user.status.online}}
</div>
users:Array<Meteor.User>; // here cannot use users:Mongo.Cursor<Meteor.User>
constructor(private _ngZone:NgZone) {}
ngOnInit()
{
this.subscribe('users', () => {
this.autorun(() => {
this._ngZone.run(() => {
this.users = Meteor.users.find().fetch();
});
});
}, true);
}
I don't know exactly what is behind the calls Meteor.users.find() and usersCursor.fetch() but I think the retrieval of your users should be done outside the filter itself. I guess that one part is done in the filter (with usersCursor.fetch()?) and this could be the problem...

Resources