3

I have a couple of API calls which rely upon each other. Specifically, I'm having trouble getting the final Observable to return properly: it causes the app to lag indefinitely.

If I call this.projectAttributeService.findAndUpdateByProjectAndMetumId({...}) on its own and then .subscribe to it, it seems to work ok. This suggests that it's an issue with my Observable chaining on the front-end. As it currently stands, the method is not even being called on the backend (I have a break point set).

// .service

submitPhasesForm(projectId) {
  return this.activityDateService.activities$.pipe(
    first(),
    concatMap((activities: ActivityDate[]) => {
      this.activities = activities;
      if (this.activities.length === 0) {
        return observableOf({});
      }
      this.activities = activities.map(a => {
        a.project_program_id = parseInt(projectId, 10);
        return a;
      });
      return this.activityDateService.update(this.activities);
    }),
    mergeMap(() => {
      if (this.activities.length === 0) {
        return observableOf({});
      }
      return this.projectAttributeService.getAllMetadata(3).pipe(first())
    }),
    mergeMap((metaData: ProjectAttMetadataAPIResponse) => {
      if (this.activities.length === 0) {
        return observableOf({});
      }
      const metaDataId = (metaData as any).find(m => m.name === 'Phase').id;

    // EDIT: the problem ended up being with the synchronous 
    // this.getProjectPhase(this.activities) method below
      return this.projectAttributeService.findAndUpdateByProjectAndMetumId({
        project_program_id: parseInt(projectId, 10),
        value: this.getProjectPhase(this.activities),
        project_attrib_metum_id: metaDataId
      })
    })
  )
}

This is what findAndUpdateByProjectAndMetumId() looks like (the call seems to work ok on its own):

findAndUpdateByProjectAndMetumId(body: ProjectAttribute): Observable < ProjectAttribute > {
  return this.http.put < ProjectAttribute > (`${ environment.API_URL }project-attribute`, body);
}

And this is where submitPhasesForm() is being called:

// .component

import { forkJoin as observableForkJoin } from 'rxjs';

return this.projectService.patch(this.projectId, {
    summary: projectSummary || proj.summary
  }).pipe(
    first(),
    mergeMap(() => {
      return observableForkJoin(
        this.phasesFormDataService.submitPhasesForm(this.projectId).pipe(first()),
        this.pdpMetricsFormService.submitPdpForm(this.projectId).pipe(first()),
        this.projectStatusFormService.submitStatusForm(this.projectId).pipe(first())
      )
    })
  )
  .subscribe((res) => {
    this.router.navigate([`./pdp/${this.currentTab}/${this.projectId}`]);
  });

The other two calls are very similar, albeit shorter:

submitPdpForm(projectId) {
    return this.pdpMetricsForm$.pipe(
      first(),
      concatMap((formGroup: FormGroup) => {
        if (!formGroup.get('etRadioModel')) {
          return observableOf({});
        }

        const objSend = {...}
        return this.projectService.upsertPdpMetrics(projectId, objSend);
      })
    )
  }

...

submitStatusForm(projectId) {
    return this.metrics$.pipe(
      first(),
      tap(metrics => {
        this.metricsData = metrics;
      }),
      mergeMap(() => this.statusesForm$),
      observableMap(statusesForm => {
        const formGroup = statusesForm;

        if (!formGroup.get('resourceRationale')) {
          return {};
        }

        const obj = [{...}]

        return sendObj;
      }),
      mergeMap((sendObj: any) => {
        if (isEmpty(sendObj)) { return observableOf(sendObj) };
        return this.projectService.upsertMetrics(projectId, sendObj).pipe(first());
      })
    )

Does anything look amiss with the way I'm chaining or calling those Observables?

Any help is much appreciated!

I'm returning of({}) if the first activities$ Observable yields no data, so I can fall through the Observable stream without making unnecessary API calls---I'm open to suggestions for a sleeker way to break out the the Observable chain.

kriskanya
  • 667
  • 2
  • 6
  • 17
  • THAT, is an extremely complex chain of Operators on that `Observable`. Your question lacks implementation details on what will the several function calls that you've done in your Operators return. Would you please consider creating a StackBlitz to replicate this issue? – SiddAjmera Oct 13 '18 at 20:21
  • 1
    https://stackoverflow.com/a/52317734/1791913 – FAISAL Oct 13 '18 at 20:26
  • @SiddAjmera Hence why I could use some help :). The examples given on the docs and otherwise are pretty trivial. I suppose I can try a StackBlitz, though I'm not sure how I could replicate this without real calls (our backend is behind Okta). Those other function calls are all returning properly; I can include them as well. I was hoping someone could perhaps help me spot a glaring logic error. – kriskanya Oct 13 '18 at 20:34
  • @kriskanya what is `return observableForkJoin()` ? – Avin Kavish Oct 13 '18 at 20:38
  • @AvinKavish it's an alias. – kriskanya Oct 13 '18 at 20:43
  • @kriskanya for what?, I don't see it anywhere in the api. Edit: okay I see your edit. – Avin Kavish Oct 13 '18 at 20:44
  • 1
    @kriskanya Okay so, if the problem occurs only when that line of code is placed, then. I would suggest inspecting these ones to see if they work properly. Your actual http call seems fine. `project_program_id: parseInt(projectId, 10)`, `value: this.getProjectPhase(this.activities)`. These ones could be throwing asynchronously, so you don't see any console output. – Avin Kavish Oct 13 '18 at 20:54
  • @AvinKavish It's a good thought. Along this line, I've called that method exactly as written by itself with a .subscribe() in the component's onInit() block and the darn thing hits the backend as expected. – kriskanya Oct 13 '18 at 20:56
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/181818/discussion-between-avin-kavish-and-kriskanya). – Avin Kavish Oct 13 '18 at 20:58

1 Answers1

0

Turns out my synchronous this.getProjectPhase(this.activities) method had a logic error which was sending the app into infinite loop.

Otherwise, the Observable operators work fine.

I still would like to figure out a sleeker way to break out of that stream if this.activities is empty.

kriskanya
  • 667
  • 2
  • 6
  • 17
  • Can you explain in some tree like structure or other simple diagram, what is it you are after? And why forkJoin or flatMap are not answers? – r2018 Oct 13 '18 at 21:27
  • Yeah I don't understand the excessive use of `first()` either, because http calls only emit once anyway. Either one next and one complete, or one error. – Avin Kavish Oct 13 '18 at 22:53
  • @AvinKavish In other words, the following would be more appropriate: `observableForkJoin( this.phasesFormDataService.submitPhasesForm(this.projectId), this.pdpMetricsFormService.submitPdpForm(this.projectId), this.projectStatusFormService.submitStatusForm(this.projectId) )` – kriskanya Oct 15 '18 at 17:18
  • @r2018 We have a series of separate components which contain forms. I need to submit all these forms at the same time to the backend when a universal Save button is clicked---but only submit the data if the forms have been altered. The form data is being stored in services since form values changed by the user should remain as the user navigates across tabs, until they hit Save again (or Cancel, which resets the forms to DB values). So I need to be able to continue through the Observable chain even if a particular form has not been changed (which I try to tackle by returning `of({})`. – kriskanya Oct 15 '18 at 17:28