1

I just want to apologize for my lack of understanding of RxJS and Observables. I posted a question earlier but it was very poorly worded and I know it wasn't understandable, so after a day I think I can explain my issue better.

  1. The objective of my code is to grab user input, pass it as an action stream to my data stream, compare the input to my array of bays, if it exists, return that single bay. If it doesn't exist (This is where I'm struggling) return an observable that I can call back in my bay.page.ts and check to see if its true or false. If it's false [Show client side error]. If it's true, navigate forward to the results page.
  2. My bay-service.ts class contains my Data & Actions streams:
export class BayServiceService {
  private baysUrl = 'api/bays';

  bays$ = this.http.get<Bay[]>(this.baysUrl)
    .pipe(
      tap(data => console.log('Bays: ', JSON.stringify(data))),
      catchError(this.handleError)
    );

  /*--------------------------------------------------------------*/
  // Grab A Single Bay
  private baySelectedSubject = new BehaviorSubject<number>(0);
  baySelectedAction$ = this.baySelectedSubject.asObservable();

  invalidBay = new BehaviorSubject<boolean>(false);

  selectedBay$ = combineLatest([
    this.bays$,
    this.baySelectedAction$
  ])
    .pipe(
      map(([bays, selectedBayNumber]) => {
        if (!bays.find(bay => bay.bayCode === selectedBayNumber)){
          this.invalidBay.next(false);
        } else {
          this.invalidBay.next(true);
          return bays.find(bay => bay.bayCode === selectedBayNumber);
        }
      }),
      );

  selectedBayChanged(selectedBayNumber: number): void {
    this.baySelectedSubject.next(selectedBayNumber);
  }

^ In the above code I use a declarative RxJS approach selectedBay$ in which I combine my bay$ data stream, and my baySelectedAction$ Action Stream (Which contains the user input). I then map them and then compare the user input selectedBayNumber to a single bay number.

ISSUE: I am trying to use the Observable -> invalidBay = new BehaviorSubject<boolean>(false); that I created back in my bay-service.ts and check it's result: If it's true, navigate forward. If it's false, show a client side error that it doesn't exist. However, If the user enters a valid bay number, I try to change that Observable to true by doing this.invalidBay.next(true); BUT, the observable won't update it's value? The BehaviorObservable stays it's default value of 'false'. So the code is partially working, but not quite.

  1. This is my onSubmit method in my bay-page.ts where I handle the user input for my action stream. I passed the user input into a method (which is in my bay-service.ts class). Depending on their input, I try to subscribe to the Observable that is "supposedly updated" and go from there. But the Observable is not being updated. Please help.
onSubmit() {
    this.bayService.selectedBayChanged(this.bayForm.get('bayStart').value);
    this.subscription = this.bayService.invalidBay.subscribe(value => {
        if (value) {
            this.navCtrl.navigateForward([`/results/`]);
        } else {
            this.bayDoesNotExistError = true;
            this.selectedBay = this.bayForm.get('bayStart').value;
            this.vibration.vibrate(500);
            console.log('Vibrating for 3 second...')
        }
    })
}

Why is the Observable not having it's value updated? Please, my mind hurts and I don't know what else to do.

atiyar
  • 7,762
  • 6
  • 34
  • 75
Donny groezinger
  • 127
  • 1
  • 3
  • 9
  • The initial bays$ comes from the `selectedBay$`. As you can see, I combine the `bays$` stream and the `baySelectedAction$` together with combineLatest function. So whenever, I called the `selectedBay$`, I will be returned ALL the bays, and then select the one that I want based on user input. – Donny groezinger Mar 02 '21 at 15:40

1 Answers1

4

An observable does nothing if no observer subscribes to it. You didn't subscribe to the selectedBay$ observable. Therefore, the code in pipe from combineLatest is not executing at all, no matter how many times new values are emitted to your action stream, and you are always receiving the initial false value from invalidBay.

Some suggestions :

