0

I'm relatively new to Angular, and I'm still figuring things out. I've read/done the Tour of Heroes tutorial multiple times and read a lot of RxJS documentation and I'm still kind of stuck in this part of the code of a real project. I'm not new to JavaScript.

Typically, when I need to hydrate an object with external data I'd just use async/await to pull the data from the sources and manipulate it accordingly. I'm not sure if this is the accepted pattern using Angular.

I have multiple services that fetch data from different sources. Within the ngOnInit method of my component, I'm trying to call all those methods, but I'm not able to get the data directly without things getting too convoluted and I'm questioning whether this is the right way to do it within the Angular ecosystem.

This is a snippet of the code I'm writing, but it just feels wrong. What would be the idiomatic way to do it in Angular?

  async fetchData(): Promise<void> {
    const clients = await this.clientService.getClients();
    clients.pipe(
      map(c => c.map(client => this.jobService.getJobs(client.clientId))
    ).subscribe();
  }

Essentially what I'm trying to do is:

  1. Fetch a list of clients from an API
  2. Iterate over the list of clients and request a list of jobs by clientId
  3. Wait until both requests are complete and push the data to a BehaviorSubject
  4. Have other components read data from that BehaviorSubject to render the data for the end-user
2
  • Do you want just the job data or client data as well? Commented Oct 27, 2020 at 16:35
  • I need both @BerkKurkcuoglu Commented Oct 27, 2020 at 16:36

2 Answers 2

1

The approach I would use is to map your list of clients into a list of jobs where your client is passed along (if you still need that info later). Then once you have this list, you run them all concurrently using forkJoin(). Once all your source observables have completed, forkJoin will emit their results as one array.

Then you can do some processing on the results and shove them into your BehaviourSubject.

dataSubject = new BehaviourSubject<Type>();

fetchData(): void {
  this.clientService.getClients().pipe(
    map(clients => clients.map(
      client => this.jobService.getJobs(client.clientId).pipe(
        map(jobs => {client, jobs})
      )
    ),
    mergeMap(clientJobs => forkJoin(clientJobs)),
    map(results => {
      const data = /* whatever you need */
      for (const {client, jobs} of results){
        for (const job of jobs){
          /* Format your data however you like for the BehaviourSubject */
        }
      }
      return data;
    })
  ).subscribe(dataSubject);
}

As a BehaviourSubject (or any RxJS Subject for that matter), dataSubject is both an observer and an observable. This means dataSubject can subscribe to any RxJS stream:

stream.subscribe(subject);

If, however, you'd like more control (say you don't want to pass errors through) you can nest your subject in another observer

stream.subscribe({
  next: subject.next,
  error: err => console.err("Found error: " + err.message),
  complete: () => {/*Do nothing*/}
});

Now errors get logged, but values get passed on. I tend to deal with errors in my stream (catchError, retry, retryWhen, ect) since that adds a metric ton of flexibility in retrying or altering your stream. So often, you'll see the whole nested subscription short-handed like this:

stream.subscribe(x => subject.next(x));
or
stream.subscribe(subject.next);
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for the thorough response, this helped me clean up my code.
0

In Angular, the prefered way is to use only rxjs, mixing async/await is too complicated, you need to choose.

In your case with async/await with observable service :

  async fetchData(): Promise<any[]> {
    const clientsWithJobs = [];
    const clients = await this.clientService.getClients().toPromise();
    for(const client of clients) {
      const jobs = await this.jobService.getJobs(client.clientId).toPromise();
      clientsWithJobs.push({...client,jobs});
    }
    return clientsWithJobs;
  }

1 Comment

So is there a way to accomplish this without using async/await if that's the preferred way?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.