0

I have two components that share data using a service:

export class ShareDataService {

  msg: BehaviorSubject<string>;

  constructor(
  ) { 
    this.msg = new BehaviorSubject("Default Message")
  }

  changeMessage(message: string) {
    this.msg.next(message)
  }
}

First component works as a router, when i click a name it redirects to the profile of that name using its id. I import the share-data service i created above, when clicking a name it triggers de sendData() function which gets the id of the clicked element and passes it as a parameter on the service changeMessagge() function:

  constructor(
    private _data: ShareDataService
  ) { }
  
  message: string = "";

  ngOnInit(): void {


    this._data.msg.subscribe(new_msg => this.message = new_msg)
  }

  sendData() {
    var self = this
    $("ul.users-list").on("click", ".user", function(event) {
      self._data.changeMessage(event.target.id)
    });
  }
}

After that, when it redirects to the new component, this new component also imports data from the service:

  message: string = "";

  constructor(
    private _http: HttpClient,
    private _data: ShareDataService,
    private _router: Router
    ) { }

async ngOnInit() {
    this._data.msg.subscribe(new_msg => this.message = new_msg)

    await new Promise(r => setTimeout(r, 1)); // Sleeps 1ms so it can load message

    if (this.message === "Default Message") { // Returns to landing if no user loaded
      this._router.navigateByUrl("/landing");
      return
    }
    this._http.get<UserInterface>(`https://jsonplaceholder.typicode.com/users/${this.message}`)
    .subscribe((data: UserInterface) => this.user = data)
  }

As you can see, it subscribes to the service and gets the value of messagge, which should have been changed when i clicked a name on the other component. The issue is, it fristly doesn't detect the change, and i have to use the new Promise() function to make it sleep for 1ms so it can load the new value of messagge from the service and then continue. Is there any way i can make it wait for the subscribe to finish loading the new messagge without using the Promise for 1ms? already tried putting the code inside the subscribe after it assigns messagge.

Javier Jerez
  • 360
  • 4
  • 16

1 Answers1

2

I see multiple issues

  1. The variable this.message is assigned asynchronously. By the time the HTTP request is sent, it isn't assigned a value yet. You need to use higher order mapping operator like switchMap to map from one observable to another.
import { switchMap } from 'rxjs/operators';

this._data.msg.pipe(
  switchMap(message => 
    this._http.get<UserInterface>(`https://jsonplaceholder.typicode.com/users/${message}`)
  )
).subscribe((data: UserInterface) => this.user = data)

You could see my post here for brief info on handling multiple co-dependent observables.

  1. await new Promise(r => setTimeout(r, 1)); looks inelegant however small the timeout be. With the above mapping operator it shouldn't be needed anymore.

  2. Avoid using jQuery in Angular. Almost everything from jQuery could be accomplished directly in Angular. Mixing them might lead to maintenance later.

Update: returning observable conditionally

Based on your condition, you could use RxJS iif function with new Observable() construct to either return the HTTP request or do something else and close the observable based on a condition.

Try the following

import { iif, EMPTY, Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';

this._data.msg.pipe(
  switchMap(message => 
    iif(
      () => message === "Default Message",
      new Observable(subscriber => {
        this._router.navigateByUrl("/landing");
        subscriber.complete();
      }),
      this._http.get<UserInterface>(`https://jsonplaceholder.typicode.com/users/${message}`)
    )
  )
).subscribe((data: UserInterface) => this.user = data)
ruth
  • 29,535
  • 4
  • 30
  • 57
  • Thank you so much it works like a charm, I'm also gonna remove jQuery and convert everything to Angular stuff. Is there any chance you can update your answer with the id condition i used? I tried implementing it the same way as with subscribe but seems to be incompatible – Javier Jerez Jun 24 '21 at 15:42
  • @JavierJerez: If you mean the condition against `"Default Message"`, please see if the posted update helps. – ruth Jun 24 '21 at 15:55
  • you are an angel, only issue is there is a missing ")" after the subscriber.complete(). Next line should be "})", instead of just "}," – Javier Jerez Jun 24 '21 at 16:01
  • @JavierJerez: Thanks I've included it. – ruth Jun 24 '21 at 16:09