0

I have a simple async observable which takes 2 parameters to perform some calculations:

  public preferredCurrency: string = '';

  wmData$ = this.http.get<any>("./api/wealthModeling/GetWMData");
  portfolioCalculation$ = this.wmData$.pipe(
    mergeMap((wmData) => {
      return this.portfolioCalculationService.getPortfolioValues(wmData, this.preferredCurrency);
    })

As you can see on initial load preferredCurrency is empty, However UI allows user to choose a different currency (via a dropdown) and hence the need to perform the calculations again.

I am not able to figure out a way to execute the portfolioCalculation$ again.

Hopefully, this explains what I am trying to achieve.

Thanks much.

foo-baar
  • 1,076
  • 3
  • 17
  • 42
  • 1
    Just to outline how you can approach that: Create a new Observable (fromEvent() or a BehaviourSubject, I'd prefer the latter). Then leverage the switchMap Operator like this: `portfolioCalculation$ = this.preferredCurrency$.pipe(switchMap(currency => mergeMap(/* Your code here */) ))`. If you use a BehaviourSubject, it will work out of the Box. If you use "fromEvent", you will need a "startsWith" so the initial request is still made. – pascalpuetz Jun 24 '20 at 18:45
  • @pascalpuetz Sounds good, unfortunately I am too new to rxjs syntax to know how to try it. – foo-baar Jun 24 '20 at 18:53
  • Try looking at the documentation on [service component interaction](https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service) as it provides a good example of how BehaviorSubject/Subjects work. You'd subscribe the observable from the behaviorsubject, adding new calculation results to the subject after triggering http calls. – Alexander Staroselsky Jun 24 '20 at 19:05
  • @AlexanderStaroselsky really confusing as hell :) – foo-baar Jun 24 '20 at 20:40
  • 1
    Take it step by step. 1) Create a new service 2) Add a private BehaviorSubject 3) expose a public variable of the subject's observable 4) add a public method to that service that will call the wealth modeling api and passed the results to the subject via next 5) update your component to instead of making the call itself, calling the exposed method any time a change to the dropdown happens of the service added in the previous step 6) subscribe the observable in your component to the value of the subject observable 7) profit – Alexander Staroselsky Jun 24 '20 at 20:43
  • @foo-baar I added an answer elaborating on my comment. Of course, what Alexander Staroselsky mentioned (extracting that into a service and only exposing the BehaviorSubject as an Observable via `this._preferredCurrency$.asObservable()`) should definitely be applied as well as it is a best practice! – pascalpuetz Jun 25 '20 at 18:28

2 Answers2

0

You can use a formcontrol to listen to to you selected value change which should be the source of the mission.

HTML

<select formControl=[preferredCurrency]....

JS

combineLatest(
  this.wmData$,
  this.preferredCurrency))
     .pipe(switchMap(([wmData, preferredCurrency])=> 
      this.portfolioCalculationService.getPortfolioValues(wmData,preferredCurrency)
  ))
Fan Cheung
  • 10,745
  • 3
  • 17
  • 39
  • This will not make another HTTP Request, but I guess that's what he wants (since the data might not be the same for each currency) – pascalpuetz Jun 25 '20 at 18:31
0

To elaborate on my comment, here an example of how you can achieve that by using a BehaviorSubject.

@Component({/*...*/})
export class YourComponent {
  // This is the BehaviorSubject we will 'next' on, whenever preferredCurrency changed
  // Replace 'initial' by your initial preferredCurrency
  public preferredCurrency$:BehaviorSubject<string> = new BehaviorSubject('Initial');

  private wmData$ = this.http.get<any>("./api/wealthModeling/GetWMData").pipe(
    // Include this if your wmData is the same for all preferredCurrency (this will make it so that the HttpRequest is only performed once)
    // shareReplay(1)
  );

  // pipe from your preferredCurrency$ subject - we will 'next' to it whenever a new preferredCurrency is selected, thus starting this chain
  public portfolioCalculation$ = this.preferredCurrency$
    .pipe(
      // you don't need this, since BehaviorSubjects already start with a value (include if Observable without initial value is used instead)
      // startsWith('initial'),

      // We want to switch to the HTTP Request observable (whenever preferredCurrency was changed)
      switchMap(currency => this.wmData$.pipe(
        mergeMap(wmData => this.portfolioCalculationService.getPortfolioValues(wmData, this.preferredCurrency)) 
      )) 
    );

  // Call this method (or run the statement inside directly) whenever your preferredCurrency changed
  public changePreferredCurrency(newPreferredCurrency:string):void {
    this.preferredCurrency$.next(newPreferredCurrency);
  }
}

In place of the BehaviorSubject, any Observable that triggers whenever your preferredCurrency changes can be used as a Source Observable in this case (e.g. if you are using @angular/forms, every control has a valueChanges Observable). Just remember to incorporate a startsWith('initial') operator as your first operator in your chain in case the Observable does not yield the initial value (e.g. @angular/forms valueChanges does not yield the initial value).

pascalpuetz
  • 5,238
  • 1
  • 13
  • 26