简体   繁体   中英

Angular 8 rxjs get values from multiple observables

I am using rxjs switchMap to get the latest form value from a subject, set a local variable and then perform a query based on that value.

Long story short I am populating select box options based on the current value of the form.

  // Get current value of Record Form
  this.recordFormService.recordForm$
    .pipe(
      switchMap(recordForm => {
        // Get current Product Owner on Record Form
        this.currentProductOwner = recordForm.value.ProductOwner;

        // Search People
        return this.recordsApiService.searchPeople$(this.currentProductOwner);
      }),
      catchError(err => {
        console.error(`get recordForm$ failed ${err}`);
        return throwError(err);
      })
    )
    .subscribe(results => {
      // Set Product Owners select box options
      this.productOwners = results.data;
    });

This is all working properly.

However, I also need to perform the same exact logic when a specific form value changes.

// Run the same logic when the ProductOwner value changes
this.parentFormGroup.controls['ProductOwner'].valueChanges.subscribe(val => {});

I tried putting the combineLatest operator at the top, however that only resolves when both observables have emitted at least one value.

The valueChanges observable may not necessarily ever change, but recordFormService.recordForm$ will run at least once when the page is loaded.

Note: The result of the valueChanges observable is a string and the result of the recordForm$ observable is a FormGroup object, they need to be processed entirely differently.

What operator can I use to achieve this requirement?

// Get latest value of all observables
combineLatest(
  this.parentFormGroup.controls['ProductOwner'].valueChanges,
  // Get current value of Record Form
  this.recordFormService.recordForm$
)
.pipe(
  switchMap(recordForm => {
    // Get current Product Owner on Record Form
    this.currentProductOwner = recordForm.value.ProductOwner;

    // Search People
    return this.recordsApiService.searchPeople$(this.currentProductOwner);
  }),
  catchError(err => {
    console.error(`get recordForm$ failed ${err}`);
    return throwError(err);
  })
)
.subscribe(results => {
  // Set Product Owners select box options
  this.productOwners = results.data;
});

UPDATE:

I also tried merge . However the results of both observables are entirely different (one is a formGroup and one is a string) and they both need to be processed differently.

// Get latest value of all observables
merge(
  this.parentFormGroup.controls['ProductOwner'].valueChanges,
  // Get current value of Record Form
  this.recordFormService.recordForm$
)

You'll want merge :

import { merge } from 'rxjs';

Make 2 observables that emit the same type from the observables you want to react to. I am using a fake type RecordForm which holds your form value and the product owner as an example:

const ownerChanges$: Observable<RecordForm> = this.parentFormGroup.controls['ProductOwner'].valueChanges.pipe(
  withLatestFrom(this.recordFormService.recordForm$),
  map(([owner, recordForm]) => ({ ...recordForm, owner }))
);
const formChanges$: Observable<RecordForm> = this.recordFormService.recordForm$.pipe(
  withLatestFrom(this.parentFormGroup.controls['ProductOwner'].valueChanges),
  map(([recordForm, owner]) => ({ ...recordForm, owner }))
);

Then pass them to merge:

// Get latest value of all observables
merge(ownerChanges$, recordChanges$).pipe(
  ...
  // the type passed here is always RecordForm - react accordingly
)
.subscribe(results => {
  // Set Product Owners select box options
  this.productOwners = results.data;
});

But you'll want to make sure both observables passed to merge are the same type, so they can be handled the same way.

I think what you want to do is create another downstream observable; the one for your form input that should trigger the API call as well as the original observable. To do this you can use mergeMap pipe on the original Observable. This will create another Emitter inside that can emit in parallel from the first. Try this:

const formObs$ = this.parentFormGroup.controls['ProductOwner'].valueChanges;

this.recordFormService.recordForm$.pipe(
  // give precedence in the merge callback to the original stream Observable,
  // but if just the second Observable fires here, grab that value and use it to pass downstream
  mergeMap(() => formObs$, (originalValue, formObsValue) => originalValue || formObsValue),
  // now recordForm is either from the original observable, or from your form input
  switchMap(recordForm => {
    this.currentProductOwner = recordForm.value.ProductOwner;

    // Search People
    return this.recordsApiService.searchPeople$(this.currentProductOwner);
  }),
  catchError(err => {
    console.error(`get recordForm$ failed ${err}`);
    return throwError(err);
  })
).subscribe(results => {
  // Set Product Owners select box options
  this.productOwners = results.data;
});

I'm making the assumption here that recordForm$ is returning a form value just like your second observable .valueChanges . If that's not the case, you'll just need to massage it in the mergeMap 2nd callback function to produce a merged value to the switchMap that you want.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM