I recently had the chance to speak at Rebuild, a design & development conference in Indianapolis. My talk was a quick introduction to Meteor, in the form of a step-by-step walk-through that takes you through building a simple Meteor app from start to finish.

But as I was preparing for the talk and going over the slides, I kept stumbling over one section. I just couldn’t find a straightforward way to explain data contexts.

So I thought it would be worth it to revisit the topic for the blog, and take a better look at one of the key building blocks of Meteor apps.

A Visual Explanation

So what is a data context exactly? To understand this, let’s begin with a piece of plain old boring HTML enclosed inside a Meteor template tag:

Dumb static HTML.

Now in most situations, you’ll probably want this data to be dynamic. So we’ll replace these static strings by dynamic placeholder tags:

A Meteor template’s source code.

Another way to look at it is imagining those tags are “holes” in the template, waiting to be filled in with something:

A Meteor template.

As you may have guessed, that “something” is the data context. When we add in that missing part, we end up with our rendered template displayed in the user’s browser:

A rendered template.

Moreover, since our template is dynamic, we can easily apply it to a different data context:

A different data context.

And finally, there’s nothing preventing us from applying a different template to the same data context:

Different template, same data context.

#with

Now that we have the basics straight, we need to talk about the main difficulty when dealing with data contexts: most of the time, we set them and use them implicitly. While that can be a great time-saver, it can also be the source of a lot of confusion and ambiguity.

The easiest way to set a data context is with the {{#with}} template tag. The tag takes an argument, and that argument will become the data context of the block enclosed within the tag. For example, in the following example the template will use the profile object as its data context:

<template name="profile">
  {{#with profile}}
    <img src="{{avatar}}"/>
    <p>{{name}}</p>
  {{/with}}
</template>

{{#with}} is fairly flexible. You can apply it to just a fragment of a template, and even nest it. For example, assuming our profile object looks something like this:

var profile = {
  avatar: 'avatar.jpg',
  name: {
    first: 'John',
    last: 'Smith'
  }
}

We could very well write:

<template name="profile">
  <h2>Profile</h2>
  {{#with profile}}
    <img src="{{avatar}}"/>
    {{#with name}}
      <p>{{first}} {{last}}</p>
    {{/with}}
  {{/with}}
</template>

So far so good. But {{#with}} is far from the only way of setting data contexts.

#each

The {{#each}} helper tag is another common way of setting a data context. Like {{#with}}, {{#each}} takes an argument (usually a cursor, i.e. the result of a Collection.find() call).

The tag will then loop over that argument, and set the data context for each iteration of the loop.

<template name="profiles">
  <ul>
    {{#each profiles}}
      <li>
        <img src="{{avatar}}"/>
        <p>{{name}}</p>
      </li>
    {{/each}}
  </ul>
</template>

So in this case, the data context is set implicitly not to profiles, but to each elements contained within profiles successively.

Template Includes

We’ve just seen the two most common ways of setting data contexts. But did you know you could also set data contexts using template include tags?

Let’s imagine we have a post template, and that we want to display the post’s author’s profile. We’ll set the profile template’s data context to author as we include it from within that post template:

<template name="post">
  {{#with post}}
    <h1>{{title}}</h1>
    <p>{{content}}</p>
    <div>{{> profile author}}</div>
  {{/with}}
</template>

Note that this won’t work if you’re already setting a data context inside the profile template (such as with a {{#with}} tag, for example).

Iron Router

Iron Router is the main routing package for Meteor, and it includes powerful features for setting and manipulating data contexts through its data function:

this.route('profile', { 
  path: '/profile/:_id',
  template: 'profile',
  data: function() { return Users.findOne(this.params._id); }
});

Here, we’re defining a route that will bring up the profile template while setting its data context to Users.findOne(this.params._id).

Setting the data context in the router means you don’t need to do it in the template. Which in turns makes it much easier to reuse your template for different data contexts.

Template Helpers

So far we’ve only talked about data contexts in the, um, context of HTML templates. But data contexts are also very important when dealing with their JavaScript counterpart, template helpers.

In a template helper, the data context is accessible through the this keyword. It’s also important to note that a template helper will inherit its data context from the template itself.

In other words, depending on where you call a template helper, it might end up with a completely different data context!

Let’s go back to our profile example:

<template name="profile">
  <h2>Profile</h2>
  {{#with profile}}
    <img src="{{avatarPath}}"/>
    {{#with name}}
      <p>{{fullName}}</p>
    {{/with}}
  {{/with}}
</template>

Here’s the corresponding JavaScript code:

Template.profile.helpers({
  profile: function () {
    return Users.findOne(Session.get('someUserId'));
  },
  avatarPath: function () { // data context set to profile
    return "images/" + this.avatar;
  },
  fullName: function () { // data context set to profile.name
    return this.first + " " + this.last;
  }
});

Because the template’s {{#with}} tag sets the data context to the name property of the profile object, the avatarPath and fullName helpers will end up having different data contexts, even though they belong to the same template.

Debugging Data Contexts

When it comes to debugging data context problems, your best friend is still the humble console.log. Just add a console.log(this) to your helper to know what context it’s using:

Template.profile.helpers({
  fullName: function () { // data context set to profile.name
    console.log(this);
    return this.first + " " + this.last;
  }
});

Alternatively, if you need to find out more about the data context of a specific template fragment, you can also write a dedicated {{log}} helper:

Template.profile.helpers({
  log: function () {
    console.log(this);
  }
});

And use it directly in your templates:

<template name="profile">
  {{#with profile}}
    {{log}}
    <img src="{{avatar}}"/>
    <p>{{name}}</p>
  {{/with}}
</template>

Update: Parent Data Context

In the comments, Rune Jeppesen asked if there was any way to access the parent data context. I wasn’t sure, so I asked Percolate’s Zoltan Olah, and he gave me this clever way to do it.

It turns out that since the 0.8 Blaze update, you can access the parent context with the .. keyword from within a template. So we’ll just pass this keyword as an argument to a template helper:

<template name="profile">
  <h2>Profile</h2>
  {{#with profile}}
    <img src="{{avatarPath}}"/>
    {{#with name}}
      {{parentHelper ..}}
      <p>{{fullName}}</p>
    {{/with}}
  {{/with}}
</template>

And access it from within our JavaScript code:

Template.profile.helpers({
  parentHelper: function (parentContext) {
    console.log(this); // profile.name data context
    console.log(parentContext); // profile data context
  }
});

And by the way, you can access the grandparent data context the same way using the ../.. keyword.

Wrapping Up

Meteor is a fairly free-form framework, and it’s ultimately up to you to decide how to architecture your app. Still, I think it’s generally good practice to try and make your templates as “data-context-free” as possible for maximum flexibility. In other words, set the data context outside of the template, either through Iron Router or include tags, rather than inside the template with the {{#with}} tag.

Data contexts are a simple concept once you get the hang of them, but the fact that they’re so often declared implicitly can make for a tough learning curve. Hopefully, this article will help you get a better understanding of their subtleties.