How to process propertychange events only when triggered by the UI?


#1

I’m using a Slider to set a temperature and whose “value” property is bound to an RxJS Observable listening to a websocket.
This slider is used to set this same temperature: when the valueChange event is triggered, I send a request to the server to update the temperature and the server notifies the updated temperature on the websocket.

My problem is that setting the slider value programmatically (e.g. via the observable when the page is loaded, or when a new value is received via the websocket from another client) triggers the valueChange event, and the update request to the server, which is unnecessary obviously.

Is there a way to be notified of the valueChange event only on UI update, or to distinguish between programmatic and UI triggers?
The answer is probably no, so is there a clever way to manage that with some Rx magic?
Here are the observables I use:

temperature$: Observable<number>; // fed by the websocket
sliderUpdate$: Observable<number>; // fed by valueChange event

#2

I don’t think that is possible as of now, it’s going to trigger change events even in pure native apps.

But {N} never stops you from implementing / extending anything, one possible option I can imagine of is, keep a boolean flag, set it to true when you programatically change it and when it’s true ignore what you have to do in change event.


#3

I came out with a pure Rx solution: what I want is skip a sliderUpdate if it is triggered by a change in temperature, i.e. if it happens less than 1 second after (sort of hacky but I see no better way).
To skip the sliderUpdate during this lapse of time, I use the windowToggle operator that allows to define windows of capture of events. I must close a window when a temperature is received, and open the next window 1 second later.
Here is the code:

// opening$ emits 1 second after a temperature is received
const opening$ = temperature$.pipe(
	delay(1000),
	tap(_ => console.log("opening"))
);

// closing$ emits as soon as a temperature is received
const closing$ = temperature$.pipe(
	skip(1), // see Notes
	tap(v => console.log("closing"))
);

sliderUpdate$.pipe(
	// wait 500ms after the slider value before emitting it
	debounceTime(500),
	 // do not emit if the slider value did not change
	distinctUntilChanged(),
	// define the windows of capture
	windowToggle(opening$, _ => closing$),
	// flatten the observable of windows into observable of values
	mergeAll()
).subscribe(v => this.sendTemperatureToServer(v as any as number));

Notes:

  • temperature$ is a BehaviourSubject that will emit a value as soon as it is subscribed to, so I must skip(1), otherwise the first window will close immediately. For the opening$, if the observable was not a BehaviourSubject, I suppose I would have to add startWith(0) to open the first window.
  • for me temperature$ is a hot observable, so I can subscribe multiple times without triggering side effects each time (such as a HTTP request for instance). It it were cold I would have to use shareReplay I think.
  • there seems to be a typescript typing error in the return value of mergeAll(): it is supposed to flatten an Observable<Observable<T>> to an Observable<T> but TS still reports a returned value of type Observable<Observable<T>>, so the value returned in the subscription must be coerced forcefully.
  • I’m still learning Rx, so there may be a nicer way to do this