Fog of Code

Random thoughts about code, software and life

Meteor inconsistent reactive context isolation with {{#each}}

with 2 comments

My previous post addressed the details of how/where Meteor implements reactive scope isolation when iterating a cursor. But here is a short reminder. According to meteor docs:

Templates:

When the database cursor is passed to #each, it will wire up all of the machinery to efficiently add and move DOM nodes as new results enter the query.

cursor.forEach:

When called from a reactive computation, forEach registers dependencies on the matching documents.

So in practice when iterating over a cursor using {{#each}} a dependency is registered for every single document as well as for the entire query result set. Meaning that a change in a single document will only trigger re-rendering of a single iteration instead of the entire loop. This is a good thing but it has side effects.

One such side effect is that the behaviour of {{#each}} is inconsistent when iterating a cursor or an array. When iterating a cursor the iteration code is isolated from the outside code. Of course it isolates the documents  which is why it is implemented that way to beging with. But it also isolated all other reactive data inside that scope. But… when iterating an array there is no implicit isolation at all.

Consider the following example:

body>
  {{> hello}}
</body>

<template name="hello">

  <!-- Iterate over a cursor -->
  <ul>
  {{#isolate}}
  {{log "Rendering 1"}}
  {{#each strings}}
    <li>{{string}}</li>
    {{read}}
  {{/each}}
  {{/isolate}}
  </ul>

  <!-- Iterate over an array -->
  <ul>
  {{#isolate}}
  {{log "Rendering 2"}}
  {{#each stringsFetch}}
    <li>{{string}}</li>
    {{read}}
  {{/each}}
  {{/isolate}}
  </ul>

</template>

 

if (Meteor.isClient) {
  Strings = new Meteor.Collection(null)

  Meteor.startup(function ()
  {
    Strings.insert({string: "Foo"});
    Strings.insert({string: "Bar"});
  });

  Template.hello.log = function (message) {
    console.log(message);
  }

  Template.hello.strings = function() {
    return Strings.find();
  }

  Template.hello.stringsFetch = function() {
    return Strings.find().fetch()
  }

  Template.hello.read = function() {
    // Just add dependency for "read" Session variable
    Session.get("read");
  }
}

The same loop runs twice. The first time iterating over a cursor and the second time iterating over a an array (by fetching cursor documents). It runs as expected and produces the sae output:

* Foo
* Bar

* Foo
* Bar

Notice the {{read}} helper that is being called. Its only purpose is to register a dependency on arbitrary reactive data. In this example Session.get("read")

Now lets make a little experiment by calling Session.set("read", 100) in the console. The output stays the same but “Rendering 2” is logged. But why isn’t “Rendering 1” printed? The answer is simple: Because Meteor isolates the inner scope of {{#each}} when iterating over a cursor so the call to {{read}} does not invalidate the outer context.

So the thing to remember is simple. When using {{#each}} keep in mind is your loop is isolated or not . It most cases it won’t matter, but when it does it can be hard to track this cause.

meteor –version
Release 0.6.2.1

Advertisement

Written by xyand

May 10, 2013 at 6:36 pm

Posted in Meteor

Tagged with , ,

2 Responses

Subscribe to comments with RSS.

  1. […] my previous post I demonstrated some non-intuitive behaviour of Meteor Handlebars implementation when iterating […]

  2. Very Interesting. You answer several questions I had. In my case I have to do a fair amount of client side-side transformation of my collections (on the fly) before passing them to blaze. I’m wondering if there is some way of transforming a server-side collection into a client-side yet collection without using fetch to get the server-side collection. This might keep some of the efficiencies built into blaze.

    David

    April 24, 2015 at 3:43 am


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: