Meteor Tips and Workarounds

Here are a few things that I’ve learned while working on Dominus that I wish I had known when I started working with Meteor.

Local template variables

Meteor sessions are great but sometimes you want a local variable that you can pass between helpers, events, and created/rendered functions.

Here’s an example with sessions.

<template name=”info”>
{{text}}
</template>
Template.info.helpers({
text: function() {
return Session.get(‘text’);
}
});
Template.info.events({
‘click .button’: function() {
Session.set(‘text’, ‘bar’);
}
});
Template.info.onCreated({
Session.set(‘text’, ‘foo’);
});

This works but the session variable is global. Also if you have many instances your template then this doesn’t work. Here’s another way.

<template name=”info”>
{{text}}
</template>
Template.info.helpers({
text: function() {
return Template.instance().text.get();
}
});
Template.info.events({
‘click .button’: function(e,t) {
Template.instance().text.set(‘bar’);
}
});
Template.info.onCreated({
this.text = new ReactiveVar(‘foo’);
this.text.set(‘foo’);
});

Now not only is the variable local instead of global it is local to that instance of the template.

This requires the ReactiveVar package which was created by Meteor Development Group.

Don’t render until ready

If there is a noticeable lag when a template renders don’t render the slow helpers until they’re ready. In Dominus when someone clicked on their castle there was a lag before the info showed up. The profiler in Chrome was helpful in figuring out that this was mostly because of one slow helper.

Here’s an example of how it worked.

<template name=”castleInfo”>
{{complicatedHelper}}
</template>
Template.castleInfo.helpers({
complicatedHelper: function() {
// complicated calculations or lots of db calls
return complicatedStuff;
}
});

This created a couple seconds of lag when the user clicked on their castle. Below is another way to do this that makes for a much better user experience. The template renders instantly then when the helper is ready the page updates.

<template name=”castleInfo”>
{{#if complicatedStuffReady}}
{{complicatedHelper}}
{{else}}
Loading
{{/if}}
</template>
Template.castleInfo.helpers({
complicatedStuffReady: function() {
return Template.instance().complicatedStuffReady.get();
},
    complicatedHelper: function() {
return Template.instance().complicatedStuff.get();
},
});
Template.castleInfo.created = function() {
self.complicatedStuff = new ReactiveVar(null);
self.complicatedStuffReady = new ReactiveVar(false);
  this.autorun(function() {
// complicated calculations or lots of db calls
self.complicatedStuff.set(results);
self.complicatedStuffReady.set(true);
})
}

Client only collections

In the Meteor docs under Meteor.subscribe there is this line.

If more than one subscription sends conflicting values for a field (same collection name, document ID, and field name), then the value on the client will be one of the published values, chosen arbitrarily.

There are many places in Dominus that need the user db and they all need different attributes of it. That meant that all of those subscriptions had to subscribe to every attribute in the user object that any other subscription might need. So if the rankings panel needed the top 15 users by networth it had to also subscribe to a bunch of other attributes that it didn’t need.

Here’s a workaround.

if (Meteor.isClient) {
NetworthRankings = new Mongo.Collection(‘networthRankings’);
}
if (Meteor.isServer) {
Meteor.publish(‘networthRankings’, function() {
var self = this;
var query = Meteor.users.find({}, {sort: {networth: -1}, fields: {networth:1}, limit:15});
Mongo.Collection._publishCursor(query, self, ‘networthRankings’);
return self.ready();
})
}
<template name=”rankingsPanel”>
{{#each rankings}}
{{name}}
{{/each}}
</template>
Template.rankingsPanel.onCreated({
this.subscribe('networthRankings');
});
Template.rankingsPanel.helpers({
rankings: function() {
return NetworthRankings.find({}, {sort: {networth: -1}});
}
})

This creates a client only collection and subscribes to only Meteor.user().networth.

Returning errors to forms

This is the best way I’ve found to return errors from methods to forms. It wasn’t obvious to me from the Meteor docs.

<template name=”aForm”>
<div id=”alert” style=”display:none;”></div>
<input type=”text” id=”formInput”>
<button id=”formButton”></button>
</template>
Template.aForm.events({
‘click #formButton’: function(event, template) {
var input = template.find(‘#formInput’);
var alert = template.find(‘#alert’);
$(alert).hide();
    Meteor.call(‘saveInput’, $(input).val(), function(error, result) {
if (error) {
$(alert).show()
$(alert).html(error.error)
} else {
// success
}
}
}
});
Meteor.methods({
saveInput: function(text) {
if (text.length > 20) {
throw new Meteor.Error(‘Text is too long.’);
}
return true;
}
})