1

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

And I'm listing threads ordered by quality in my component:

featured-threads.component.ts

constructor(af: AngularFire) {
    this.threads = af.database.list('/threads', {
        query: {
            orderByChild: 'quality',
            endAt: 10
    }
});

Here's an excerpt from my view:

featured-threads.component.html

<div *ngFor="let thread of threads | async" class="thread-tile">
...
    {{thread.author}} //Renders memberid3
...
</div>

Instead of rendering memberid3 here, I'd like to get the corresponding member's username property value.

The solutions here and here both get their observables outside of the constructor, instead of inside as demonstrated in the Angularfire2 docs. They use this.af.database. When I use the this keyword within the constructor, TypeScript warns that it doesn't recognize af as a component property (because it obviously isn't). I can use this outside of the constructor by declaring the property af: Angularfire;, but then I get the console error TypeError: Cannot read property 'database' of undefined, which I assume has to do with the fact that declaring the property isn't the same as injecting the AngularFire service. I've tried some other things that are probably too comical to be relevant to this question, too.

I'm not entirely sure what's happening here. This problem prevents me from creating a method to which I could simply pass the value of author from threads as a parameter. This is because methods can't be defined within the constructor, and outside of the constructor af is null.

I'm not even entirely sure that this addresses the core question. I want to be able to join/nest these observables any time, not just in this simpler case when I have the benefit of a direct path to the memberid. There are a number of questions regarding nesting observables whose solutions don't use Angularfire 2. Unfortunately, I've been unable to translate them to an Angularfire 2 solution.

Update

I moved all the logic I had in the component over to a service and now inject the service's method getFeaturedThreads() into the component with the following:

ngOnInit() {
    this.threads = this.featuredThreadsService.getFeaturedThreads()
    this.threads.subscribe( 
        allThreads => 
            allThreads.forEach(thisThread => {
                this.featuredThreadsService.getUsername(thisThread.author)
                    .subscribe( 
                        username => console.log(username))
            })
    )
}

getUserName() looks like this:

getUsername(memberKey: string) {
    return this.af.database.object('/members/' + memberKey + '/username')
}

For now, this logs the username property for each member to the console. Unfortunately, I only get the key. The values are empty:

enter image description here

... which is strange to me, given the fact that getUsername() is successfully passing the member id's into the query path.

enter image description here

This is a grab from my Firebase console view that shows the path is correct.

enter image description here

I recognize the fact that this is a usage question and not an issue with the tech.

Community
  • 1
  • 1
J. Adam Connor
  • 1,694
  • 3
  • 18
  • 35
  • 1
    If you want to access the af field outside the constructor, add private before it. `private af: AngularFire`. Now you can do this.af.someMethod – smoyer Jan 19 '17 at 17:48

1 Answers1

1

I would implement it like this, I moved it to ngOnInit because you really shouldn't be do it in the constructor. Just add private before your af declaration in your constructor.

public ngOnInit() {
    this.threads = this.af.database.list('/threads', {
        query: {
            orderByChild: 'quality',
            endAt: 10
        }
    }).do(threads => {
        threads.forEach(thread => {
            thread.author = this.af.database.getName(thread.author); //actual method to get the username
        });
    });
}

And your component html

<div *ngFor="let thread of threads | async" class="thread-tile">
...
    {{thread.author | async}}
...
</div>
smoyer
  • 7,932
  • 3
  • 17
  • 26
  • I get the typescript warning that `do` property doesn't exit on type `FirebaseListObservable`. – J. Adam Connor Jan 19 '17 at 18:16
  • 1
    you have to import that operator. 'rxjs/add/operators/do' i think. – smoyer Jan 19 '17 at 18:29
  • I'd be interested to see how you define that method. In order to attach `do` to `this.threads` I had to change the type from FirebaseListObservable to Observable. Since the method isn't a method of `database` I don't see how it gets attached to it. I implemented `this.getUsername(thread.author);` After those adjustments my method returns an object that renders as `[object Object]`. – J. Adam Connor Jan 19 '17 at 19:35
  • FirebaseListObservable inherits from Observable, so do should be available. It's probably rendering as object Object because it has a field on there with he actual username. You'll have to map to that. – smoyer Jan 19 '17 at 19:42
  • Thanks for your help so far, but without addressing the question specifically, this answer is incomplete. I'm not sure if the object Object is an empty observable (is that possible?) because when I log thread.author to the console there are no keys or values in the object. I've done some reading on RxJS operators, but haven't been able to figure out the usage of .map to get the values from the field you're talking about--that is, if the observable is even fulfilled. – J. Adam Connor Jan 20 '17 at 06:20
  • Turns out my issue had to do with non-existent database keys. I'm going to accept your answer but I suggest you improve it to demonstrate how you'd actually render the username in the template because as it is your code renders `[object Object]` – J. Adam Connor Jan 21 '17 at 02:26
  • I just assumed your database call would return the actual username string. Not the actual user object. – smoyer Jan 21 '17 at 19:34
  • This was actually made clear in my database structure at the top of my question. Nor is it atypical to structure two-way relationships this way. http://stackoverflow.com/a/37414449/6024090 Since you haven't edited the solution addrress the specific question it wouldn't be instructive for other users to keep this as the accepted answer. – J. Adam Connor Jan 23 '17 at 06:26