2

Background

I am currently making a find function for a messaging app, where each collection of messages is referenced by an index. When find() is invoked, it has to extract each message collection reference from the messageRef variable, then use getMessageCollectionFromRef(id) to get the Observable for the message collection. Then, it must grab the user id from this Observable's subscribed JSON data of the person who is not the user, and then invoke getUserById(id) to get the Observable for the user info. Lastly, it must compare the name of the retrieved user to the value of the ngModel search. If search is a substring of userJSONData, then the messageRef of the message collection must be put into the messages Observable, from which async data is loaded on the Angular front-end.

Note: Function cancelFind() is called at the beginning of the find() function only to revert the message Observable list to its original length from AngularFire2 data. This works well.

Attempt

find():void {
        this.cancelFind();
        console.log("- find Started -");
        var newMessages = new Array();
        this.messages = this.messages.switchMap((messages) => {
              console.log("Got messages: " + JSON.stringify(messages));
              messages.forEach((msg) => {
                    console.log("Iterating through message #" + msg.$value);
                    this.msgClass.getMessageCollectionFromRef(msg.$value).take(1).subscribe((message) => {
                          var id = (message.messager1.id != this.user.id ? message.messager1.id : message.messager2.id)
                          this.userClass. getUserById(id).take(1).subscribe((messager) => {
                                if (messager.name.includes(this.search)) {
                                      console.log("Pushing " + JSON.stringify(msg) + " to list");
                                      newMessages.push(msg);
                                }
                          });
                    });
              });
              console.log("Found list: " + JSON.stringify(newMessages));
              return Observable.forkJoin(newMessages);
        });
        console.log("- find Resolved -");
  }

Output

[08:41:59]  console.log: - find Started - 
[08:41:59]  console.log: Got messages: [{"$value":0},{"$value":1}] 
[08:41:59]  console.log: - find Resolved - 
[08:41:59]  console.log: Iterating through message #0
[08:41:59]  console.log: Iterating through message #1 
[08:41:59]  console.log: Found list: []
[08:41:59]  console.log: Pushing {"$value":0} to list 

Side note: Yes, I know my - find Started - and - find Resolved - logs follow synchronous conventions. This is standard in the app I'm developing and would not like to stray away from this.

Error

I am not getting an error, but my issue is that the messages Observable remains empty and, consequently, no message collections appear on screen.

This problem is very unique in that it concerns retrieving nested Observable values in a loop, and I have not found adequate solutions to this issue online. The only similar solutions, such as that to a question I asked months ago found here, concern creating an Observable array off of synchronously-retrieved data.

I apologize for the adequately large description in relation to the length of code, but I believe there is an easy fix for this that I was not able to pinpoint for days. I am still new to Observables, so I will appreciate any help you can provide. Thank you so much!

CozyAzure's Solution

Following CozyAzure's solution, I have been able to produce a newly searched Observable array with properly added values. However, instead of omitting non-matches (null values), the filter command does not do anything to the result Observable array. Here is my resulting error: Error

Workaround

Check for null in your front-end as the async pipe loads messages.

<div *ngFor="let msg of messages | async">
      <message-card *ngIf="msg" [ref]="msg" ></message-card>
</div>
11
  • 1
    getMessageCollectionFromRef() can be called in parallel right? Commented Jul 12, 2017 at 12:33
  • Nope, you need the Message Collection to get the ID of the user whom you are messaging. Commented Jul 12, 2017 at 12:34
  • but, each subsequent calls has no dependency to each other right? You can fire them in parallel, and then compare the results later, no? Commented Jul 12, 2017 at 12:35
  • I agree with @CozyAzure, put a console.log before your return statement, you'll probably see that it is shown before Pushing ... to list Commented Jul 12, 2017 at 12:36
  • @CozyAzure Which calls do you mean? Calls to nested functions or each call to get a message collection? Commented Jul 12, 2017 at 12:38

1 Answer 1

1

I really think you got it mostly correct, other than changing some of the subscribe to .switchMap and .map.

I maybe understanding your requirement wrongly though. I am assuming you want an Observable that contains all the results after calling this.msgClass.getMessageCollectionFromRef(), and then just subscribed to it so that you get all the values.

Here's how you can do it:

find(): Observable<any> {
    return this.message
        .switchMap(messages => {
            let arrayOfObservables = messages.map(msg => {
                return this.msgClass.getMessageCollectionFromRef(msg.$value)
                    .take(1)
                    .switchMap(message => {
                        var id = (message.messager1.id != this.user.id ? message.messager1.id : message.messager2.id)
                        return this.userClass.getUserById(id)
                            .take(1)
                            .map(messager => {
                                return messager.name.includes(this.search) ? messager : null;
                            })
                    })
            });
            return Observable
                .forkJoin(...arrayOfObservables) 
                 //filter out the null values
                .filter(x => x !== null);
        })
}

Walkthrough:

  1. Get all the messages from this.messages.

  2. For each of these messages:

    2.1. call msgClass.getMessageCollectionFromRef()

    2.2. take the first value returned, and then

    • call getUserById with the id
    • map the Observable. If the response is the same as input, return the response, else return Observable.empty
  3. Combine all the Observables in point 2 using Observable.forkJoin()

  4. Filter out the values which are null

Some notes:

  1. In your question, your method .find() explicitly stating return of void, but you are actually returning an Observable. You might want to fix that.
  2. At step 3 and 4, Use rest parameters ... to flatten the arrays to be passed into .forkJoin as arguments
Sign up to request clarification or add additional context in comments.

6 Comments

Great idea, but switchMap would need a default value to return if a messager is not found. This is one of the issues I came across before. Is there a way to wait for the map to complete before returning a value in the switchMap method? Thank you! learnrxjs.io/operators/transformation/switchmap.html
@AnthonyKrivonos you are right. Just need to add the return statement. see my edit
Awesome! I'm almost there. Observable.empty() returns a false value for the _isScalar key, which is a tad hard to parse out of the Observable array. Do you know of a way to filter out null values in the returned Observable? I went on to change one of the last lines to return twinee.name.includes(this.search) ? tw : null; because it's easier to check for null.
@AnthonyKrivonos yes, simply use Observable.filter() . See my edit
For some reason, the Observable array still returns with null values. Check my edit!
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.