Angular/RxJS 手动更新管道主题(即使没有更改数据),"unit conversion in rxjs pipe"
Angular/RxJS update piped subject manually (even if no data changed), "unit conversion in rxjs pipe"
我正在开发一个使用 Angular 9 和 RxJS 6.5 的应用程序。我遇到问题的功能使用 Angular 服务从服务器获取一些数据并将此数据提供给视图(使用 RxJS behaviorSubjects)。不过,在将该数据传递给视图之前,我想进行一些单位转换,以便用户可以使用 toggle/switch 在整个应用程序的两个单位之间切换,特别是 "metric tons" 和 "short tons".
虽然在专用服务中提供了实际的转换功能,但我需要控制应转换数据(由服务器获取)的哪些属性,所以我是 "piping" 主题,调用管道内的转换方法。
// 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;
})
);
}
到目前为止,这就像一个魅力:单位被转换并且视图可以订阅可观察到的商品。
现在的问题是,当用户更新 "preferredWeightUnit" 时,"commodities" observable 没有被重新评估,所以 "this.preferredWeightUnit" 在 CommoditiesService 内部被更新,但是单位转换没有再次进行。
我想我可以用相同的数据更新主题,所以调用 this._commodities.next(this._commodities.value)
,但这对我来说看起来不对。
一旦首选单位发生变化,我如何才能再次触发单位转换(因此是 RxJS 管道)?
此外,这种设计选择甚至是让单位以反应方式改变的好主意吗?我认为最好在它们可能出现的任何地方更改视图内的单位。
您可以使用 combineLatest
运算符合并来自多个可观察源的更改。
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;
})
);
}
我正在开发一个使用 Angular 9 和 RxJS 6.5 的应用程序。我遇到问题的功能使用 Angular 服务从服务器获取一些数据并将此数据提供给视图(使用 RxJS behaviorSubjects)。不过,在将该数据传递给视图之前,我想进行一些单位转换,以便用户可以使用 toggle/switch 在整个应用程序的两个单位之间切换,特别是 "metric tons" 和 "short tons".
虽然在专用服务中提供了实际的转换功能,但我需要控制应转换数据(由服务器获取)的哪些属性,所以我是 "piping" 主题,调用管道内的转换方法。
// 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;
})
);
}
到目前为止,这就像一个魅力:单位被转换并且视图可以订阅可观察到的商品。 现在的问题是,当用户更新 "preferredWeightUnit" 时,"commodities" observable 没有被重新评估,所以 "this.preferredWeightUnit" 在 CommoditiesService 内部被更新,但是单位转换没有再次进行。
我想我可以用相同的数据更新主题,所以调用 this._commodities.next(this._commodities.value)
,但这对我来说看起来不对。
一旦首选单位发生变化,我如何才能再次触发单位转换(因此是 RxJS 管道)? 此外,这种设计选择甚至是让单位以反应方式改变的好主意吗?我认为最好在它们可能出现的任何地方更改视图内的单位。
您可以使用 combineLatest
运算符合并来自多个可观察源的更改。
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;
})
);
}