0

I am working on an app using Angular 9 and RxJS 6.5. The functionality I have problems with uses Angular services to fetch some data from a server and provides this data to views (using RxJS behaviorSubjects). Before passing that data to the views though, I want to do some unit conversion, so that the user can use a toggle/switch to change between two units for the entire app, specifically "metric tons" and "short tons".

While the actual conversion functions are provided within a dedicated service, I need to control which properties of the data (that was fetched by the server) should be converted, so I am "piping" the subjects, calling the conversion methods inside the pipe.

// This "CommoditiesService" fetches data from the server
// and should return all fetched commodities, so "Commodity[]"
export class CommoditiesService {
    // Inside this subject, all Commodities are stored (without unit conversion)
    private _commodities: BehaviorSubject<Commodity[]> = new BehaviorSubject(null);

   public readonly commodities: Observable<Commodity[]> = this._commodities.pipe(
    // This pipe is where all unit conversions for commodities is happening,
    // so for every commodity, the function "convertCommodityUnits" is called.
    // the arguments there are the base and the target unit. Base is always kilogram, as this is what the server returns.
    // The target unit is what the user can set globally. This "preferredWeightUnit" is subscribed to inside the service, so it is updated once the user changes the unit.

    map((commodities: Commodity[]) => {
      // For every commodity, convert Price and inventory into the prefered display unit
      return commodities?.map(commodity => this.convertCommodityUnits(commodity, "kg", this.preferedWeightUnit)) || null;
    })
  );
}

So far this works like a charm: The units are converted and views can subscribe to the commodities observable. The problem is now, when the user updates the "preferredWeightUnit", the "commodities" observable is not re-evaluated, so "this.preferredWeightUnit" is updated inside CommoditiesService, but the unit conversion is not done again.

I suppose I could update the subject with the same data, so calling this._commodities.next(this._commodities.value), but this just looks wrong to me.

How could I trigger the unit conversion (so the RxJS pipe) again once the prefered unit changed? Also, is this design choice even a good idea to make the units changable in a reactive way? I thought it was better then changing the units inside views everywhere they might appear.

Janis Jansen
  • 996
  • 1
  • 16
  • 36

1 Answers1

1

You can incorporate changes from multiple observable sources using the combineLatest operator.

export class CommoditiesService {
    private _commodities: BehaviorSubject<Commodity[]> = new BehaviorSubject(null);

    public readonly commodities: Observable<Commodity[]> = combineLatest([this._commodities, this.preferredWeightUnit$]).pipe(
        map(([commodities: Commodity[], weightUnit]) => {
           return commodities?.map(commodity => this.convertCommodityUnits(commodity, "kg", weightUnit)) || null;
        })
    );
}
BizzyBob
  • 12,309
  • 4
  • 27
  • 51