iron-router does not wait for subscription - meteor

My router function is defined like this
this.route('time', {
template: "app",
yieldTemplates: { 'appNav': {to: 'top'}, 'time': {to: 'appPage'} },
data: function() {
console.log("data is ready for time " + (personsSub.ready() && tenantsSub.ready() && teamsSub.ready()));
return {
dataReady: personsSub.ready() && tenantsSub.ready() && teamsSub.ready()
}
},
waitOn: function(){
return [personsSub, tenantsSub, teamsSub];
}
});
the 3 subscriptions are:
var personsSub = Meteor.subscribe("allPersons");
var tenantsSub = Meteor.subscribe("allTenants");
var teamsSub = Meteor.subscribe("allTeams");
In the console I can see 2 entries when I navigate to this route
data is ready for time false
data is ready for time true
I obviously did not understand the meaning of waitOn. The expected result is that the data part be only called once and that it be called after all the subscriptions are ´ready´

Try
waitOn: [personsSub, tenantsSub, teamsSub];
Im not sure the function/return is needed.

Related

how to waitOn data ready using iron-router and publish-composite

I have the following route :
this.route('groupPage', {
path: '/group/:_groupId',
waitOn: function(){
return Meteor.subscribe("groupPage", this.params._groupId);
},
data: function() {
var group = Groups.findOne({_id: this.params._groupId});
var members = Meteor.users.find({_id : {$in: group.memberIds}}); ******** ISSUE HERE******
return {
group: group,
members: members,
}; }});
and the following publication :
Meteor.publishComposite('groupPage', function(groupId, sortOrder, limit) {
return {
// return the group
find: function() {
if(this.userId){
var selector = {_id: groupId};
var options = {limit: 1};
return Groups.find(selector, options);
}
else{
return ;
}
},
children: [
{ // return the members
find: function(group) {
var selector = {_id: {$in: group.memberIds} };
return Meteor.users.find(selector);
}
}
]}}) ;
Now my issue is that : when the related page renders for the first there is no problems but when i actualize the group Page view the line : var members = Meteor.users.find({_id : {$in: group.memberIds}}); gives me the error : undefined object don't have memberIds property. i guess it's because the subscription is not yet ready when doing group.memberIds , isn't it ? Please a hint.
Thanks.
The data function doesn't wait for the subscription to be ready. Further more, subscriptions in the router are considered an anti-pattern for the most part, and should be done in the template: https://www.discovermeteor.com/blog/template-level-subscriptions/
I would pass to the template the groupId, and then get the group and members in the template, like so:
this.route('groupPage', {
path: '/group/:_groupId',
data: function() {
return {
_groupId: this.params._groupId,
}
}
});
and then in the template file:
Template.groupPage.onCreated(function(){
this.subscribe("groupPage", this.data._groupId);
})
Template.groupPage.helpers({
members(function(){
tempInst = Template.instance()
var group = Groups.findOne({_id: tempInst.data._groupId});
return Meteor.users.find({_id : {$in: group.memberIds}});
})
})
The general pattern of your route and publication are all solid. I suspect it's something simple such as:
There is no group with the _id you're using
You're not logged in when you load the route
Here's a version of your code that guards against the error. Note that the publication executes this.ready() instead of just returning if the user is not logged in.
this.route('groupPage', {
path: '/group/:_groupId',
waitOn: function(){
return Meteor.subscribe("groupPage", this.params._groupId);
},
data: function() {
var group = Groups.findOne({_id: this.params._groupId});
var members = group && Meteor.users.find({_id : {$in: group.memberIds}});
return { group: group, members: members };
}
});
Meteor.publishComposite('groupPage', function(groupId,sortOrder,limit) {
return {
find: function() {
if (this.userId) return Groups.find(groupId);
this.ready()
}
},
children: [
find: function(group) {
var selector = {_id: {$in: group.memberIds} };
return Meteor.users.find(selector);
}
]
});

Meteor when many subscriptions are ready

