My Meteor app runs slowly in the beginning for about ten seconds, and then becomes fast again. I am trying to improve the performance but having troubles to find the real cause.
I thought the problem was that I am publishing all the course information like following:
if (Meteor.isServer) {
Meteor.publish("courses", function() {
return Courses.find();
});
}
I tried using Kadira to monitor exactly what's happening. However, looking at the result, I am starting to think maybe it's not the real problem.
If it only takes 292ms for pubsub response time, it shouldn't feel that laggy but I cannot think of any other reason why the app would be so slow in the beginning and become fast again. Can an expert point me to the redirection?
UPDATE:
I could improve the duration of lagginess in the beginning by making the following changes:
in /server/publications.js
if (Meteor.isServer) {
Meteor.publish("courses", function() {
// since we only need these two fields for the search bar's autocomplete feature
return Courses.find({}, {fields: {'catalog':1, 'titleLong':1}});
});
Meteor.publish("courseCatalog", function(catalog) {
// publish specific information only when needed
return Courses.find({"catalog": catalog});
});
}
and in router.js I made changes accordingly so I subscribe based on specific pages. But there's still some lag in the beginning and I wonder if I can make more optimizations, and what is the real cause of the slowness in the beginning.
UPDATE2:
I followed the suggestion and made changes like below:
Session.set('coursesReady', false); on startup.
and in router:
Router.route('/', function () {
Meteor.subscribe("courses", function(err) {
if (!err) {
console.log("course data is ready")
Session.set('coursesReady', true);
}
});
....
and in /lib/helpers.js which returns data for typeahead library
if (Meteor.isClient) {
Template.registerHelper("course_data", function() {
console.log("course_data helper is called");
if (Session.get('coursesReady')) {
var courses = Courses.find().fetch();
return [
{
name: 'course-info1',
valueKey: 'titleLong',
local: function() {
return Courses.find().fetch();
},
template: 'Course'
},
But now the problem is that when the helper function is called, the data is never ready. The console print:
Q: How do I ensure that the helper function is called only after the data is ready, OR called again when the data is ready? Since Session is reactive, shouldn't it be called again automatically?
I can't check this right now, but I believe your issue might be that the course_data helper is being run multiple times before all 1000+ documents in the subscription are ready, causing the typeahead package to re-run some expensive calculations. Try something like this:
/client/views/global/helpers.js
Template.registerHelper("course_data", function() {
if (!Session.get('coursesReady')) return [];
return [ //...
/client/subscriptions.js
Meteor.subscribe("courses", function(error) {
if (!error) Session.set('coursesReady', true);
});
Update:
Really, Meteor's new features this.subscribe() and Template.instance().subscriptionsReady() are ideal for this. Session isn't really the right choice, but it should still be reactively updating (not sure why it isn't for you). Try instead making the following changes to /client/views/navwithsearch.js (and main, though ideally both templates should share a single search template):
Template.NavWithSearch.onCreated(function() {
this.subscribe('courses');
});
Template.NavWithSearch.onRendered(function() {
this.autorun(function() {
if (Template.instance().subscriptionsReady()) {
Meteor.typeahead.inject();
}
});
});
The idea is to tie the lifecycle of the subscription to the view that will actually be using that subscription. This should delay the typeahead injection until the subscription is completely ready.
Related
I have a template that subscribes to a document. Everything works fine in the DOM and Blaze updates as soon as an attribute used in the template helpers is changed.
I also have some custom logic that doesn't appears in the DOM and depends on the document attributes. How can I call a function to change that logic when an attribute is updated?
I'm looking for something like this.data.attr.onChanged where this would refer to the template and this.data is the data send to the template, as usual; or a Meteor function that is rerun on change where I could put my callback in.
I hoped that template.onRendered would be recalled, but that's not the case.
I've read a lot about reactive variables, but could not find how they could be useful here.
[edit] the change is coming from the server that is communicating with another service
I've tried Tracker.autorun like this:
Template.editItem.onRendered(function() {
var self = this;
Tracker.autorun(function () {
console.log("tracker", self.data.item.socketId);
});
});
And the corresponding route is:
Router.route('editItem', {
path: '/edit/:_id',
waitOn: function () {
var sub = Meteor.subscribe('item', this.params._id);
return [sub];
},
data: function () {
return {item: Items.findOne(this.params._id)};
},
action: function () {
if (this.ready())
this.render();
}
});
At some point, the property socketId gets removed from the corresponding document by the server and I'm sure of that since I've checked in the shell, but the tracker doesn't rerun.
Use Template.currentData().item.socketId instead of self.data.item.socketId, this will give you reactivity.
And in templates generally, use self.autorun instead of Tracker.autorun (unlike Tracker.autorun, this will ensure that the autorun is stopped when the template is destroyed). Likewise, if you want to subscribe in a template, use self.subscribe instead of Meteor.subscribe.
Code to see if Template.currentData() works for you:
Template.editItem.onRendered(function() {
var self = this;
self.autorun(function () {
console.log("tracker", Template.currentData().item.socketId);
});
});
I'm not sure if I got you right, you just want to observe your html inputs and apply the new value to your helper method(s) on change?!
If so, you could use session variables to store your temporary UI state:
// observe your input
Template.yourTemplate.events({
"change #inputA": function (event) {
if(event.target.value != "") {
Session.set("valueA", event.target.value);
}
}
}
// apply the changed value on your helper function
Template.yourTemplate.helpers({
getSomeData: function() {
var a = Session.get("valueA");
// do something with a ..
}
}
In meteor's official todo app tutorial this concept is also used.
If you need to re-run something which is not part of DOM/helper, you can use Tracker.autorun. According to meteor docs, Run a function now and rerun it later whenever its dependencies change.
here's the docs link
Try moving the subscription into Tracker.autorun
Template.editItem.onRendered(function() {
var self = this;
Tracker.autorun(function () {
Meteor.subscribe('item', this.params._id);
console.log("tracker", self.data.item.socketId);
});
});
Of course you can't use this.params there so you can store this as a Session variable
I'm not sure why this code works once in a while and fails other times:
var u = Meteor.users.findOne(username:'john');
console.log(u);
When I go to my page for the first time, sometimes the console.log(u) shows some results. But if I press refresh, console.log(u) shows undefined. I can't consistently reproduce one issue or the other. It seems pretty random when i get undefined or a collection. What's wrong with my code? How do I consistently get a collection for the variable u?
Like Christian Fritz said in comment on your question, it's probably a matter of collection not being fully loaded when your code is executed. If you use iron:router, you can use subscribe or waitOn as described there: http://iron-meteor.github.io/iron-router/#the-waiton-option so the page is loaded only when the collections are ready (meaning they are fully loaded).
You can also put it in a helper or use a Tracker Autorun to detect when your entry is available and then do whatever you want to do with it.
Edit: A sample for iron:router below
// myproject.jsx
var Cars = new Mongo.Collection('cars');
if(Meteor.isServer)
{
Meteor.publish("myCollections", function () {
return Meteor.users.find();
});
Meteor.publish("anotherCollection", function(){
return Cars.find();
});
}
//lib/router.js
Router.route('/my-page', {
name: 'myPage',
layoutTemplate: 'myPage',
waitOn: function() {
'use strict';
return [Meteor.subscribe('myCollection'),Meteor.subscribe('anotherCollection')];
},
data: function() {
'use strict';
return Collection.findOne();
}
});
I'm struggling to get my head around the ability to edit any collection that's available to the front-end, and how to prevent it - and if this is a feature only available to Mongol.
Mongol states:
… because Mongol is a debugOnly package, it does not compile to production code.
Which is great, but as I'm new to Meteor I'm not sure if Mongol is just an interface in this scenario, or if the ability to update is something always available to the front-end (and Mongol is just making it easier).
My scenario is that I have a form submission page that grabs the profile of an associated Meteor.user to display their name along with the form:
HTML
<template name="form">
<h2>Submission for: {{ user.profile.name }}</h2>
<form id="brief">
…
</form>
</template>
Route
Router.route('/form/:_id', {
loadingTemplate: 'loading',
waitOn: function () {
return Meteor.subscribe('forms', this.params._id);
},
action: function () {
this.render('form', {
data: {
_id: this.params._id,
form: function() {
return Forms.findOne({});
},
user: function() {
return Meteor.users.findOne({});
}
}
});
}
});
Publication
Meteor.publishComposite('forms', function(formId) {
return {
find: function() {
return Forms.find({_id: formId});
},
children: [
{
find: function(form) {
return Meteor.users.find({_id: form.userId}, {fields: {profile:1}});
}
}
]
};
});
This works perfectly - however using the Mongol console I can update, duplicate and remove the user. Naturally in a production environment I wouldn't want this to be possible - is this something only available because Mongol is there, or could a determined user achieve the same thing without Mongol?
If they can, how do I prevent/work with it?
Edit: It's also elaborated on here: https://github.com/msavin/Mongol/blob/master/documentation/SECURITY.md
Given how they refer to 'special methods' I'm assuming that's what allows this to happen, and that the ability to directly update the fields isn't ordinarily available to the front-end. If anyone's able to confirm that would be ace!
Yes, Mongol uses a backdoor solution (in debug/dev only) to access and change your mongo docs in the db. This means it wont be included in your production code. As far as client side operations on the DB, Meteor restricts updating, removing, and inserting to the server although you can use Meteor's allow/deny rules to allow the client to update a DB collection. However, allow/deny rules need to be very tight to ensure the client can not alter data they should not be able to. For this reason, most people stick to using server side DB changes that are fired by meteor.methods that the client can initialize.
Since it is a debugOnly package as long as you don't deploy to production in "debug mode" it is safe.
Current common Iron Router pattern is to display a loading template while waiting for data to be available. But I would prefer simply to wait on the previous rendered template/data context until data is available and then trigger rerendering. Data is quickly available, so that short flicker of loading template is worse than a short delay user will experience after a link click.
Does this pattern work for you?
Router.route('/', {
name: 'nameOfTemplate',
data: function() { return CollectionName.find({title: 'nameOfMongoDBQuery'}); },
waitOn: function() { return Meteor.subscribe('nameOfSubscription'); } // waits until resources arrive before rendering page
});
You'll want to specify an explicit action function like so:
action: function() {
if (this.ready()) {
this.render();
}
}
This will just not render anything until the data is around.
Currently I'm creating an app with meteor en learning it while building. I try to incorporate Sessions instead of writing everything to the database (what I was doing). In my understanding Session is a global object which stores key-value pairs and is reactive. Therefore I thought it would a great choice to use for template rendering specifics in my simple game. My goal is small game, and the different steps will be renderend in a template for each player based on certain action they made.
I rewrote my app and wanted to use Session in this way (simplified of course).
My template:
<template name="gameRoom">
<button id='click'>click</button>
{{#if lastAction}}
{{>waiting}}
{{/if}}
</template>
Template.gameRoom.events({
lastAction: function() {
return Session.get('lastAction') === Meteor.userId();
};
})
Template.gameRoom.helpers({
'click #click' : function() {
Session.set('lastAction', Meteor.userId());
};
})
However this doesn't work they way I thought it would work. It looks like that each Session is individual for each user (what makes sense of course considering it's (sort-of) a replacement of cookies).
So my question is:
Is there a good alternative for Sessions? Do i really need to have a
alternative or is using a database ok for this? What about a local database?
Your events and helpers functions are backwards, you're missing a couple curly braces, and your event key (the button's ID) is wrong. Try this:
Template.gameRoom.helpers({
lastAction: function() {
return Session.equals('lastAction', Meteor.userId());
}
});
Template.gameRoom.events({
'click #click': function() {
Session.set('lastAction', Meteor.userId());
}
});
Edit: from what you are trying to do, it might make sense to do something like this:
Actions = new Meteor.Collection('actions');
if (Meteor.isClient) {
Template.gameRoom.events({
'click #click': function() {
Actions.insert({userId: Meteor.userId()});
}
});
Template.gameRoom.helpers({
lastAction: function() {
var lastAction = Actions.findOne() || {};
return lastAction.userId === Meteor.userId();
}
});
}