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 your question. If you're looking for a way to 'join' queries in AngularFire2, the accepted answer below does a fine job of this. I'm currently using combineLatest
instead of forkJoin
. In order to do this you have to import 'rxjs/add/observable/combineLatest';
.
I have the following denormalized Firebase structure:
members
-memberid1
-threads
-threadid1: true,
-threadid3: true
-username: "Adam"
...
threads
-threadid1
-author: memberid3
-quality: 67
-participants
-memberid2: true,
-memberid3: true
...
I want to render username
in my threads
view, which is sorted by quality
.
My service:
getUsername(memberKey: string) {
return this.af.database.object('/members/' + memberKey + '/username')
}
getFeaturedThreads(): FirebaseListObservable<any[]> {
return this.af.database.list('/threads', {
query: {
orderByChild: 'quality',
endAt: 10
}
});
}
My component:
ngOnInit() {
this.threads = this.featuredThreadsService.getFeaturedThreads()
this.threads.subscribe(
allThreads =>
allThreads.forEach(thisThread => {
thisThread.username = this.featuredThreadsService.getUsername(thisThread.author)
console.log(thisThread.username)
})
)
}
For some reason this logs what looks like unfulfilled observables to the console.
I'd like to get these values into a property of threads
so I can render it in my view like this:
<div *ngFor="let thread of threads | async" class="thread-tile">
...
{{threads.username}}
...
</div>
Updated: console.log
for allThreads
and thisThread
Updated: subscribed to getUsername()
this.featuredThreadsService.getUsername(thisThread.author)
.subscribe( username => console.log(username))
The result of this is objects with no values:
AngularFirestore allows you to work with Cloud Firestore, the new flagship database for mobile app development. It improves on the successes of Realtime Database with a new, more intuitive data model. Cloud Firestore also features richer, faster queries and scales better than Realtime Database. Documents. Collections.
Querying DataWith Firebase database queries, you can selectively retrieve data based on various factors. To construct a query in your database, you start by specifying how you want your data to be ordered using one of the ordering functions: orderByChild() , orderByKey() , or orderByValue() .
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With