I'm completely new to Rx*. I'm trying to create a reactive version of MVC using RxJS for my thesis. It's loosely based on https://github.com/staltz/mvi-example
I probably should've studied RxJS more before starting to code, but I've realised I usually learn the best by just jumping to the deep end of the pool. Now I'm completely stuck however.
The View has a text field with a numeric value and a button. Pressing the button changes the color of the text, clicking the text iterates the numeric value. The value is stored in the model, so that "chain" goes through all three objects (View->Controller->Model->View). Pressing the button does not require the model, so that chain is View->Controller->View.
Everything else works, but the final part of the chains (->View) is giving me trouble. Clicking on the text (in the View) propagates changes to the Controller and to the Model, but the View is not notified of the change in the Model. I don't understand why, since the way I understand this all these are implemented in the same way.
I've added comments to the code sample below to designate the functions that don't get called.
The code is available here. Note that, like I said, the code is for my thesis so some design decisions and the app itself may seem odd. Below I've also tried to include the parts I feel are most relevant. I know I should provide a complete example, but it's kind of impossible in this case.
The "obs" parameter that objects get is an internal data structure that I use to store observables. The relevant part is
var OBSERVABLES = {
observe: function(source, observer, observer_name) {
this.request(source, function(s) {
console.log("\t[" + source + " is being observed by " + observer_name + " (" + s.constructor.name + " -> " + observer.constructor.name + ")]")
return s.subscribe(
function myOnNext(x) {
console.log("\t(" + source + ") " + s.constructor.name + " -> OnNext -> (" + observer_name + ") " + observer.constructor.name)
observer.onNext(x)
},
function myOnError(err) {
console.log(error)
})
})
},
View.js
var modelAmount = new Rx.Subject()
var textClicks = new Rx.Subject()
var buttonClicks = new Rx.Subject()
var changeColors = new Rx.Subject()
function View(obs, div) {
console.log("View :: New View")
obs = obs
div = div
var self = this
obs.observe("clickAmount", modelAmount, "modelAmount")
obs.observe("changeColor", changeColors, "changeColors")
obs.add("textClicks", textClicks)
obs.add("buttonClicks", buttonClicks)
//Draw HTML elements
render(div);
}
...
function onClick() {
console.log("View :: onClick() emits 'textClicks'");
textClicks.onNext()
}
function onButtonClick() {
console.log("View :: onButtonClick() emits 'buttonClicks'");
buttonClicks.onNext()
}
//Listen to controllers instruction to change the color
// DOES NOT WORK
var changeNumberColor = changeColors.map(function(c) {
console.log("View :: Listened to 'changeColor'")
if (c === undefined) {
c = getColor()
}
CONTENT.css('color', c)
});
//Listen to model's instruction to change the value
// DOES NOT WORK
var setValue = modelAmount.last(function(latestValue) {
console.log("View :: Listened to 'clickAmount'")
console.log("View :: setValue(" + latestValue + ")")
CONTENT.text(latestValue)
});
Controller.js
var inputTextClicks = new Rx.Subject();
var inputButtonClicks = new Rx.Subject();
var obs = null;
function Controller(obs){
console.log("Controller :: New Controller")
obs = obs
obs.observe("textClicks", inputTextClicks, "inputTextClicks")
obs.observe("buttonClicks", inputButtonClicks, "inputButtonClicks")
obs.add("addAmount", addAmount);
obs.add("changeColor", changeColor);
}
//Listen to input events and give instructions to model
var addAmount = inputTextClicks.map(function(){
console.log("Controller :: addAmount() listened to 'textClicks' and emits 'addAmount'")
return 1;
});
//Listen to button presses and give instructions to the view
var changeColor = inputButtonClicks.map(function(){
console.log("Controller :: ChangeColor() listened to 'buttonClicks' and emits 'changeColor'")
return 1;
});
Model.js
var controllerAddAmount = new Rx.Subject();
var obs = null;
//Stores the texts value. Starts at 0
var VALUE = 0;
function Model(obs){
console.log("Model :: New Model")
obs = obs
obs.observe("addAmount", controllerAddAmount, "controllerAddAmount")
obs.add("clickAmount", amountChanged)
}
//Listen to the controller about changing the value, notify the view about the new value
var amountChanged = controllerAddAmount.map(function(val){
console.log("Model :: amountChanged() listened to 'addAmount' and emits 'clickAmount'")
VALUE += val
console.log("Model :: Amount Changed to " + VALUE)
return VALUE;
})
So the problem is View.setValue() and View.setNumberColor().
You have a lot of code that essentially looks like this:
// Listen to x to ...
var a = x.map(function (val) { ... });
But then you never show what you do with a
. map
takes an observable and produces a new observable that will run the supplied mapping operation. However, the observable it returns is inert and does not actually do anything unless you eventually subscribe to a
(or some derivative of a
).
It looks like for your controller and model you eventually do subscribe to the observables they are producing (through the circuitous route of adding them to your obs
and then subscribing to them in your view).
However, it isn't clear you ever subscribe to changeNumberColor
or modelAmount
. Based upon the imperative code within those two map
methods, I'm guessing you should be using subscribe
instead of map
in those 2 cases.
Also, for modelAmount
you are using the last
operator incorrectly. Most likely you think last
does something different than it does.