Convert multiple FirebaseObjectObservable into a FirebaseListObservable - firebase

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.

Related

Async Await with four nested loops

I am currently trying to return a JSON object array that requires me to do one asynchronous function and then four nested asynchronous map functions in order to populate an array of entities. Basically, each user has an array of orders, each order has an array of items, each item has an array of options and each option has an array of values. I am using loopback4 framework and therefore cannot do res.send once all things have been populated. The function seems to return on the first await, but any await after that, it does not wait on it and instead runs to the end of the function. I have tried using Promises and .thens(), but cannot seem to figure out how to populate each entity fully nested, and then return the array of populated entities. I keep getting an empty array. Below is only one nest of maps, but I cannot get it to even populate up to the first nest and return this, so I decided not to go any further. This is the code:
async getUserOrders2(#param.path.number('id') id: number): Promise<any> {
if ( !this.user) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
}
else if (this.user.id != id) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
}
else {
let restaurantId = this.user.restaurantId
let orderFrameArray = new Array<OrderFrame>()
return this.restaurantRepository.orders(restaurantId as string).find()
.then(async orders => {
orders.map(async (val, key)=> {
let orderFrame = new OrderFrame(val)
orderFrame.itemArray = await this.orderRepository.orderItems(val.id).find()
orderFrameArray.push(orderFrame)
})
orderFrameArray = await Promise.all(orderFrameArray)
return orderFrameArray
})
}
}
The function is returning before the orderFrameArray has been populated. I need four nested map loops and this first one is not working, so I am not sure how to do the rest. Any help would be extremely appreciated.
Based on #Tomalaks solution I tried the following, but its still only returning the top level array and nothing is nested:
async getUserOrders2(#param.path.number('id') id: number): Promise<any> {
if ( !this.user) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
}
else if (this.user.id != id) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
}
else {
let restaurantId = this.user.restaurantId
let orderFrameArray = new Array<OrderFrame>()
return this.restaurantRepository.orders(restaurantId as string).find()
.then(orders => {Promise.all(orders.map(
order => {
let orderFrame = new OrderFrame(order)
orderFrame.itemArray = new Array<Item>()
this.orderRepository.orderItems(order.id).find()
.then(orderItems => Promise.all(orderItems.map(
orderItem => {
let itemFrame = new Item(orderItem)
itemFrame.options = new Array<Option>()
this.orderItemRepository.orderItemOptions(orderItem.id).find()
.then(orderItemOptions => Promise.all(orderItemOptions.map(
orderItemOption => {
let optionFrame = new Option(orderItemOption)
optionFrame.values = new Array<Value>()
this.orderItemOptionRepository.orderItemOptionValues(orderItemOption.id).find()
.then(orderItemOptionValues => Promise.all(orderItemOptionValues.map(
orderItemOptionValue => {
let valueFrame = new Value(orderItemOptionValue)
optionFrame.values.push(valueFrame)})))
itemFrame.options.push(optionFrame)})))
orderFrame.itemArray.push(itemFrame)})))
orderFrameArray.push(orderFrame)}))
return orderFrameArray})
}
}
I apologize for the formatting I wasn't sure how best to format it. Is there something else I'm doing wrong?
Thanks to everyone for their response. The answer that was posted by #Tomalak was correct. I just had to surround the entire function in brackets, and put a .then to return the populated entity I had made
You only need to use async when you are using await in the same function. If there's await in a nested function, the parent function does not need async.
However, in your case, there is no function that should be made async in the first place.
There is no benefit in awaiting any results in your function, because no code inside depends on any intermediary result. Just return the promises as you get them.
There's no need for intermediary result variables like orderFrameArray, you're making things harder than they are with your approach of awaiting individual orders and pushing them to a top-level variable.
Using await in a loop like you do inside your .map() call is bad for performance. You are basically serializing database access this way – the next query will only be sent after the current one has returned. This kind of daisy-chaining nullifies the database's ability to process multiple concurrent requests.
getUserOrders2 is not Promise<any>, it's Promise<Array<OrderFrame>>.
throw terminates the function anyway, you can do multiple checks for error conditions without using else if. This reduces nesting.
So a fully asynchronous function would look like this:
getUserOrders2(#param.path.number('id') id: number): Promise<Array<OrderFrame>> {
if (!this.user) throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
if (this.user.id != id) throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
return this.restaurantRepository
.orders(this.user.restaurantId).find().then(
orders => Promise.all(orders.map(
order => this.orderRepository.orderItems(order.id).find().then(
order => new OrderFrame(order)
)
))
);
}
The async/await equivalent of this function would be more complex.
You then would await the result in the calling code, as you would have to do anyway:
async test() {
const orders = await foo.getUserOrders2(someUserId);
// ...
}
// or
test() {
foo.getUserOrders2(someUserId).then(orders => {
// ...
});
}

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

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.

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...

Use Promise and service together in Angular

My question is based on this topic in Angular Google group.
I want to provide a service which stores some basic data retrieved from the backend via $http, then I only need to fetch those data once. like,
var load = function() {
return $http.get(...).then(function(data) {
return data.user;
});
};
module.factory("userProvider", function() {
var user;
var getUser = function() {
if(!user) {
load().then(function(data) {
user = data;
});
}
return user;
};
return {
getUser : getUser
}
});
module.controller("UserController", ["userProvider", function UserController("userProvider") {
var user = userProvider.getUser();
// do something with user
}]);
The problem is that the promise chain ends in userProvider but not in controller, so the user is undefined the first time I use this controller since the data has not been returned.
How can I use such a storage service and return the data correctly? Thanks!
You can just create your own promise. Here is the modified code:
module.factory( "userProvider", function( $q ) {
// A place to hold the user so we only need fetch it once.
var user;
function getUser() {
// If we've already cached it, return that one.
// But return a promise version so it's consistent across invocations
if ( angular.isDefined( user ) ) return $q.when( user );
// Otherwise, let's get it the first time and save it for later.
return whateverFunctionGetsTheUserTheFirstTime()
.then( function( data ) {
user = data;
return user;
});
};
// The public API
return {
getUser: getUser()
};
});
Update: The solution below by #yohairosen is a great one for many circumstances, but not for all. In some circumstances, we would only want to cache the successful result, as I have done here. If, for example, a failure of this request indicates the user needs to log in first, we would not want the next call (presumably after logging in) to deliver the cached failure. In cases where the method isn't necessarily consistent from call-to-call in all circumstances, this method is better; in all other cases, #yohairosen's solution is simpler and recommended.
It's a bit of an overhead to create your own promise, angular's $http creates one for you anyway. What you're looking for is caching and http can handle it for you by passing cache:true to the service call.
So you can simply do something like this:
module.factory("userProvider", function() {
var getUser = function() {
return $http.get(..., {cache:true}).then(function(data) {
return data.user;
});
return {
getUser : getUser
}
});

Resources