The issues I was encountering with the empty value fields had to do with non-existent keys in my database, so most of the discourse here won\'t apply to yo
You can compose an observable based on getFeaturedThreads
that queries members and replaces the values in each thread's participants
property with user names:
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/forkJoin';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/first';
import 'rxjs/add/operator/switchMap';
let featuredThreadsWithUserNames = this.getFeaturedThreads()
// Each time the getFeaturedThreads emits, switch to unsubscribe/ignore
// any pending member queries:
.switchMap(threads => {
// Map the threads to the array of observables that are to be
// joined. When the observables emit a value, update the thread.
let memberObservables = [];
threads.forEach(thread => {
// Add the author:
memberObservables.push(this.af.database
.object(`members/${thread.author}`)
.first()
.do(value => { thread.author = value.username; })
);
// Add the participants:
Object.keys(thread.participants).forEach(key => {
memberObservables.push(this.af.database
.object(`members/${key}`)
.first()
.do(value => { thread.participants[key] = value.username; })
);
});
});
// Join the member observables and use the result selector to
// return the threads - which will have been updated.
return Observable.forkJoin(...memberObservables, () => threads);
});
This will give you an observable that emits each time getFeaturedThreads
emits. However, if the user names change, it won't re-emit. If that's important, replace forkJoin
with combineLatest
and remove the first
operator from the composed member observables.
To solve joins on users, I wrote a service that caches the users already fetched and maps them into the referencing data with minimal code. It uses a nested map structure to do the join:
constructor(public db: AngularFireDatabase, public users:UserProvider) {
this.threads = db.list('threads').valueChanges().map(messages => {
return threads.map((t:Message) => {
t.user = users.load(t.userid);
return m;
});
});
}
And the UserProvider service looks like so:
@Injectable()
export class UserProvider {
db: AngularFireDatabase;
users: Map<String, Observable<User>>;
constructor(db: AngularFireDatabase) {
this.db = db;
this.users = new Map();
}
load(userid:string) : Observable<User> {
if( !this.users.has(userid) ) {
this.users.set(userid, this.db.object(`members/${userid}`).valueChanges());
}
return this.users.get(userid);
}
}
There's a complete working example of the joins and all the boilerplate here