RxJs Compose a list of Observables and then combine (Firebase) - 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

Related

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.

Extract data from FirebaseListObservable to Array

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.

How to enrich result list from FirebaseListObservable with FirebaseObjectObservables

I have a FirebaseListObservable with IDs and I want to look them up using FirebaseObjectObservable and create a Array of the result, but I don't really understand how to do it.
this.af.database.list('/favourites/'+ this.userId)
.flatMap(favouriteId => {
return this.af.database.object('/dhs/en/'+favouriteId.$value)
})
.subscribe(favourite => {
console.log("favourite", favourite);
});
This doesn't work because favouriteId.$value is undefined. What is the right way to do this?
This is a related question: How to merge an observable that is a property of another observable
EDIT: I have created an ugly temporary solution
getAllFavourites(): Observable<any> {
return Observable.create(observer => {
var resArray = [];
this.af.database.list('/favourites/'+ this.userId)
.subscribe(favouriteArray => {
favouriteArray.forEach((favouriteId) => {
this.secretProvider.getDay(favouriteId.$value)`
.subscribe(favourite => {
resArray.push(favourite);
observer.next(resArray);
});
})
})
})
}
Obviously this is not the purpose of observables and in addition the resulting observable array is not updated correctly when for example a favourite ID is removed from this.af.database.list('/favourites/'+ this.userId).
UPDATE: I have found a better solution following this thread: How to move FirebaseObjectObservable into a FirebaseListObservable
getAllFavourites(): Observable<any> {
return this.af.database.list('/favourites/'+ this.userId)
.map((favourites)=>{
return favourites.map((favourite) => {
favourite = this.secretProvider.getDay(favourite.$value)
return favourite;
})
});
}
(and in the HTML using a nested async varibale as described in the other thread)
I still think there must be a better solution though. Something using mergeMap or similar, and avoiding the nested asyncs.

Angular2 using http.get with a distinct

I am trying to learn more about the Http.Get. I am using a json file to mock the HTTP call. After the call I want to limit the rows to unique states. I know there is an Rxjs command called distinct (help on Rxjs distinct). However I do not understand the syntax for the distinct. When I run this code as is I get an array of states. However when I add the distinct it still has duplicated states like Texas.
public getStates(): Observable<IState[]> {
return this._http.get(this.stateUrl)
.map((res: Response) => <IState[]>res.json())
// distinct by state name
.distinct((x) => return x.state)
;
}
Here is the interface for IState
export interface IState {
id: number;
state: string;
city: string;
name: string;
}
Trying to only get rows with a unique state.
I have repo for the code on this Github project
You're confusing a bit the meaning of the distinct in this situation!
This distinct is meant to filter the response from your Observable, not its content! Here you're trying to "distinct" (filter) your json object, but the distinct will filter the responses (multiple) "resolved" by the Observable. So, if the observable "resolves" the same response multiple times, you'll receive only once, because of your .distinct.
This means, if the 1st time the Observable returns you back one json (e.g [{0: a, 1:b, 2:c}]) and the second time he returns the same json reponse, then your "subscribers" will not receive it twice, because the .distinct will "filter" it and as a result it will not be fired!
more details here: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/distinct.md
So, what you're looking for, is how to "filter" duplicated results within your json.
The answer is simple: use a loop to check its properties/values or some library like underscore which might have this method already implemented.
The IState is an object. You have to define what exactly makes them distinct from each other.You can look at the documentation, there is a way to define what makes an object distinct
For example:
/* Without key selector */
var source = Rx.Observable.of(42, 24, 42, 24)
.distinct();
var subscription = source.subscribe(
function (x) {
console.log('Next: %s', x);
},
function (err) {
console.log('Error: %s', err);
},
function () {
console.log('Completed');
});
// => Next: 42
// => Next: 24
// => Completed
/* With key selector */
var source = Rx.Observable.of({value: 42}, {value: 24}, {value: 42}, {value: 24})
.distinct(function (x) { return x.value; });
var subscription = source.subscribe(
function (x) {
console.log('Next: %s', x);
},
function (err) {
console.log('Error: %s', err);
},
function () {
console.log('Completed');
});
// => Next: { value: 42 }
// => Next: { value: 24 }
// => Completed
The problem is that when you're calling .distinct((x) => return x.state) the x variable is the entire array from https://github.com/ATXGearHead12/distinct/blob/master/src/api/state.json. Then property x.state doesn't exists.
So you need to call distinct on each item in the array not on the array itself.
For example like the following:
this._http.get(this.stateUrl)
.map((res: Response) => <IState[]>res.json())
.concatMap(arr =>
Observable.from(arr).distinct((x) => return x.state)
)
.toArray(); // collect all items into an array

Get data from Firebase in Ionic 2

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.

Resources