Reputation: 1046
Consider the following Bacon.js code sample (loosely based on the code here, uses bacon.model & bacon.jquery):
<input id="total" type="text" placeholder="total">
/
<input id="quantity" type="text" placeholder="quantity" value="1">
=
<span id="price"></span>
<script>
var $total = Bacon.$.textFieldValue($("#total")).changes();
var quantity = Bacon.$.textFieldValue($("#quantity"));
var price = Bacon.when(
[$total, quantity], function(x,y) { return x/y; }
).toProperty(0);
price.onValue(function (p) {
$('#price').text(p);
});
</script>
What happens here is that the stream $total
and the property quantity
are joined into a property price
. Both $total
and quantity
get their input from a text input field, but only $total
prompts price
to be updated. (I.e. quantity
is sampled but not part of the synchronization pattern.)
I am trying to achieve this same behavior using RxJS instead of Bacon.js, but all I can come up with is super ugly solutions. I reckon I must be overlooking something…?
My best shot so far (behavior not identical, but you get the idea):
var totalChange = Rx.Observable.fromEvent($('#total'), 'input');
var totalValue = totalChange.map(function () {
return parseInt($('#total').val(), 10);
});
var quantityValue = totalChange.map(function () {
return parseInt($('#quantity').val(), 10);
});
var price = Rx.Observable.zip(
totalValue,
quantityValue,
function (x,y) {
return x/y;
}
);
price.subscribe(function (p) {
$('#price').text(p);
});
Because neither combineLatest
or zip
offer the desired behavior on observables that represent the two text fields directly, all I can think of is to create an observable for quantity based on input events in the text field for total. Which works, but it feels pretty far-fetched.
Any suggestions for alternative approaches?
Upvotes: 3
Views: 782
Reputation: 13984
Use the recently added withLatestFrom
instead of combineLatest
.
var price = totalValue.withLatestFrom(quantityValue, (x,y) => x/y);
Compare their diagrams:
Upvotes: 6
Reputation: 39182
Edit: I just realized this solution is at the bottom of Ben Lesh's answer. I'll leave this here just for the wordier explanation.
If a variable isn't reactive, sometimes the simplest solution is to just acknowledge that and not try to react to it.
Since quantity
is not observable, any attempts to make it observable and then combining 2 observable streams is just going to feel hackish.
Instead I'd just embrace its unobservable-ness and use map
to read it when total
changes:
var totalChange = Rx.Observable.fromEvent($('#total'), 'input');
var totalValue = totalChange.map(function () {
return parseInt($('#total').val(), 10);
});
var price = totalValue.map(function (total) {
var quantity = parseInt($('#quantity').val(), 10);
return total / quantity;
});
price.subscribe(function (p) {
$('#price').text(p);
});
// or, more succinctly with a single map operation:
var totalChange = Rx.Observable.fromEvent($('#total'), 'input');
var price = totalChange.map(function () {
var total = parseInt($('#total').val(), 10);
var quantity = parseInt($('#quantity').val(), 10);
return total / quantity;
});
price.subscribe(function (p) {
$('#price').text(p);
});
Upvotes: 0
Reputation: 108471
I think maybe you're looking to do something like this?
var totalChange = Rx.Observable.fromEvent($('#total'), 'input');
var totalValue = totalChange.map(function () {
return parseInt($('#total').val(), 10);
});
var quantityValue = totalChange.map(function () {
return parseInt($('#quantity').val(), 10);
});
Observable.combineLatest(totalValue, quantityValue, function(total, quantity) {
return total / quantity;
});
Unfortunately, that's the prettiest thing I can think of to answer the spirit of your question.
In reality though, given the example, you'd just do this:
var price = totalChange.map(function() {
return parseInt($('#total').val(), 10) / parseInt($('#quantity').val(), 10);
});
Upvotes: 1