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

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.

Related

Meteor: show maintenance page in client side from a server side variable

I want to use reactive information on the client side to disable some functionalities while the server is performing an important heavy task.
I tried using a publication but even though the subscription in the client was inside an autorun it was not updating the field. I’m not sure if using a publication is the best option.
Let’s say I have a server variable called “IN_MAINTENANCE” and in the client side I want to load a specific template for the maintenance page, but never allowing the user to change this (The variable should only be defined in the server side).
How can I achieve this without storing anything in the Database?
Pretty sure you have to use the database for it to be a reactive update that applies to all users. You could use a settings variable, but that would require an app restart to take effect. This answer has code you can repurpose for this.
I think your first instinct was right in doing a publication. Reacting to a data change is the only way for the server to influence the client. Make sure that your publish and subscribe is working correctly, and then in your autorun function you can take the user (and all users) to the maintenance page.
This is a Meteor brain teaser right? The "without storing anything in the db" is the interesting bit.
Let's say that you have some publication that all clients are getting, whether logged in or not, and that is expected to return at least one document:
Meteor.publish('default',function(){
let query = ...
let options = ...
return MyCollection.find(query,options);
});
You could signal to the client by just returning nothing:
Meteor.publish('default',function(){
if ( IN_MAINTENANCE ) {
return;
} else {
let query = ...
let options = ...
return MyCollection.find(query,options);
}
});
Then on the client you just look to see that the subscription is ready but empty and switch layouts based on that.
There are 100 solutions to this puzzle, this is just a particularly hacky one.

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)

How can I avoid an infinite loop in my meteor router?

I'm building an online store in meteor where customers can customize products in the store. I have setup a client-only collection called Inventory which stores all the product data and is updated accordingly in response to user input. Once the user is ready to checkout, I dump the product data into a client & server side collection called ShoppingCart. I want to allow users to go back and revise their edits on the product in Inventory so I setup my router to $set data from the ShoppingCart into Inventory if it finds a match:
Router.route '/:_type/:_id', ->
Session.set "inCart", false
#render #params._type,
data: =>
storedItem = ShoppingCart.findOne {
userId: Meteor.userId(),
image: #params._id
}
if storedItem?
delete storedItem._id
Inventory.update {image: #params._id}, {
$set: storedItem
}
Inventory.findOne image: #params._id
EDIT: This seems to cause my router method to get stuck in an infinite loop whenever data in Inventory changes. Is there any way to avoid this issue? Is there a better way of handling this kind of data altogether that I should consider?
MAJOR CAVEAT - I don't do CoffeeScript, so this is what I can gather having put your code through a compiler.
I think the problem is that the data function is reactive, and you're updating and returning an item from the Inventory collection within it. Every time the route runs, unless there is no storedItem, it's going to invalidate a computation on which it itself depends and thus rerun again immediately (and subsequently do the same again, etc...).
As a general rule, I think it's a very bad idea indeed to be updating a collection from within a data function - if you have to do this within the route function, consider the onRun, or onBeforeAction hooks for the update.
Final thing, just because I don't understand: why do you need to dump the item from the ShoppingCart back into Inventory? Shouldn't it already be there, unless the user has started a new session?

Meteor - how can I empty out the collection of 10,000 objects I subscribed to after I no longer need it?

I have a template in which a user should be able to click on a button to bring up a modal and in the modal choose a handful of items out of a list of about 10,000 items which are displayed there to search or scroll through.
Since this collection is so big, I don't want to keep it around in memory when I don't absolutely need it.
So I would like to subscribe to this collection only when the modal is being viewed and I would like to ensure that I am unsubscribed if the modal is not being viewed.
Is there a way to explicitly unsubscribe from a collection?
There are a couple of ways you can do this:
Use the subscription handle
subscribe returns a handle you can call stop on. For example:
var handle = Meteor.subscribe('stuff');
handle.stop();
Use an autorun
Because an autorun will automatically start and stop subscriptions when its reactive dependencies change, this will work:
Tracker.autorun(function () {
if (Session.get('showingModal'))
Meteor.subscribe('stuff');
});
Side note - it may make more sense to use a method call for searching such a large data set rather than publishing the entire thing to the client. For example you can set a session variable whenever the user's query changes, then use an autorun to update the result set based on the method's return value.
https://docs.meteor.com/#/full/meteor_subscribe
Quoting the docs :
Meteor.subscribe returns a subscription handle, which is an object
with the following methods:
stop() Cancel the subscription. This will typically result in the
server directing the client to remove the subscription's data from the
client's cache.
So basically what you need to do is storing the subscription handle in a variable and call the stop method when you don't need those published documents anymore.
Note that if you're using iron:router (and you probably should), this is taken care of automatically for you on each route change, which is convenient but has the side effect of provoking a lot of sometimes unnecessary calls to Meteor.publish calls which are non trivial for the server and bandwidth... to address this matter you can use meteorhacks:subs-manager but it's another topic anyway.

