Why would iron-router ignore waitOn? - meteor

onAfterAction is run twice, once before the data arrives, and once after. Why is it run before the data arrives? Also, in this basic version, rendered is called after the data arrives, but in my app, it's called before the data arrives. Any idea why that might be? Basic reproduction:
https://github.com/lorensr/waiton-bug
Items = new Meteor.Collection 'items'
Router.configure
waitOn: ->
Meteor.subscribe 'items'
if Meteor.isServer
Meteor.publish 'items', ->
Items.find {}
Router.route '/',
name: 'hello'

You don't have a loadingTemplate defined. Iron Router can't use the loading template if you don't have one so the effect is the waiting effect of waitOn is ignored.
Simply add the loadingTemplate and it should work.
The onAfterAction is run once and after. The first when its waiting, the other times when there is a reactive change or the data is ready. If you want something that doesn't do this use onRun instead.

You are telling the Router to what for a collection subscription. The collection and the subscription both are reactive data sources. So when that collection changes, the waitOn fill fire and update the route including the onAfterAction.

Related

Meteor: why autorun executed twice in onCreated

When I declare autorun subscribe in the Template.onCreated function,
the autorun was fired twice so the publication is triggered twice as well.
Route.route 'moneyDepositRequest',
onRun: ->
console.log 'onRun'
Session.set 'condition', where: name: 'example'
Template.moneyDepositRequest.onCreated ->
console.log 'onCreated'
#autorun ->
console.log 'autorun'
#subscribe 'data', Session.get('condition')
if Meteor.isServer
Meteor.publish 'data', (condition) ->
console.log 'data publication'
Data.find()
This code shows this sequence of logs.
'data publication'
'onRun'
'onCreated'
'autorun'
'data publication'
I don't understand why 'data publication' is fired in advance before onCreated executed.
If I declared this autorun action in the onRenderd function, the problem is solved.
How could I understand this?
Tracker.autorun always executes the code right away, and then later whenever any reactive variables or functions within it change. So it's running once right away, and then immediately again when the route is triggered. Try this instead:
Template.moneyDepositRequest.onCreated ->
console.log 'onCreated'
#autorun =>
console.log 'autorun'
if !!Session.get('condition')
#subscribe 'data', Session.get('condition')
BTW, as a side note, I start moving away from CoffeeScript. ES6 will slowly quickly become the new standard, and CoffeeScript has no roadmap for ES6 transpilation.
I've found my huge mistake finally.
I've set the Session.set 'condition' on the other route using same template, so the Reactive was fired by that new Session condition before the route moved.
And there's one more thing.
If you use IronRouter and use template for several routes, you need to declare template: 'templateName' for every route's map.
For example just suppose that we have three router using same template.
Router.route 'first'
Router.route 'second',
template: 'sharedTemplate'
Router.route 'third',
template: 'sharedTemplate'
In this case, the first route will be fired with onCreated template hooks so the autorun and subscription will be fired again. (for the other templates, the screens are changed for data changes only by reactive subscription.
So if we declare template: 'sharedTemplate' on the first route,
it will work as we expected. (only changed by data when route was changed).
So all the happening was my stupid confusion, but the reactive is complex enough still if we want to use flexibly.

Iron router + observechanges = repeated observechanges handler calls?

I'm attempting to use observechanges with iron router but they don't seem to be compatible at all.
Router.route('/gaming', {
waitOn: function() {
return Meteor.subscribe('chat', function() {
window.chatmessagesCache = new ReactiveVar;
chatmessagesCache.set([]);
return chat.find().observeChanges({
added: function(id, doc) {
var tmpArr;
tmpArr = chatmessagesCache.get();
tmpArr.push(doc);
return chatmessagesCache.set(tmpArr);
}
});
});
}
If I leave the route and come back to it, observechanges begins being handled as many times as I've navigated away and returned, for each new record. What's the deal?
If I use subs manager it works as expected, but I don't understand why Meteor.subscribe inside waitOn is so cache/subscription unaware when it ALREADY gets called multiple times per load. Why!? I can't decipher what's causing this behavior at all.
Also, what I'm trying to accomplish is simple. I want to let chat messages that the user's client has received remain on the page even if the chat cursor is no longer publishing them (I'm publishing the last 10 chat messages)
Iron router has reactivity built in, which means when something inside your route function is invalidated, it will repeat the function as well as anything reactive with a Router.current(). These unexpected invalidation runs are a primary reason why folks made the exodus to flow router.
To solve this, you'll want to abstract your code away from the router. You can leave the sub, but I'd suggest you remove the sub's callback from the waitOn and move it into an onRendered callback. If you don't want the history loaded in chunks, you can first do a var collectionCount = chat.find({},{reactive:false}).count() on how many docs are in the collection & then in the added callback you can do something like if (++currentCount === collectionCount) /* add stuff */ to add al the records to the history when it reaches the last record.
On a bigger picture level, consider eliminating the observeChanges & just do an #each over the chat collection in spacebars to show your messages. Fewer cycles, cleaner code.
Iron router just has no management of observations you created yet it manages subscriptions itself, hence the multiple additions.
I figured this out by using a window level variable to check if I'm observing. Even in cases when the subscription is unhooked by iron, if I go back and never re-add the handler, the original observation hook still runs (!). ALSO, if you navigate away and drop the subscription, the handler is no longer called--which is the behavior I want in this scenario (This is all very insane behavior but at least it's now predictable to me )
This is caused by the fact that subscriptions != collections and the API for observations doesn't seem to expose any metadata, unfortunately, so I don't know how the iron router maintainers would account for this. Not to mention you return iron router a subscription, not a collection.
#Matt K if you were correct, this would always be an infinite loop (which admittedly I had a bunch of while trying to solve this) but the posted code is adding too many handlers, not looping indefinitely. Thanks for the post though.
This is what I settled on
Router.route('/gaming',
waitOn: ->
Meteor.subscribe('chat', ->
window.chatmessagesCache = new ReactiveVar(chat.find().fetch().reverse())
if !window.chatListening
window.chatListening = true
after = chat.find().count()
chat.find().observe(
added: _.after(after + 1,(doc) ->
tmpArr = chatmessagesCache.get()
tmpArr.push(doc)
chatmessagesCache.set(tmpArr))
changed : (id, doc) ->
))
I really just wanted to test out a pattern of locally "disconnected" documents. I still may use subs manager because it keeps subscriptions and their handlers alive without rerunning them constantly (which was rerunning the sub handler, which was adding multiple observations)

The best pattern to organize my subscriptions in Meteor without the risk to find the data not ready

I read some of the posts here about how to organize subscriptions in Meteorjs, but I still I don't understand what is the best pattern to avoid finding out some data I subscribed are not ready to be used in a template. I use Iron Router and now I have all my subscriptions organized using a waitOn option in Router.configure. I find out that this way doesn't help sometimes. If I have multiple subscriptions like this:
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function () {
return [
Meteor.subscribe('channels'),
Meteor.subscribe('invitations'),
Meteor.subscribe('messages')
];
}
});
I understood that order matters. If I change the order of my subscriptions in the array, the program respond differently.
What I want to get is that ALL my subscriptions get completely loaded before navigating the app.
Someone in a previous post talked about putting them in a separate file to solve this problem. But how? Where do I need to put that file?
I would appreciate some examples here for my case.
With the release of Meteor 1.0.4 the templates instances now have a subscribe method that works exactly like the Meteor.subscribe, check this release notes to more information about
So you can use that subscriptions inside a onCreated like the follow example.
Template.notifications.onCreated(function () {
this.subscribe("channels");
this.subscribe("invitations");
this.subscribe("messages");
});
Check Meteor Docs about the subscriptionsReady()

Meteor, Iron Router and collection state in the client/server

This is my code:
Router.route "/", ->
users = Meteor.users.find().fetch().length
if users > 0
#render 'header'
else
#render 'register'
And the problem is simple: the route is called 2 time, first in the find() and later in the find.fetch().
I only want to know the number of user to determinate what template render, but in the client side is necessary fetch first, and in the server I can do it, but I don't know how to say that render the template from the client....
I need something like callback after find to know the result and do an action. How do it? Only determinate the length and take a decision, it's simple!
The reason your route is being called twice is that there is some latency before the data from your server arrives client, so when you first call it,
Meteor.users.find().fetch()
is just an empty array. Only after a fraction of second it becomes populated with some data. In general there is little you can do about, but in your particular case - since you only need to know if a user is logged in or not - you can use Meteor.userId() function instead.
If by some reason, you prefer to have the collection data being fetched prior to your route's action, then you should probably use waitOn hook with a custom subscription which is responsible for fetching the set of data you're interested in.

What triggers an onAfterAction in IronRouter other than a route change?

I'm having trouble debugging an onAfterAction that I don't want to run. It happens when I click a certain div. Router.go is not being called (verified with debugger; in the iron router code), and the URL is not changing. I can't find anything in my click handlers that would cause a route change. The onAfterAction happens from a deps invalidation:
Router.configure.onAfterAction (routes.coffee:5)
RouteController.runHooks (route_controller.js:155)
(anonymous function) (route_controller.js:291)
RouteController.runHooks (route_controller.js:158)
(anonymous function) (route_controller.js:283)
Deps.Computation._compute (deps.js:196)
Deps.Computation._recompute (deps.js:210)
Deps.flush (deps.js:304)
Probably you have some reactive data source in onAfterAction (cursor, Session object).
It will rerun every time reactive data source will be changed.
You can forbid this behavior by wrapping function in onAfterAction :
Tracker.nonreactive(function(){})
Clicking the div caused the User doc to change. There was global onBeforeAction that checked to see if the user was logged in (if Meteor.user()), which is reactive and triggered a rerun of action and onAfterAction. I changed it to if Meteor.userId(), which did not get invalidated when updating the user doc.

Resources