Meteor reactive-var package is missing the equals() method, is this a bug? - meteor

I'm learning about reactive programming in Meteor:
https://stephenwalther.com/archive/2014/12/05/dont-do-react-understanding-meteor-reactive-programming
I believe that the idea behind Session.equals(key, value) is to remember an association between the reactive variable and the desired value so that updates only propagate to the surrounding code if the equality changes. That way if we have hundreds of views that depend on the variable, only the old and new views get their update code triggered when the value changes.
Note that this would not be the case if we called Session.get(key) === value because every view's code would be called when the variable changes. This is discussed further under the Session.get versus Session.equals() section of the article.
But I found an inconsistency under the Using Reactive Variables section where it says:
Notice that a reactive variable, unlike the Session object, does not have an equals() method. Yes, that is a shame.
So reactive-var is missing equals() but reactive-dict has ReactiveDict.equals().
I can't really see a conceptual reason to exclude ReactiveVar.equals(). Maybe they had no context for storing the association, or maybe there is some scoping or other issue with Javascript that prevents this that I don't fully understand.
So my question is: is this a bug?
Should I just always use reactive-dict? In which case I would change everything from:
let myReactiveVar = new ReactiveVar();
...
if(myReactiveVar.get() === 'myValue')
To the more verbose (but performant):
let myReactiveDict = new ReactiveDict();
...
if(myReactiveDict.equals('myReactiveVar', 'myValue'))
Which would match the functionality provided by Session.equals().
Another option would be to extend the ReactiveVar prototype with my own equals() method or inherit it in a child class and provide a MyReactiveVar.equals() method. Kudos if someone can provide examples to do either of these workarounds that we could submit as a pull request to the Meteor maintainers.
Update: I forgot to mention that ReactiveVar does take an equalsFunc optional parameter in its constructor. It might be possible to hack that as a reactive code block to partially implement equals() functionality without extending the class. Also, here is a related issue on GitHub.
Update: to save time, here is the relevant source code for ReactiveVar and ReactiveDict.equals(). I believe that the value parameter gets converted to serializedValue and is then added as a dependency in ReactiveDict, but I still don't see why it wouldn't be possible to do something similar for ReactiveVar.

The reason there's no equals method for ReactiveVar is because set only invalidates the computations is the new value differs from the current value.
Sets the current value of the ReactiveVar, invalidating the Computations that called get if newValue is different from the old value.
const example = new ReactiveVar(0);
Tracker.autorun(() => {
console.log(example.get());
});
example.set(1); // logs 1
example.set(0); // logs 0
example.set(0); // doesn't log
This is similar behaviour to ReactiveDict's equals method.
Note that set on ReactiveDict does not behave this way. Calling set broadcasts that the value has changed. If you want to prevent the computation from invalidating, that is when you would use equals.
Set a value for a key in the ReactiveDict. Notify any listeners that the value has changed (eg: redraw templates, and rerun any Tracker.autorun computations, that called ReactiveDict.get on this key.)

Related

When do Template.currentData() and template.data differ in value?

I know one is a reactive source, while the other is not. But I thought they would always give the same value.
Then I found the following code in Telescope's source:
var newTerms = Template.currentData().terms; // ⚡ reactive ⚡
if (!_.isEqual(newTerms, instance.data.terms)) {
instance.postsLimit.set(instance.data.terms.limit || Settings.get('postsPerPage', 10));
}
link: https://github.com/TelescopeJS/Telescope/blob/master/packages/telescope-posts/lib/client/templates/posts_list/posts_list_controller.js#L33
So it seems these two values can sometimes differ. When?
According to Meteor's documentation, about template.data:
This property provides access to the data context at the top level of
the template. It is updated each time the template is re-rendered.
Access is read-only and non-reactive.
Since we know that the current data context is reactive, hence can change without the template being re-rendered (which is what makes reactivity look all nice and smooth on Blaze), this if statement is written to check if the "real" current terms (which are stored in the reactive Template.currentData()) have changed compared to the "previous" terms we had the last time the current template was rendered. (stored in the non-reactive template.data)
To wrap it up, what this autorun does is:
Anytime the current data context changes...
Fetch the terms from said data context
Compare these terms to the ones stored in template.data when the template was rendered
If they differ, that means the terms have changed (duh): reset the post limit.
Within post_list_controller.js in the Template.onCreated() there is a section with:
// initialize the reactive variables
instance.terms = new ReactiveVar(instance.data.terms);
This reactive variable obtains it's terms value through:
{{> Template.dynamic template=template data=data}} // see posts_list_controller.html
Whenever you pass that template twice with a different set of data (David Weldon - scoped-reactivity), that reactive variable will be set and will cause the autorun to run.
// this part will cause the autorun to run
var terms = Template.currentData().terms; // ⚡ reactive ⚡