Is there any way to know when an meteor subscription is 'valid'?

If I change a Session var and trigger a re-subscription via autosubscribe, is there any callback mechanism to wait until the 'latest' data is down from the server? [1]
If you take a look at this gist you'll see some code that logs the content of a collection over time as the subscription changes. A relevant section of the output:
at Subscribed; comments are: first post on #1 - second post on #1
at Flushed; comments are: first post on #1 - second post on #1
at Subscription complete; comments are: first post on #1 - second post on #1 - first post on #2 - second post on #2
So, even after (a) calling .subscribe, (b) calling Meteor.flush (c) being inside the onReady callback for the .subscribe; there is still stale data in the collection, and only in the 3rd case is the 'correct' data in there.
I realise that reactive templates and .observe will eventually receive the correct data and things will 'settle' into the correct state. But is there some way we can tell that we aren't there yet?
For instance, most of the meteor example apps (and my own apps) are prone to jerking around a bit (similar to a FOUC) while data is added + removed from a subscribed collection. If we could tell that a subscription was 'loading' we could do something about this.
[1] Obviously the data on the server is constantly changing, but as you'll see in the gist, I can't (without a timeout) find a point where it's even correct. Thus my use of 'valid' in the question.
A very simple and common use case
Take the madewith app; when you first load it up it appears that there are no apps registered, until after the data comes down the wire and the apps suddenly appear.
The reason for this is that Meteor.subscribe has been called, but the data has not yet come down the wire. But there's no easy way for the template to tell that the data is pending and that it should show a 'loading' template. In madewith they actually do something when the data has loaded, but this is a callback and thus breaks out of the normal meteor way of doing things (i.e. reactive coding).
It would be much nicer (IMO) to be able to write something like:
{{unless apps_loaded}}{{> loading}}{{/unless}}
and
Template.madewith.apps_loaded = function() { return !Apps.isComplete(); }
Interesting question.
The right place to be notified for new subscriptions is the onReady callback. Notice the logging that happens there always includes your new data. Checking at (a) and (b) aren't useful, because there's latency between calling subscribe and when all the data arrives from the server.
The underlying problem is there's no equivalent onRemove callback that runs once the data for a just-stopped subscription has been removed. Moreover, autosubscribe deliberately starts new subs before stopping old ones, to avoid flicker.
What's the real use case? Most of the time such a callback isn't necessary, because templates can also restrict their queries to the data that should be in scope. In your example, the template helper that renders comments might query only for comments with the current post_id in the Session, so there's no harm having extra comments in the database.
Something like this:
Template.post.comments = function () {
return Comments.find({post_id: Session.get('post_id')});
};
That permits strategies more sophisticated that the generic autosubscribe function, like subscribing to comments for the last three posts the user has viewed. Or, subscribing to the first few comments for each post, and then separately subscribing to a post's full set of comments only when that post is selected.
I've come a bit late to the meteor party, so I don't know the history behind when meteor features have been added, but just for completeness it is now possible to do this using template-level subscriptions and 'subscriptionsReady' :
[js]
Template.myTemplate.onCreated(function() {
this.subscribe('myData');
});
[html]
<template name="myTemplate">
<h2>my Template</h2>
{{#if Template.subscriptionsReady}}
// code to loop/display myData
{{else}}
<p>Please wait..</p>
{{/if}}
</template>

Resources