  1. A BehaviorSubject is a multicasting observable and you should always keep it private to minimize the scope of calling next() on it. Expose it as an observable with the .asObservable() method, and if any external code needs the ability to emit new value, provide a public method to do that. You are already handling baySelectedSubject in this way. Consider doing the same for invalidBay.

  2. Never put your subscription code in a method that will execute multiple times. You'll end up creating a new subscription each time the method gets executed. The observable will emit values to each of those subscription. So, remove your subscription from onSubmit() and put it in ngOnInit().

In your service, change -

invalidBay = new BehaviorSubject<boolean>(false);

to -

private invalidBay = new BehaviorSubject<boolean>(false);
invalidBay$ = this.invalidBay.asObservable();

and in your component -

ngOnInit(): void {
    this.bayService.selectedBay$.subscribe(
        p => {
            // do something with the Bay value
        }
    );

    this.bayService.invalidBay$.subscribe(
        p => {
            // do something with the invalidBay value
        }
    );
}

onSubmit(): void {
    this.bayService.selectedBayChanged(this.bayForm.get('bayStart').value);
}
atiyar
  • 7,762
  • 6
  • 34
  • 75
  • Wow, I really appreciate the suggestions, and I agree with them! However, I don't want the `this.bayService.invalidBay$.subscribe` in my ngOnInit as it SHOULD be updated everytime this piece of code runs: `this.bayService.selectedBayChanged(this.bayForm.get('bayStart').value);` Which will happen when the user submits. That piece of code will trigger the selectedBay$ and depending on the user input, should trigger the invalidBay$ observable to emit a value. But it doesn't, it's just false. I want to handle the value from that Observable and throw a client side error if false, or move forward. – Donny groezinger Mar 02 '21 at 15:26
  • @Donnygroezinger You need updated value from `invalidBay$`, not a new subscription to it. Put the subscription code inside `ngOnInit`, and you will still receive updated emits if anytime `.next()` is called on `invalidBay` in the service. That's how Observables work. Test the version I implemented in the answer, and you'll understand. – atiyar Mar 02 '21 at 15:37
  • I did, and I'm trying to hear you. I don't want the `this.bayService.selectedBay$.subscribe()` on this file as I already call it declaretively in my results page that will return a selected bay. I just want the `this.bayService.invalidBay$` result to either emit a false or true. And handle that onSubmit. I did a console.log(p) in my `invalidBay$` subscription (in ngOnInit) and it does NOT change it's value of false when A user submits a bay. I don't know how else to explain my problem other than a phone call or discord. It partially works except invalidBay is always false, no matter the input. – Donny groezinger Mar 02 '21 at 15:52
  • @Donnygroezinger You **_will_** receive update even if you put the subscription in `ngOnInit()` each time `onSubmit` gets called. If you are not receiving it right now, then you have the issue somewhere else which is not shared in the post. Don't try to see "subscribing to an observable" similar as a function call. You do the subscribing only once, and whenever the observable has any updates, you get the update in the .subscribe(value => ...) method. – atiyar Mar 02 '21 at 16:06
  • @Donnygroezinger Also, I have tested your scenario, _in code_, and I got the updated emits. I'm don't know how you are subscribing to `selectedBay$` on your results page, I've tested in my way. – atiyar Mar 02 '21 at 16:18
  • OM MY GOSH THANK YOU SO MUCH. SERIOUSLY, I HAVE BEEN WORKING ON THIS FOR 2 DAYS. I was handling it WRONG. I apologize. you are a life saver. <3 – Donny groezinger Mar 02 '21 at 16:48
  • Here is my `results.page.ts`: `bay$ = this.bayService.selectedBay$.pipe(catchError(err => { this.errorMessageSubject.next(err); return EMPTY;}));` I call my bayService.selectedBay$, and then in my template I do: ` ` – Donny groezinger Mar 02 '21 at 17:17