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)
Related
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.)
I have the autorun function in rendered callback:
Template.drawFlow.rendered = ->
outerData = this
this.autorun ->
# do some work here using reactive collections
s = Session.get("redrawLines")
connectRecursive outerData.data.tasks, outerData.data.startTask
What I want is that on data change in this.autorun to reexecute the function. Right now it doesn't do it, so I am wondering why.
Update
updated the code above, and also, on some events I do:
Session.set('redrawLines', moment())
which should invalidate the computation inside the autorun function.
Also, the "outerData" variable should be reactive, as it's from "data" section of the IronRouter.
this.autorun is a reactive computation, but you need a reactive source within that autorun function in order for the view to be rendered upon update of that data.
Reactive sources in Meteor include Session variables, cursors, Meteor.user, and more. Here's a good read that should help you out:
https://www.discovermeteor.com/blog/reactivity-basics-meteors-magic-demystified/
Hope that helps.
Final working code, which fires after DOM is finshed (as opposed to my solution):
Template.drawFlow.onRendered ->
console.log "Template.drawFlow.onRendered"
this.autorun ->
data = Router.current().data()
Tracker.afterFlush ->
console.log "dom is now created"
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
I want to take a list of item names from a collection as a simple array to use for things like autocompleting user input and checking for duplicates. I would like this list to be reactive so that changes in the data will be reflected in the array. I have tried the following based on the Meteor documentation:
setReactiveArray = (objName, Collection, field) ->
update = ->
context = new Meteor.deps.Context()
context.on_invalidate update
context.run ->
list = Collection.find({},{field: 1}).fetch()
myapp[objName] = _(list).pluck field
update()
Meteor.startup ->
if not app.items?
setReactiveArray('items', Items, 'name')
#set autocomplete using the array
Template.myForm.set_typeahead = ->
Meteor.defer ->
$('[name="item"]').typeahead {source: app.items}
This code seems to work, but it kills my app's load time (takes 5-10 seconds to load on dev/localhost vs. ~1 second without this code). Am I doing something wrong? Is there a better way to accomplish this?
You should be able to use Items.find({},{name: 1}).fetch(), which will return an array of items and is reactive, so it will re-run its enclosing function whenever the query results change, as long as it's called in a reactive context.
For the Template.myForm.set_typeahead helper, you might want to call that query inside the helper itself, store the result in a local variable, and then call Meteor.defer with a function that references that variable. Otherwise I'm not sure that the query will be inside a reactive context when it's called.
Edit: I have updated the code below both because it was fragile, and to put it in context so it's easier to test. I have also added a caution - in most cases, you will want to use #zorlak's or #englandpost's methods (see below).
First of all, kudos to #zorlak for digging up my old question that nobody answered. I have since solved this with a couple of insights gleaned from #David Wihl and will post my own solution. I will hold off on selecting the correct answer until others have a chance to weigh in.
#zorlak's answer solves the autocomplete issue for a single field, but as stated in the question, I was looking for an array that would update reactively, and the autocomplete was just one example of what it would be used for. The advantage of having this array is that it can be used anywhere (not just in template helpers) and that it can be used multiple times in the code without having to re-execute the query (and the _.pluck() that reduces the query to an array). In my case, this array ends up in multiple autocomplete fields as well as validation and other places. It's possible that the advantages I'm putting forth are not significant in most Meteor apps (please leave comments), but this seems like an advantage to me.
To make the array reactive, simply build it inside a Meteor.autorun() callback - it will re-execute any time the target collection changes (but only then, avoiding repetitive queries). This was the key insight I was looking for. Also, using the Template.rendered() callback is cleaner and less of a hack than the set_typeahead template helper I used in the question. The code below uses underscore.js's _.pluck() to extract the array from the collection and uses Twitter bootstrap's $.typeahead() to create the autocomplete.
Updated code: I have edited the code so you can try this with a stock meteor created test environment. Your html will need a line <input id="typeahead" /> in the 'hello' template. #Items has the # sign to make Items available as a global on the console (Meteor 0.6.0 added file-level variable scoping). That way you can enter new items in the console, such as Items.insert({name: "joe"}), but the # is not necessary for the code to work. The other necessary change for standalone use is that the typeahead function now sets the source to a function (->) so that it will query items when activated instead of being set at rendering, which allows it to take advantage of changes to items.
#Items = new Meteor.Collection("items")
items = {}
if Meteor.isClient
Meteor.startup ->
Meteor.autorun ->
items = _(Items.find().fetch()).pluck "name"
console.log items #first result will be empty - see caution below
Template.hello.rendered = ->
$('#typeahead').typeahead {source: -> _(Items.find().fetch()).pluck "name"}
Caution! The array we created is not itself a reactive data source. The reason that the typeahead source: needed to be set to a function -> that returned items is that when Meteor first starts, the code runs before Minimongo has gotten its data from the server, and items is set to an empty array. Minimongo then receives its data, and items is updated You can see this process if you run the above code with the console open: console.log items will log twice if you have any data stored.
Template.x.rendered() calls don't don't set a reactivity context and so won't retrigger due to changes in reactive elements (to check this, pause your code in the debugger and examine Deps.currentComputation -- if it's null, you are not in a reactive context and changes to reactive elements will be ignored). But you might be surprised to learn that your templates and helpers will also not react to items changing -- a template using #each to iterate over items will render empty and never rerender. You could make it act as a reactive source (the simplest way being to store the result with Session.set(), or you can do it yourself), but unless you are doing a very expensive calculation that should be run as seldom as possible, you are better off using #zorlak's or #englandpost's methods. It may seem expensive to have your app querying the database repetitively, but Minimongo is caching the data locally, avoiding the network, so it will be quite fast. Thus in most situations, it's better just to use
Template.hello.rendered = ->
$('#typeahead').typeahead {source: -> _(Items.find().fetch()).pluck "name"}
unless you find that your app is really bogging down.
here is my quick solution for bootstrap typeahead
On client side:
Template.items.rendered = ->
$("input#item").typeahead
source: (query, process) ->
subscription = Meteor.subscribe "autocompleteItems", query, ->
process _(Items.find().fetch()).pluck("name")
subscription.stop() # here may be a bit different logic,
# such as keeping all opened subsriptions until autocomplete
# will be successfully completed and so on
items: 5
On server side:
Meteor.publish "autocompleteItems", (query) ->
Items.find
name: new RegExp(query, "i"),
fields: { name: 1 },
limit: 5
I actually ended up approaching the autocompletion problem completely differently, using client-side code instead of querying servers. I think this is superior because Meteor's data model allows for fast multi-rule searching with custom rendered lists.
https://github.com/mizzao/meteor-autocomplete
Autocompleting users with #, where online users are shown in green:
In the same line, autocompleting something else with metadata and bootstrap icons:
Please fork, pull, and improve!
I have read the docs and looked at the source behind reactivity, but I don't understand it.
Can someone explain how this works behind the scenes, as it looks like magic to me :).
So it's actually rather straight forward, at a basic level there are 2 types of functions involved:
Functions that create a reactive context (reactive function)
Functions that invalidate a reactive context (invalidating function)
Functions that can do both. (I lied there are 3)
When you call a reactive function it creates a context that meteor stores globally and to which the reactive function subscribes an invalidation callback. The function that you pass to a reactive function, or any functions that run from within it, can be an invalidating function and can grab the current context and store it locally. These functions can then at any time, like on a db update or simply a timer call, invalidate that context. The original reactive function would then receive that event and re-evaluate itself.
Here's a step by step using meteor functions (note that Tracker.autorun used to be called Deps.autorun):
Tracker.autorun(function(){
alert("Hello " + Session.get("name"));
});
Session.set("name", "Greg");
autorun takes a function as its parameter
before autorun runs this function, it creates a context
autorun attaches a callback to the context's invalidation event
This callback will re-run the function passed to autorun
The function is then run in the context for the first time.
Meteor stores this context globally as the currently active context
Inside the function is another function: Session.get()
Session.get() is both a reactive function and an invalidating function
Session.get sets up it's own context and associates it internally with the key "name"
Session.get retrieves the current context (autorun's context) globally from meteor
The invalidation callback that Session.get registers to it's own context, will simply invalidate it's enclosing context (in this case, autorun's context)
So now we have 2 contexts, autorun's and session.get's
when these functions return, meteor cleans up the active context global variable
Session.set is another function capable of invalidating a context.
in this case we're invalidating all contexts created by Session associated with the key "name"
All of those contexts, when invalidated, run their invalidation callbacks.
Those callbacks just invalidate their enclosing contexts (That's the design of Session.get and not what a invalidation callback must do)
Those enclosing contexts now run their invalidation callbacks.
In the autorun case, that callback runs the function we originally passed to autorun and then sets up the context again.
The whole implementation is actually rather straight forward as well, you can see it here:
https://github.com/meteor/meteor/blob/master/packages/tracker/tracker.js
And a good example of how it works can be found here:
https://github.com/meteor/meteor/blob/master/packages/reactive-dict/reactive-dict.js
Reactive programming is not actually meteor or JS specific
you can read about it here: http://en.wikipedia.org/wiki/Reactive_programming