how does Tracker.autorun pick out its computation?

Looking at Tracker.autorun, this mostly works magically...but I'd like to know how it decides which variables are going to form the dependency for the computation. It picks out only "reactive" vars, for example the following:
window.bar = 1
Tracker.autorun (c) =>
bar = window.bar
foo = Session.get('foo')
console.log('autorun', foo, bar)
If I change the value of Session.set('foo') this will cause the computation to run again.
whereas just changing window.bar doesn't cause a rerun. If I use a subscribe result (not a collection) this also works, so that I guess is reactive too.
Are there any guides to understanding this behavior a bit better?
EDIT:
thanks for the comments below that clarify the computation is able to be inferred because accessors are used for reactive vars, so meteor can track deps.
However I need a bit more clarity to understand when a var is flagged. for instance in this example below, the subscribe call is outside the autorun, but it puts the results into an array. So that means that Tracker is not just tracking calls to (reactive var) accessor methods, but also any variables that are referenced within a block - even if the calls to setup those methods are outside the autorun() block.
subList = [
Meteor.subscribe("Players"),
Meteor.subscribe("Stuff" )
]
Tracker.autorun (c) =>
subReady = _.filter subList, (item) ->
return item.ready()
allDone = (subList.length == subReady.length)
# this code will rerun when the subs ready() are true
maybe i should add this as a new question... it's related to this question .
I'm not an expert and haven't read much about it, but I can try to explain it briefly.
All reactive variables have a thing called a dependency. For example, when one creates a new ReactiveVar, an new dependency is created. See here.
To retrieve the value from a reactive variable, one must call a function. In that "getter", the dependency is instructed to remember that it has a dependency. For example, see here for ReactiveVar.get.
To change the value for a reactive variable, one must call a function. In that "setter", the dependency is notified that something has changed, and that signals that all functions depending on the dependency must rerun. For example, see here for ReactiveVar.set.
Not complicated, right? Well, that was just the easy part, all that remains now is building the infrastructure that makes it work :) That's harder and more complicated to explain.
Reactive variables aren't reactive by themselves; they must be evaluated in a reactive environment in order to be reactive. A reactive environment is created by calling Tracker.autorun. See here.
When you call Tracker.autorun, the function you passed to it will be executed in a new reactive environment, and all dependencies the reactive variables notifies of with the depend method will be tracked by the environment. When you call aDependency.depend, this function will be executed, and it kind of adds the dependency to the environments list over dependencies it depends on.
When a reactive variable changes its value, this function will be executed. It tells the environment that one of the reactive variables it depends on has changed, and invalidates all the dependencies in the environment. After this has happened, the entire function you passed to Tracker.autorun will be re-run, and the new dependencies will be tracked.
Do you get the big picture? It's implementation is a bit more complicated than I've explained, but I think that's kind of how it works.
Notice that whenever you access a reactive variable, it's through a function call, like Session.get(...), or collection.find(...).fetch(), or Meteor.status(). A function like Session.get does not only get the value of a Session variable, but also registers a dependency on the current computation (the current computation is dynamically scoped, so Session.get knows that it was called from an autorun).
Here's how you can implement your own reactive variable using Tracker.Dependency:
dependency = new Tracker.Dependency()
currentValue = null
#setCurrentValue = (newValue) ->
if newValue isnt currentValue
# rerun computations which depend on this Dependency
dependency.changed()
currentValue = newValue
#getCurrentValue = ->
# register this dependency on the current computation (if there is one)
dependency.depend()
return currentValue
And here's how you could use it:
setCurrentValue("hello")
Tracker.autorun ->
console.log(getCurrentValue())
# => "hello" printed
setCurrentValue("goodbye") # => "goodbye" printed
For more information, you could look at this guide: https://meteor.hackpad.com/DRAFT-Understanding-Deps-aAXG6T9lkf6 (note that Tracker was originally called Deps in older versions of Meteor)

JavaFX binding and null values

