40

How can I define a Meteor method which is also callable in a template helper?

I have these two files:

file: lib/test.js

Meteor.methods({
    viewTest : function (str) {
        return str;
    }
});

file: client/myView.js

Template.helloWorld.helpers({
    txt : function () {
        var str = Meteor.call('viewTest', 'Hello World.');
        return str;
    }
});

When I give "str" a normal string everything works fine. But in this case my template does not get any value. I defined - for the test - in the same file where the method is a normal function and tried to call the function. The error I got was that the function does not exist. So I think that Meteor tries to render the template before it knows anything about the methods I defined for it. But I think that this is a bit unusual - isn't it?

aknuds1
  • 65,625
  • 67
  • 195
  • 317
TJR
  • 6,307
  • 10
  • 38
  • 64

6 Answers6

59

There is now a new way to do this (Meteor 0.9.3.1) which doesn't pollute the Session namespace

Template.helloWorld.helpers({
    txt: function () {
        return Template.instance().myAsyncValue.get();
    }
});

Template.helloWorld.created = function (){
    var self = this;
    self.myAsyncValue = new ReactiveVar("Waiting for response from server...");
    Meteor.call('getAsyncValue', function (err, asyncValue) {
        if (err)
            console.log(err);
        else 
            self.myAsyncValue.set(asyncValue);
    });
}

In the 'created' callback, you create a new instance of a ReactiveVariable (see docs) and attach it to the template instance.

You then call your method and when the callback fires, you attach the returned value to the reactive variable.

You can then set up your helper to return the value of the reactive variable (which is attached to the template instance now), and it will rerun when the method returns.

But note you'll have to add the reactive-var package for it to work

$ meteor add reactive-var
Peppe L-G
  • 7,351
  • 2
  • 25
  • 50
Petrov
  • 4,200
  • 6
  • 40
  • 61
  • 1
    Is this still the preferred method? – Nick Redmark Aug 06 '15 at 15:26
  • Isn't this non-reactive though? Since the `ReactiveVar` only gets updated in the `created` handler? So if `asyncValue` gets updated, it wouldn't update your template unless it refreshed...? – evolross Oct 13 '15 at 02:10
  • @evolross the op didnt ask for reactive, just that the method call's result would update the template. – Petrov Oct 14 '15 at 21:07
  • 1
    True... just FYI'ing in case people think it would be automatically reactive since it's using a template helper (perhaps people new to Meteor). – evolross Oct 14 '15 at 21:38
  • Just an FYI to those late to the party, it's now the `onCreated()` function you need: http://docs.meteor.com/#/full/template_onCreated – Nathan Hornby Dec 01 '15 at 14:25
  • 1
    Great, but how can I now iterate if I'm getting an object of objects back? #each works accepts arrays only. I want to be able to access properties of every object I get returned with my call. – user841760 Jul 22 '16 at 13:01
24

Sashko added a neat little package called meteor-reactive-method to solve this problem.

$ meteor add simple:reactive-method
Template.helloWorld.helpers({
  txt: function() {
    return ReactiveMethod.call('viewTest', 'Hello World.');
  }
});

As I point out in common mistakes, helpers should be side-effect free, so I'd use this technique with caution. However, it's a really handy shortcut for cases where:

  • The helper should fire only once (it doesn't depend on reactive state).
  • The invoked method doesn't mutate the database.
David Weldon
  • 63,632
  • 11
  • 148
  • 146
12

You need to interface your return value with a Session variable as the request is asynchronous:

Template.helloWorld.helpers({
    txt : function () {
        return Session.get("txt") || "Loading";
    }
});

Template.helloWorld.created = function() {
    Meteor.call('viewTest', 'Hello World.', function(err, result) {
        Session.set("txt", result);
    });

}

So .rendered should be called once when your template loads (at least it should with the newer version of Meteor.)

The value would be called and displayed. Otherwise it would say "Loading".

Tarang
  • 75,157
  • 39
  • 215
  • 276
  • This solved the problem. But in my special case this is not a good solution because i have to get tge right return value before the template was rendered. In my special case i now use a typical javascript function to solve it. U got the "right answer" mark because it would solve it - doesnt matter if it solves my special case. Thanks – TJR Mar 04 '14 at 09:29
  • @TimoRütten have you considered not using a Meteor.call and using a transform with a local collection instead? – Tarang Mar 04 '14 at 11:15
  • A local collection ? The usage of the collection doesnt matter in my case. The problem is that i have to get a return value before the template is rendered. Maybe i dont understood your question ? – TJR Mar 04 '14 at 11:33
11

Methods on the client side are asynchronous, and their return value is always undefined. To get the actual value returned by the method, you need to provide a callback:

Meteor.call('method', 'argument', function(error, result) {
    ....
});

Now, there's no easy way to use the result in your helper. However, you can store it in your template as a data object and then return it in the helper:

Template.template.created = function() {
    var self = this;
    self.data.elephantDep = new Deps.Dependency();
    self.data.elephant = '';
    Meteor.call('getElephant', 'greenOne', function(error, result) {
        self.data.elephant = result;
        self.data.elephantDep.changed();
    });
};

Template.template.showElephant = function() {
    this.elephantDep.depend();
    return this.elephant;
};
Hubert OG
  • 19,314
  • 7
  • 45
  • 73
  • Alternatively, you can use Session variable as Akshat suggested and do not worry about the dependencies. This method, however, has the advantage that it's local to the template. – Hubert OG Mar 03 '14 at 13:58
  • Thanks Hubert, I've been looking for a while for something that gave template specific Dependencies. So if the template is changed/loaded back are the dependencies cleared up too? – Tarang Mar 03 '14 at 14:06
  • Yup this works for me with Template specific dependencies. I did something similar but cleared them out with destroyed too – Tarang Mar 03 '14 at 14:43
2

This is expected behavior. You are not using methods as they are intended.

Your code defines a server method viewTest and a corresponding method stub on the client with the same name.

Meteor.call('viewTest', 'Hello World.'); remotely calls viewTest on the server and in parallel runs the stub on the client.

Regarding the return value of the stub please see the documentation here, in particular:

On the client, the return value of a stub is ignored. Stubs are run for their side-effects: they are intended to simulate the result of what the server's method will do, but without waiting for the round trip delay.

Regarding the return value of the server method please see the documentation here, in particular:

On the client, if you do not pass a callback and you are not inside a stub, call will return undefined, and you will have no way to get the return value of the method. That is because the client doesn't have fibers, so there is not actually any way it can block on the remote execution of a method.

Tobias
  • 4,034
  • 1
  • 28
  • 35
0

There is a fine little package for this by @msavin:
https://atmospherejs.com/msavin/fetcher

avalanche1
  • 3,154
  • 1
  • 31
  • 38