I'm creating a chat app. I hope i can add a new "hello" message if i check the messages count of current chat is equal to 0 (Problem #1). Also i have a dictionary as a collection for translation. But t() returns EN variant (Problem #2)
t = function(text) {
var res = Dictionary.findOne({o:text});
return res && res.t || text;
}
Meteor.startup(function () {
Deps.autorun(function () {
Meteor.subscribe('dictionary', Session.get('lang'), function(){
Session.set('dictionaryReady', true);
});
Meteor.subscribe('chats', Session.get('domain'), function(){
if (chatCurrent(Meteor.userId(), Session.get('domain')).count()===0 //true, even is not actually [problem_#1]
&& Session.get('dictionaryReady') //true, but next function t() doesn't work properly [problem #2]
) {
var mudata = Session.get('my_manager') ? udata(Session.get('my_manager'), Session.get('domain')) : null,
hello = mudata && mudata.hello || t('Hello! How I can help you?'),
name = mudata && mudata.name || t('Anna');
Meteor.call('create_message', {chat: Meteor.userId(), to: Meteor.userId(), text: hello, name: name, from: Session.get('my_manager'), domain: Session.get('domain'), last_manager: Session.get('my_manager')});
});
});
});
Problem #1 and Problem #2 everytime when page just loaded. So when i refresh the page i get another "hello message" on default EN locale.
Here is how you can render your template only once your subscriptions are ready. This is a solution taken from meteor kitchen generated code.
first you create a "loading" template
<template name="loading">
<div class="loading">
<i class="fa fa-circle-o-notch fa-4x fa-spin"></i>
</div>
</template>
Second, attach to your template a route controller. Here is a simplified version of it (but it should work):
this.myTemplateController = RouteController.extend({
template: "myTemplate",
onBeforeAction: function() {
this.next();
},
action: function() {
if(this.isReady()) { this.render(); } else { this.render("loading"); }
},
isReady: function() {
var subs = [
Meteor.subscribe("sub1", this.params.yourParam),
Meteor.subscribe("sub2", this.params.yourParam),
Meteor.subscribe("sub3", this.params.yourParam)
];
var ready = true;
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
data: function() {
return {
params: this.params || {},
yourParamWhatever: Chat.findOne({_id:this.params.yourParam}, {})
};
},
});
Now you should have all your subscriptions ready when your template is loaded.
Concerning the translation, you could have a look at TAPi18n package that I highly recommend. It is quite easy to implement.

meteor iron:router Blaze.ReactiveVar calling multiple times

I have a route I call many times. I have to subscribe two collections for having all datas, here's a snapshot:
var one = new Blaze.ReactiveVar(false);
var two = new Blaze.ReactiveVar(false);
this.route('stopIndex', {
path: '/stop/:section/:stop_id',
waitOn: function() {
Meteor.call('getTripIdsForStop', {
stop_id: this.params.stop_id,
from: fromNow(),
to: toMax(),
db: prefix
}, function(err, ids) {
DEBUG && console.log('TRIP_IDS:', ids);
Meteor.subscribe(prefix + '_trips', {
trip_id: {$in: ids}
}, function() {
one.set(true);
});
Meteor.subscribe(prefix + '_stop_times', {
trip_id: {$in: ids}
}, function() {
two.set(true);
});
});
return [
function () { return one.get(); },
function () { return two.get(); }
];
},
The first time I call the route, all goes fine. The second time, the one and two vars are already setted to true so the waitOn doesn't wait and I get a no data message on my template for some seconds, until collections responds. I've tried putting on the first lines of waitOk method:
one.set(false);
two.set(false);
but this makes the waitOn to wait forever. Am I doing something wrong or missing something? Thanks for the help.
I've solved this way:
Router.onStop(function() {
one.set(false);
two.set(false);
});
that invalidates ReactiveVars and will wait. I've also moved all code from waitOn to data. Now the waitOn is like this:
return [
function () { return one.get(); },
function () { return two.get(); }
];

Meteor : subscribe to collection subset & collection total count

Here is my issue :
I'm subscribing to a subset of a Collection for "infinite pagination" in iron-router (like the Discover Meteor example) :
ApplicationsListController = RouteController.extend({
template: 'applicationsList',
increment: 10,
limit: function(){
return parseInt(this.params.applicationsLimit) || this.increment;
},
findOptions: function(){
return {sort: {name: 1}, limit: this.limit()};
},
subscriptions: function(){
this.applicationsSub = Meteor.subscribe('applications', this.findOptions()) ;
},
applications: function(){
return Applications.find({}, this.findOptions());
},
data: function(){
var hasMore = this.applications().fetch().length === this.limit();
var nextPath = this.route.path({applicationsLimit: this.limit() + this.increment});
return {
applications: this.applications(),
ready: this.applicationsSub.ready,
nextPath: hasMore ? nextPath : null
};
}
});
//(...)
this.route('applicationsList', {
path: '/applications/:applicationsLimit?',
controller: ApplicationsListController
});
I'm publishing it well, no problem there. But, on the same page, I also need the total count of the entire collection (not only the subset). I publish it like that :
Meteor.publish('applications', function(options){
return Applications.find({}, options);
});
Meteor.publish('applicationsCount', function(){
return Applications.find().count();
});
But there is something I guess I did not understand. I need to use the total count in my template, but I just can't see how to subscribe to "just a number", without creating a new collection (which I don't want to do).
I've seen the 'counts-for-room' example on Meteor Doc, but it seems that it is far from what I need (I don't have room with message in it, I just need to count my applications without getting them all on client).
Thanks a lot, I hope I was clean enough.
Have a great day.
If you want the Count of the collection.
Try with the publish-counts package.
$ meteor add tmeasday:publish-counts
So this how your code should looks alike.
//server.js
Meteor.publish('applicationsCount', function() {
Counts.publish(this, 'applicationsCount', Applications.find());
});
on the lib folder.
if(Meteor.isClient){
Meteor.subscribe('applicationsCount')
Counts.get('applicationsCount');
}
Now look that Counts.get works like a helper, so you can use it on the template like this.
<span> There is a Total of {{getPublishedCount 'applicationsCount'}} Applications</span>
Thanks to Ethann I made it work.
First I installed the publish-counts package
$ meteor add tmeasday:publish-counts
As Ethann said, I published the count on my server\publications.js
Meteor.publish('applicationsCount', function() {
Counts.publish(this, 'applicationsCount', Applications.find());
});
And I updated my iron-router Controller like that :
ApplicationsListController = RouteController.extend({
template: 'applicationsList',
increment: 10,
(...)
subscriptions: function(){
this.applicationsSub = Meteor.subscribe('applications', this.findOptions()) ;
this.applicationsCount = Meteor.subscribe('applicationsCount');
},
(...)
data: function(){
var hasMore = this.applications().fetch().length === this.limit();
var nextPath = this.route.path({applicationsLimit: this.limit() + this.increment});
Counts.get('applicationsCount');
return {
applications: this.applications(),
ready: this.applicationsSub.ready,
nextPath: hasMore ? nextPath : null
};
}
});
To finally call the count in my template:
<span> There is a Total of {{getPublishedCount 'applicationsCount'}} Applications</span>
Thanks a lot. Hope it will help some people around here.

How do I show loading templates for with routes with multiple subscriptions when using iron router 1.0 on meteor?

I have a project which includes multiple subscriptions which are shared between routes.
I have been using a session variable to indicate when my subscription is loading. I can use these session variables as helpers in my templates and show different loading whirlygigs on my page. This is great when i have a single subscription (although in this case I could use the "loadingTemplate" i guess).
One subscription per route - no problems:
this.route('foos', {
path: '/foos/;id',
layoutTemplate: 'pagelayout',
yieldTemplates: {
'login_header': {to: 'header'},
'foos': {to: 'main'},
'footer': {to: 'footer'}
},
waitOn: function () {
Session.set('fooLoading',true);
return Meteor.subscribe("foos", this.params.id, Session.get("some-input"), {
onReady: function() {
Session.set('fooLoading',false);
},
onError: function (error) {
Session.set('fooLoading',false);
}
});
}
});
However, with multiple subscriptions I run into trouble because when one subscription is updated the waitOn hook runs, both session variables get set to loading but only one is reset when the subscription is ready.
Two subscriptions per route - problems:
this.route('foobar', {
path: '/foobar/;id',
layoutTemplate: 'pagelayout',
yieldTemplates: {
'login_header': {to: 'header'},
'foobar': {to: 'main'},
'footer': {to: 'footer'}
},
waitOn: function () {
//Can put logic here to figure out which subscription will re-run, but gets hacky and is harder to maintain.
Session.set('fooLoading',true);
Session.set('barLoading',true);
var fooHandle = Meteor.subscribe("foo", this.params.id, Session.get("some-input"), {
onReady: function() {
Session.set('fooLoading',false);
},
onError: function (error) {
Session.set('fooLoading',false);
}
});
var barHandle = Meteor.subscribe("bar", this.params.id, Session.get("some-other-input"), {
onReady: function() {
Session.set('barLoading',false);
},
onError: function (error) {
Session.set('barLoading',false);
}
});
return [fooHandle, barHandle];
}
});
I have been writing logic to decide if each subscription will re-run but this feels hacky and "un-meteor". If there was something on each subscription like onBeforeUpdate that would be awesome. Does such a thing exist or is there a more elegant solution?
You've to use the property this.ready(), check this example from Iron Router documentation: https://github.com/EventedMind/iron-router/blob/devel/examples/waiton/waiton.js

Resources