'Joining' Firebase Queries in Angularfire2

廉价感情. 提交于 2019-11-29 20:40:25

问题


Update:

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:


回答1:


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.




回答2:


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



来源:https://stackoverflow.com/questions/41769360/joining-firebase-queries-in-angularfire2

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!