Search code examples
angularrxjsrxjs6rxjs-observablesrxjs-pipeable-operators

How to pass two Subjects/Action streams as HTTP Params in a GET request to return a single object/Array of objects in Angular using RxJS?


OBJECTIVE: I want to GET a single object and/or a range of objects from my backend service by passing in user inputs as HTTP params using RxJS. For Example, I am using declarative RxJS and so I have two Subjects (Action streams) to grab the input from the user when they hit Submit. I want to pass those Subjects values, as HTTP params, into my http request in order to get either the specified bay object, or a range of bays. The first Subject/UserInput will always be mandatory, whereas the second Subject/UserInput will be optional. So if someone enters 4 for the first input, and then nothing for the second input. The backend will accept both inputs as 4 and 'null' because nothing was put in. If that's the case, return the bay that has a bay Number of 4. If there is a second input like 6, return a range of bays from 4 -> 6, if they exist.

The Issue: How do I pass those two Subjects as http params using declarative RxJS?

Here are my two subjects and their method, as well as my attempt at the HTTP request in my bay-service.ts file.

  private bayStartSelectedSubject = new Subject<number>();
  baySelectedAction$ = this.bayStartSelectedSubject.asObservable();

  private bayEndSelectedSubject = new Subject<number>();
  bayEndSelectedAction$ = this.bayEndSelectedSubject.asObservable();

  selectedBayChanged(selectedBayStartNumber: number, selectedBayEndNumber?: number): void {
    this.bayStartSelectedSubject.next(selectedBayStartNumber);
    this.bayEndSelectedSubject.next(selectedBayEndNumber);
  }

  private HandlingUnitResponseUrlSecondary = 'http://localhost:8080/sbtemplate/readBayInventory';


bayOrBays$ = combineLatest([
    this.baySelectedAction$,
    this.bayEndSelectedAction$
  ])
    .pipe(
      map(([bayStart, bayEnd]) => {
        mergeMap(() => this.http.get<HuResponse>(`${this.HandlingUnitResponseUrlSecondary}/COS/${bayStart}/${bayEnd}`))
      })
    );

Here is where I grab the user inputs in my bay-page.ts file, and call their method to emit the user inputs value:

onSubmit() {
    this.bayService.selectedBayChanged(this.bayForm.get('bayStart').value, this.bayForm.get('bayEnd').value);
    if(!this.invalidBay) {
      this.bayDoesNotExistError = true;
      this.selectedBay = this.bayForm.get('bayStart').value;
      this.vibration.vibrate(500);
      console.log('Vibrating for 3 second...')
    } else {
      this.navCtrl.navigateForward([`/results/`]);
    }

I know the code doesn't look great, but I can't find any information on this. Thanks for the help!

Also Here is the backend API that I am trying to hit:

@RequestMapping(value="/readBayInventory/{centerId}/{beginBayId}/{endBayId}", method = GET)
    public ResponseEntity<HUResponse> readBayInventory(@PathVariable String centerId, @PathVariable int beginBayId, @PathVariable(required = false) int endBayId) {

Solution

  • You can try to change the way you declare bayOrBays and move to this form

    const bayOrBays$ = combineLatest([
      baySelectedAction$,
      bayEndSelectedAction$
    ]).pipe(
      concatMap(([bayStart, bayEnd]) => this.http.get<HuResponse>(`${this.HandlingUnitResponseUrlSecondary}/COS/${bayStart}/${bayEnd}`))
    );
    

    This code basically means that every time one of the 2 Subjects emits a new value, you will trigger the http call and return its result. concatMap is one of the so called "higher level operators", i.e. is an operator that takes a function which returns an Observable and returns the value emitted by such Observable (in other words it "flattens" the inner Observable returned by the function passed in as input).

    Other "higher level operators" are mergeMap (aka flatMap), switchMap and exahustMap. All have slightly different behaviors, but for http calls usually concatMap is a safe default. You may get some inspiration on how to use rxJs in common http related use cases from this article.

    Finally, the reason why currently you get Observable<void> as type of bayOrBays$ is in part related to the fact that the function you pass to mergeMap does not return anything. Remember that if you use curly-braces to define the body of a function, you have to explicitly use a return statement to return something. On the contrary, if you use the one-line format for the body (i.e. without curly-braces) the function returns whatever is the result of the execution of that one line.

    So, this format (which is what you used) does not return anything

    const myFunction = () => {
      doStuff();
    }
    

    while both of these formats return whatever doStuff() returns

    const myFunction = () => {
      return doStuff();
    }
    
    const myFunction = () => doStuff()