Are cursors really reactive data sources? and a small journey to understand the magic of {{#each}}


#1

I was reading through The Meteor Manual when I came across this tidbit which thoroughly surprised me:

Using Collection.find() doesn’t set up any reactive dependencies.

I didn’t think I had been asleep at the wheel, but I wouldn’t be surprised if I misunderstood something. So I jumped over to the official docs and took a closer look. I think the message is a bit muddled because of the opening line below, but sure enough it is at least indirectly implied that cursors alone will not setup reactive dependencies.

From: http://docs.meteor.com/#/full/find

Cursors are a reactive data source. On the client, the first time you retrieve a cursor’s documents with fetch, map, or forEach inside a reactive computation (eg, a template or autorun), Meteor will register a dependency on the underlying data."

“But wait!” I said, "don’t they return only cursor.find() in plenty of examples that are in theory reactive?

Some more sifting around in the docs shows this to be true:

<!-- in myapp.html -->
<template name="players">
  {{#each topScorers}}
    <div>{{name}}</div>
  {{/each}}
</template>

// in myapp.js
Template.players.helpers({
  topScorers: function () {
    return Users.find({score: {$gt: 100}}, {sort: {score: -1}});
  }
});

Even though this does work, how can it? A call to Users.find() does not setup any reactive dependencies, right? Or am I taking crazy pills!

My search for answers eventually lead me to the spacebars documentation which provides what I believe is the closest thing to answering my question(s).

So I am left with the educated guess that passing a cursor to the {{#each}} helper, or for that matter the {{#if}} or {{#with}}, leads eventually to the calling of .fetch(), .map(), or .forEach() on the cursor. This in turn establishes reactive dependencies.

If my assumption is correct then I guess this story is just a vote to make this more explicit in the documentation. Additionally maybe there is a nuanced differentiation between a “Reactive data source” which the documentation calls all cursors, and something that establishes reactive dependencies inside an autorun or template (or maybe not).

Also does passing a cursor vs. the results of fetch() to {{#each}} still provide the same benefit described here now that Blaze is used? In the latter case would the whole {{#each}} block be re-rendered if the data in the cursor changes?


#2

After reading the Discover Meteor ebook. It’s the subscribe() that is reactive and send a push/update whenever the collections have received new data after the current index in cursor, therefore, rerender the template automatically without having to reload the page.

Correct me if I’m wrong.


#3

When you call Collection.find() (no matter fetch()) you are set up new observer with the Cursor returning by Collection.find(). So Cursor is a simple IdMap instance that contains all the documents, relevant to Collection.find() selector and options. Every Cursor has its own observer registered for the Collection. It means that Cursor doesnt have a dependency but while it lives all the Collection’s changes affects on a Cursor’s IdMap and re-runs Blaze Each helper.


#4

cursors are reactive data sources, but they can’t force recurring computations on their own

when you use a reactive data source inside a helper, Blaze automatically uses this dependency and re-runs the helper everytime the source changes

however, if I do something like

Template.foo.created = function() {

    Collection.find();

}

the created event wouldn’t be automatically re-computed, unless I wrap the cursor with a Tracker/Template.autorun or write up a dependency relationship manually (using Tracker.Dependency)

so perhaps the documentation requires some revising for better clarity

as for Blaze’s built-in tags, you don’t need to explicitly call fetch() - Blaze will do that implicitly with cursors
this is very convenient when you want to directly use cursors stored in the template’s data context, without helpers


#5

A cursor passed to an #each block helper is passed to a package called observe-sequence that starts an .observe() on the cursor.


#6

I would say that cursors are a reactive data source in the sense that attempting to get data from a cursor through any provided method (i.e. fetch, map, forEach and not the underlying map) in the context of a reactive computation, will result in dependency registration.

Exploring Further

forEach is actually the originator of dependency registration and fetch and map call forEach internally.

LocalCollection.Cursor.prototype.forEach = function (callback, thisArg) {
  var self = this;

  var objects = self._getRawObjects({ordered: true});

  if (self.reactive) {
    self._depend({
      addedBefore: true,
      removed: true,
      changed: true,
      movedBefore: true});
  }

  _.each(objects, function (elt, i) {
    // This doubles as a clone operation.
    elt = self._projectionFn(elt);

    if (self._transform)
      elt = self._transform(elt);
    callback.call(thisArg, elt, i, self);
  });
};

Taking note of the self._depend

LocalCollection.Cursor.prototype._depend = function (changers, _allow_unordered) {
  var self = this;

  if (Tracker.active) {
    var v = new Tracker.Dependency;
    v.depend();
    var notifyChange = _.bind(v.changed, v);

    var options = {
      _suppress_initial: true,
      _allow_unordered: _allow_unordered
    };
    _.each(['added', 'changed', 'removed', 'addedBefore', 'movedBefore'],
           function (fnName) {
             if (changers[fnName])
               options[fnName] = notifyChange;
           });

    // observeChanges will stop() when this computation is invalidated
    self.observeChanges(options);
  }
};

So my opinion is that while we don’t get a reactive computation by just calling a find, its still a reactive data source because calling collection.find() alone would not be useful and so you’ll always find yourself with the result of a find, fetch or forEach.