Meteor: Creating Custom Reactive Data Sources
A core concept in Meteor is reactivity. Basically, this means that Meteor can re-run computational functions automatically whenever their (reactive) data sources change. Typically, this happens without your having to think about it, since you’re using an in-built reactive data source, such as database collections. A typical example of a function needing to react to a data source (e.g. a collection) changing is a template helper, which needs to produce a new value to base the template rendering on.
Custom Reactive Data Sources
However, sometimes you will be required to create a reactive data source of your own. And thankfully, Meteor provides an API for this, centered around the Tracker component. It is quite simple when it comes down to it. When reading from your reactive data source, you have to acquire a corresponding Tracker.Dependency and call depend() on it. F.ex.:
getDependency = (dropzone) ->
if !dropzone._meteorDependency?
dropzone._meteorDependency = new Tracker.Dependency()
dropzone._meteorDependency
hasFiles: (dropzone) ->
dep = getDependency(dropzone)
dep.depend()
!R.isEmpty(dropzone.files)
Conversely, when modifying your reactive data source, you must acquire the same Tracker.Dependency object and call changed() on it:
clearDropzone: (dropzone) ->
dep = getDependency(dropzone)
dropzone.removeAllFiles(true)
dep.changed()
As you can see, it’s quite simple to register dependencies on reactive data sources, and to notify that they have changed, thanks to Meteor’s reactive framework. The remaining piece of the puzzle is how to implement the actual reaction (i.e. that dependent functions get re-run upon changes).
Consuming Reactive Data Sources
Often you don’t have to think about implementing reactions to reactive data sources, as you are plugging into parts of Meteor that are automatically reactive, f.ex. template helpers. When you write such hooks, they automatically get registered as dependents of any reactive data sources that they consume directly or indirectly (for example a custom reactive data source as described above).
However, if you write a function that isn’t called from a reactive origin, you’ll have to ensure its reactivity yourself by making use of Tracker.autorun. F.ex.:
Tracker.autorun(->
# This will get re-run whenever dropzone changes observable state
hasFiles = dropzone.hasFiles()
logger.debug("Dropzone has files: #{hasFiles}")
)