Sesha
Sesha

Reputation: 566

Retrieve value inside Observable

I make a request to the server and get the response as observable. Inside the data, I have an ID which refers to another document, which I need to send another request to fetch the data and append it with the data of the parent Observable. How to achieve this?

With Promise, I can use async/await to do this, but I want to know how to achieve this using Observables

Sample with promise

async func_name() {
  let data1 = await <first_server_call_returns_promise>();
  let data2 = await <second_call_with_data1.id_returns_promise>();
  data1.value = data2;
  return data1;
}

How to achieve the above with Observables?

Edit 1

Based on @TeddySterne reply the code I wrote is below but I'm getting an observable within the subscribe instead of actual data which prevent me to assign the observable to html

this.blogDoc.snapshotChanges().pipe(
    exhaustMap((blogs) => {
        return blogs.map(blog => {
          const data = blog.payload.doc.data();
          const id = blog.payload.doc.id;
          return this.userService.getUserById(data.uid).pipe(
            map((user) => {
              const name = user.name;
              return { id, name, ...data}
            }),
          )
        })
    }),
  )
  .subscribe(finalData => {
    finalData.subscribe(data => console.log(data));
  });

Edit 2

Below is the updated code that I have now

this.blogDoc.snapshotChanges().pipe(
      exhaustMap((blogs: any[]) => {
        return zip(blogs.map((blog) => {
          const data = blog.payload.doc.data();
          const id = blog.payload.doc.id;
          console.log(data);
          return this.userService.getUserById(data.uid).pipe(
            map((value) => {
              console.log(value);
              return value;
            })
          )
        })).pipe(first())
      })
    ).subscribe(values => console.log("Values: ", values));

The console.log(data) prints the data correctly but console.log(value) is not getting printed nor the console.log("Values: ", values)

When I use subscribe on this.userService.getUserById(data.uid), it returns the expected single user document.

What am I missing here?

Upvotes: 2

Views: 6996

Answers (2)

Teddy Sterne
Teddy Sterne

Reputation: 14221

You can use an exhaustMap to pipe the data to a second http call and then merge the data together when it returns.

Example

import { zip } from 'rxjs/observable/zip';

this.http.get('/data1').pipe(
  exhaustMap((data1Arr: any[]) => {
    return zip(...data1Arr.map((data1) => {
      return this.http.get('/data2/' + data1.id).pipe(
        map((data2) => {
          data1.value = data2;
          return data1;
        }),
      );
    }).pipe(first());
  }),
).subscribe((fullQualifiedData) => {
  // do stuff with the data
});

Example

Upvotes: 4

Ingo B&#252;rk
Ingo B&#252;rk

Reputation: 20043

I tried the above code, but what I'm getting in fullQualifiedData is another observable, which I can't use directly on *ngFor in html file.

This is discussed in the Angular docs as well, but essentially you have two solutions:

Using the async pipe

The async pipe will subscribe to the observable for you and then provide the data. This is easy to use, but note that every async pipe will create a new subscription, which in the case of HTTP requests can mean firing that request multiple times. So use with care.

@Component({ … })
export class YourComponent {
  public data$ = this.http.getFoo();

  constructor(private http: Http) {}
}
<ng-container *ngIf="data$ | async as data">
    {{ data.foobar }}
</ng-container>

Manually managing the subscription

@Component({ … })
export class YourComponent implements OnInit {
  public data: Data;

  constructor(private http: Http) {}

  public ngOnInit() {
    this.http.getFoo().subscribe(data => this.data = data);
  }
}
{{ data?.foobar }}

or

<ng-container *ngIf="data">
  {{ data.foobar }}
</ng-container>

Upvotes: 1

Related Questions