I was wondering how to bind values where the source of the bind could be null.
I have a property:
private ObjectProperty<Operation> operation = new SimpleObjectProperty<>(null);
I also have a text field:
#FXML
private Text txtCurrentOperation;
I would like to bind the textProperty of the field to the value of the operation object.
My first thought was to use FluentAPI with its when/then/otherwise construct, but it is eagerly evaluated so the solution:
Bindings.when(operation.isNotNull())
.then("null")
.otherwise(operation.get().getName()));
will throw a NPE, because the parameter of otherwise is evaluated no matter what the result of the when.
My next idea was to use lambda somehow:
txtCurrentOperation.textProperty().bind(() ->
new SimpleStringProperty(
operation.isNotNull().get() ? "Null" : operation.get().getName()
));
But the bind has no lambda enabled solution. (Later I realized that it couldn't have, becasue the real work goes backward: the change of the binded object (operation) will trigger the update of the binder (the field text property).)
Some articles I found suggested to use an "extremal" value for the property instead of null. But Operation is a complex and heavy weight component so it is not trivial to construct an artifical instance to represent null. Even more, this seems to me boilercode, something the binding mechanism is designed to help eliminating.
My next try was to logically swap the binding direction and add listener to the operation property and let it update the field programatically. It works and rather simple as long as the need of update only depends the operation object instances:
operation.addListener((e) -> {
txtCurrentOperation.setText(operation.isNull().get() ?
"Null" : operation.get().getName());
});
operation.set(oper);
It is relatively simple, but doesn't work: it throws "A bound value cannot be set." exception and I don't see why is the text property of the control regarded as bound.
I ran out of ideas. After much searching, I still cannot solve the simple problem to update a text field differently based on whether the source is null or not.
This seems so simple and everyday problem, that I am sure I missed the solution.
If a 3rd party library is an option, check out EasyBind. Try something like this:
EasyBind.select(operation)
.selectObject(Operation::nameProperty)
.orElse("null");
There's also a JavaFX JIRA issue for the type of functionality provided by EasyBind. If you don't want to use a 3rd party library, try Bindings.select:
Bindings.when(operation.isNotNull())
.then("null")
.otherwise(Bindings.select(operation, "name"));
Be aware the null checking in Bindings.select isn't super efficient. There's a JIRA issue for it.
Just in case if somebody using not Java itself but Kotlin.
It is a good idea to use wonderful tornadofx library.
There you can just use operation.select{it.name}. Although, this feature seems not to be documented yet, so it took some time to discover it.

Meteor subscribe onReady() and observe() added double counted

I want to wait for all data to be downloaded from the subscription and then create map markers for them all at once at the beginning. To do this, I have a session variable set to false. Then when onReady calls, I initialize all the markers. Then I set the session variable true indicating that the first delivery is in and initialized. In my observe callback, I check the session variable and so long as its false, I dont add any markers. Then, if its true, I will add markers -- assuming non of these markers are already initialized. Sometimes, however, I get a double-count and create twice as many markers.
I guess a good first question to ask is what the relationship is between onReady and observe added? Its not terribly clear in the docs. Is this even the correct way of doing things -- creating a session variable to suppress the observe added function until onReady is done? I dont think so. Also note that the double count doesnt happen every time so its a timing thing... kind of annoying.
Thanks
Yes this is the behavior with observe(). When you run observe initially it will have an initial query that will match everything and run into added.
It is also present when onReady hasn't yet fired but the collections are empty at that point so the initial ones aren't visible. This is mentioned in the docs
Before observe returns, added (or addedAt) will be called zero or more times to deliver the initial results of the query.
I'm not sure entirely how to avoid the initial items. I have done something like this in the past:
var QueryHandle = Collection.find().observe({
added: function() {
if(!QueryHandle) return false;
});
I know this works on the server but I'm not certain if it does on the client.
Another solution would be to run the handle before onReady is called and only stop returning if the subscription is complete: i.e
Meteor.subscribe("collection", function() {
subscribed = true;
});
var QueryHandle = Collection.find().observe({
added: function() {
if(!subscribed) return false;
}
);
Be careful not to run this in a Deps.autorun because then it would be run again if the .find() query params are reactive.
This might happen sometimes depending on how fast the server response. If you use Session it becomes a reactive hash so if it happens fast enough that subscribed returns true. Try using an ordinary variable instead.
If its not helpful there might be an alternative way to avoid the initial ones and a deeper level but it might take a dig into the livedata package.

Reactive behavior in Meteor template

Could someone clarify how Meteor (Handlebars) templates interact with reactive sources? There is already a lot in the documentation but a more systematic explanation would help.
For instance, it seems the following does not trigger a template re-draw when the Session variable is changed
Template.foo.rendered = function () {
var selectedItem = Session.get('item_selected');
... do stuff ...
}
I don't understand why the Template.rendered event does not react to reactive source changes. I would also like to understand if other events/methods exhibit this special behavior.
The template will re-render when you set the item_selected value with
Session.set("item_selected","value");
This Session hash, beside the name has a reactive dependency similar to Deps.depends. When you change this Session hash the current reactive context will be invalidated and everything will be redrawn/re-rendered (which is called via Session.set).
The .rendered method will then be run where you can use this new value like you do with var selectedItem
For a very detailed videocast on how exactly it works you can have a look at the videos on EventedMind which demonstrate how Session is built and how to make another variable reactive.
http://www.eventedmind.com/posts/meteor-the-reactive-session-object
http://www.eventedmind.com/posts/meteor-introducing-deps

Resources